/* * m3d.h * * Copyright (C) 2019 bzt (bztsrc@gitlab) * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * @brief ANSI C89 / C++11 single header importer / exporter SDK for the Model 3D (.M3D) format * https://gitlab.com/bztsrc/model3d * * PNG decompressor included from (with minor modifications to make it C89 valid): * stb_image - v2.13 - public domain image loader - http://nothings.org/stb_image.h * * @version: 1.0.0 */ #ifndef _M3D_H_ #define _M3D_H_ #ifdef __cplusplus extern "C" { #endif #include /*** configuration ***/ #ifndef M3D_MALLOC #define M3D_MALLOC(sz) malloc(sz) #endif #ifndef M3D_REALLOC #define M3D_REALLOC(p, nsz) realloc(p, nsz) #endif #ifndef M3D_FREE #define M3D_FREE(p) free(p) #endif #ifndef M3D_LOG #define M3D_LOG(x) #endif #ifndef M3D_APIVERSION #define M3D_APIVERSION 0x0100 #ifndef M3D_DOUBLE typedef float M3D_FLOAT; #ifndef M3D_EPSILON /* carefully choosen for IEEE 754 don't change */ #define M3D_EPSILON ((M3D_FLOAT)1e-7) #endif #else typedef double M3D_FLOAT; #ifndef M3D_EPSILON #define M3D_EPSILON ((M3D_FLOAT)1e-14) #endif #endif #if !defined(M3D_SMALLINDEX) typedef uint32_t M3D_INDEX; #define M3D_UNDEF 0xffffffff #define M3D_INDEXMAX 0xfffffffe #else typedef uint16_t M3D_INDEX; #define M3D_UNDEF 0xffff #define M3D_INDEXMAX 0xfffe #endif #define M3D_NOTDEFINED 0xffffffff #ifndef M3D_NUMBONE #define M3D_NUMBONE 4 #endif #ifndef M3D_BONEMAXLEVEL #define M3D_BONEMAXLEVEL 8 #endif #if !defined(_MSC_VER) || defined(__clang__) #ifndef _inline #define _inline __inline__ #endif #define _pack __attribute__((packed)) #define _unused __attribute__((unused)) #else #define _inline #define _pack #define _unused __pragma(warning(suppress : 4100)) #endif #ifndef __cplusplus #define _register register #else #define _register #endif #if _MSC_VER > 1920 && !defined(__clang__) # pragma warning(push) # pragma warning(disable : 4100 4127 4189 4505 4244 4403 4701 4703) # if (_MSC_VER > 1800 ) # pragma warning(disable : 5573 5744) # endif #endif // _WIN32 /*** File format structures ***/ /** * M3D file format structure * 3DMO m3dchunk_t file header chunk, may followed by compressed data * HEAD m3dhdr_t model header chunk * n x m3dchunk_t more chunks follow * PRVW preview chunk (optional) * CMAP color map chunk (optional) * TMAP texture map chunk (optional) * VRTS vertex data chunk (optional if it's a material library) * BONE bind-pose skeleton, bone hierarchy chunk (optional) * n x m3db_t contains propably more, but at least one bone * n x m3ds_t skin group records * MTRL* material chunk(s), can be more (optional) * n x m3dp_t each material contains propapbly more, but at least one property * the properties are configurable with a static array, see m3d_propertytypes * n x m3dchunk_t at least one, but maybe more face chunks * PROC* procedural face, or * MESH* triangle mesh (vertex index list) or * SHPE* mathematical shapes like parameterized surfaces * LBLS* annotation label chunks, can be more (optional) * ACTN* action chunk(s), animation-pose skeletons, can be more (optional) * n x m3dfr_t each action contains probably more, but at least one frame * n x m3dtr_t each frame contains probably more, but at least one transformation * ASET* inlined asset chunk(s), can be more (optional) * OMD3 end chunk * * Typical chunks for a game engine: 3DMO, HEAD, CMAP, TMAP, VRTS, BONE, MTRL, MESH, ACTN, OMD3 * Typical chunks for CAD software: 3DMO, HEAD, PRVW, CMAP, TMAP, VRTS, MTRL, SHPE, LBLS, OMD3 */ #ifdef _MSC_VER #pragma pack(push) #pragma pack(1) #endif typedef struct { char magic[4]; uint32_t length; float scale; /* deliberately not M3D_FLOAT */ uint32_t types; } _pack m3dhdr_t; typedef struct { char magic[4]; uint32_t length; } _pack m3dchunk_t; #ifdef _MSC_VER #pragma pack(pop) #endif /*** in-memory model structure ***/ /* textmap entry */ typedef struct { M3D_FLOAT u; M3D_FLOAT v; } m3dti_t; #define m3d_textureindex_t m3dti_t /* texture */ typedef struct { char *name; /* texture name */ uint8_t *d; /* pixels data */ uint16_t w; /* width */ uint16_t h; /* height */ uint8_t f; /* format, 1 = grayscale, 2 = grayscale+alpha, 3 = rgb, 4 = rgba */ } m3dtx_t; #define m3d_texturedata_t m3dtx_t typedef struct { M3D_INDEX vertexid; M3D_FLOAT weight; } m3dw_t; #define m3d_weight_t m3dw_t /* bone entry */ typedef struct { M3D_INDEX parent; /* parent bone index */ char *name; /* name for this bone */ M3D_INDEX pos; /* vertex index position */ M3D_INDEX ori; /* vertex index orientation (quaternion) */ M3D_INDEX numweight; /* number of controlled vertices */ m3dw_t *weight; /* weights for those vertices */ M3D_FLOAT mat4[16]; /* transformation matrix */ } m3db_t; #define m3d_bone_t m3db_t /* skin: bone per vertex entry */ typedef struct { M3D_INDEX boneid[M3D_NUMBONE]; M3D_FLOAT weight[M3D_NUMBONE]; } m3ds_t; #define m3d_skin_t m3ds_t /* vertex entry */ typedef struct { M3D_FLOAT x; /* 3D coordinates and weight */ M3D_FLOAT y; M3D_FLOAT z; M3D_FLOAT w; uint32_t color; /* default vertex color */ M3D_INDEX skinid; /* skin index */ #ifdef M3D_VERTEXTYPE uint8_t type; #endif } m3dv_t; #define m3d_vertex_t m3dv_t /* material property formats */ enum { m3dpf_color, m3dpf_uint8, m3dpf_uint16, m3dpf_uint32, m3dpf_float, m3dpf_map }; typedef struct { uint8_t format; uint8_t id; #ifdef M3D_ASCII #define M3D_PROPERTYDEF(f, i, n) \ { (f), (i), (char *)(n) } char *key; #else #define M3D_PROPERTYDEF(f, i, n) \ { (f), (i) } #endif } m3dpd_t; /* material property types */ /* You shouldn't change the first 8 display and first 4 physical property. Assign the rest as you like. */ enum { m3dp_Kd = 0, /* scalar display properties */ m3dp_Ka, m3dp_Ks, m3dp_Ns, m3dp_Ke, m3dp_Tf, m3dp_Km, m3dp_d, m3dp_il, m3dp_Pr = 64, /* scalar physical properties */ m3dp_Pm, m3dp_Ps, m3dp_Ni, m3dp_Nt, m3dp_map_Kd = 128, /* textured display map properties */ m3dp_map_Ka, m3dp_map_Ks, m3dp_map_Ns, m3dp_map_Ke, m3dp_map_Tf, m3dp_map_Km, /* bump map */ m3dp_map_D, m3dp_map_N, /* normal map */ m3dp_map_Pr = 192, /* textured physical map properties */ m3dp_map_Pm, m3dp_map_Ps, m3dp_map_Ni, m3dp_map_Nt }; enum { /* aliases */ m3dp_bump = m3dp_map_Km, m3dp_map_il = m3dp_map_N, m3dp_refl = m3dp_map_Pm }; /* material property */ typedef struct { uint8_t type; /* property type, see "m3dp_*" enumeration */ union { uint32_t color; /* if value is a color, m3dpf_color */ uint32_t num; /* if value is a number, m3dpf_uint8, m3pf_uint16, m3dpf_uint32 */ float fnum; /* if value is a floating point number, m3dpf_float */ M3D_INDEX textureid; /* if value is a texture, m3dpf_map */ } value; } m3dp_t; #define m3d_property_t m3dp_t /* material entry */ typedef struct { char *name; /* name of the material */ uint8_t numprop; /* number of properties */ m3dp_t *prop; /* properties array */ } m3dm_t; #define m3d_material_t m3dm_t /* face entry */ typedef struct { M3D_INDEX materialid; /* material index */ M3D_INDEX vertex[3]; /* 3D points of the triangle in CCW order */ M3D_INDEX normal[3]; /* normal vectors */ M3D_INDEX texcoord[3]; /* UV coordinates */ } m3df_t; #define m3d_face_t m3df_t /* shape command types. must match the row in m3d_commandtypes */ enum { /* special commands */ m3dc_use = 0, /* use material */ m3dc_inc, /* include another shape */ m3dc_mesh, /* include part of polygon mesh */ /* approximations */ m3dc_div, /* subdivision by constant resolution for both u, v */ m3dc_sub, /* subdivision by constant, different for u and v */ m3dc_len, /* spacial subdivision by maxlength */ m3dc_dist, /* subdivision by maxdistance and maxangle */ /* modifiers */ m3dc_degu, /* degree for both u, v */ m3dc_deg, /* separate degree for u and v */ m3dc_rangeu, /* range for u */ m3dc_range, /* range for u and v */ m3dc_paru, /* u parameters (knots) */ m3dc_parv, /* v parameters */ m3dc_trim, /* outer trimming curve */ m3dc_hole, /* inner trimming curve */ m3dc_scrv, /* spacial curve */ m3dc_sp, /* special points */ /* helper curves */ m3dc_bez1, /* Bezier 1D */ m3dc_bsp1, /* B-spline 1D */ m3dc_bez2, /* bezier 2D */ m3dc_bsp2, /* B-spline 2D */ /* surfaces */ m3dc_bezun, /* Bezier 3D with control, UV, normal */ m3dc_bezu, /* with control and UV */ m3dc_bezn, /* with control and normal */ m3dc_bez, /* control points only */ m3dc_nurbsun, /* B-spline 3D */ m3dc_nurbsu, m3dc_nurbsn, m3dc_nurbs, m3dc_conn, /* connect surfaces */ /* geometrical */ m3dc_line, m3dc_polygon, m3dc_circle, m3dc_cylinder, m3dc_shpere, m3dc_torus, m3dc_cone, m3dc_cube }; /* shape command argument types */ enum { m3dcp_mi_t = 1, /* material index */ m3dcp_hi_t, /* shape index */ m3dcp_fi_t, /* face index */ m3dcp_ti_t, /* texture map index */ m3dcp_vi_t, /* vertex index */ m3dcp_qi_t, /* vertex index for quaternions */ m3dcp_vc_t, /* coordinate or radius, float scalar */ m3dcp_i1_t, /* int8 scalar */ m3dcp_i2_t, /* int16 scalar */ m3dcp_i4_t, /* int32 scalar */ m3dcp_va_t /* variadic arguments */ }; #define M3D_CMDMAXARG 8 /* if you increase this, add more arguments to the macro below */ typedef struct { #ifdef M3D_ASCII #define M3D_CMDDEF(t, n, p, a, b, c, d, e, f, g, h) \ { \ (char *)(n), (p), { (a), (b), (c), (d), (e), (f), (g), (h) } \ } char *key; #else #define M3D_CMDDEF(t, n, p, a, b, c, d, e, f, g, h) \ { \ (p), { (a), (b), (c), (d), (e), (f), (g), (h) } \ } #endif uint8_t p; uint8_t a[M3D_CMDMAXARG]; } m3dcd_t; /* shape command */ typedef struct { uint16_t type; /* shape type */ uint32_t *arg; /* arguments array */ } m3dc_t; #define m3d_shapecommand_t m3dc_t /* shape entry */ typedef struct { char *name; /* name of the mathematical shape */ M3D_INDEX group; /* group this shape belongs to or -1 */ uint32_t numcmd; /* number of commands */ m3dc_t *cmd; /* commands array */ } m3dh_t; #define m3d_shape_t m3dh_t /* label entry */ typedef struct { char *name; /* name of the annotation layer or NULL */ char *lang; /* language code or NULL */ char *text; /* the label text */ uint32_t color; /* color */ M3D_INDEX vertexid; /* the vertex the label refers to */ } m3dl_t; #define m3d_label_t m3dl_t /* frame transformations / working copy skeleton entry */ typedef struct { M3D_INDEX boneid; /* selects a node in bone hierarchy */ M3D_INDEX pos; /* vertex index new position */ M3D_INDEX ori; /* vertex index new orientation (quaternion) */ } m3dtr_t; #define m3d_transform_t m3dtr_t /* animation frame entry */ typedef struct { uint32_t msec; /* frame's position on the timeline, timestamp */ M3D_INDEX numtransform; /* number of transformations in this frame */ m3dtr_t *transform; /* transformations */ } m3dfr_t; #define m3d_frame_t m3dfr_t /* model action entry */ typedef struct { char *name; /* name of the action */ uint32_t durationmsec; /* duration in millisec (1/1000 sec) */ M3D_INDEX numframe; /* number of frames in this animation */ m3dfr_t *frame; /* frames array */ } m3da_t; #define m3d_action_t m3da_t /* inlined asset */ typedef struct { char *name; /* asset name (same pointer as in texture[].name) */ uint8_t *data; /* compressed asset data */ uint32_t length; /* compressed data length */ } m3di_t; #define m3d_inlinedasset_t m3di_t /*** in-memory model structure ***/ #define M3D_FLG_FREERAW (1 << 0) #define M3D_FLG_FREESTR (1 << 1) #define M3D_FLG_MTLLIB (1 << 2) #define M3D_FLG_GENNORM (1 << 3) typedef struct { m3dhdr_t *raw; /* pointer to raw data */ char flags; /* internal flags */ signed char errcode; /* returned error code */ char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; /* decoded sizes for types */ char *name; /* name of the model, like "Utah teapot" */ char *license; /* usage condition or license, like "MIT", "LGPL" or "BSD-3clause" */ char *author; /* nickname, email, homepage or github URL etc. */ char *desc; /* comments, descriptions. May contain '\n' newline character */ M3D_FLOAT scale; /* the model's bounding cube's size in SI meters */ M3D_INDEX numcmap; uint32_t *cmap; /* color map */ M3D_INDEX numtmap; m3dti_t *tmap; /* texture map indices */ M3D_INDEX numtexture; m3dtx_t *texture; /* uncompressed textures */ M3D_INDEX numbone; m3db_t *bone; /* bone hierarchy */ M3D_INDEX numvertex; m3dv_t *vertex; /* vertex data */ M3D_INDEX numskin; m3ds_t *skin; /* skin data */ M3D_INDEX nummaterial; m3dm_t *material; /* material list */ M3D_INDEX numface; m3df_t *face; /* model face, polygon (triangle) mesh */ M3D_INDEX numshape; m3dh_t *shape; /* model face, shape commands */ M3D_INDEX numlabel; m3dl_t *label; /* annotation labels */ M3D_INDEX numaction; m3da_t *action; /* action animations */ M3D_INDEX numinlined; m3di_t *inlined; /* inlined assets */ M3D_INDEX numextra; m3dchunk_t **extra; /* unknown chunks, application / engine specific data probably */ m3di_t preview; /* preview chunk */ } m3d_t; /*** export parameters ***/ #define M3D_EXP_INT8 0 #define M3D_EXP_INT16 1 #define M3D_EXP_FLOAT 2 #define M3D_EXP_DOUBLE 3 #define M3D_EXP_NOCMAP (1 << 0) #define M3D_EXP_NOMATERIAL (1 << 1) #define M3D_EXP_NOFACE (1 << 2) #define M3D_EXP_NONORMAL (1 << 3) #define M3D_EXP_NOTXTCRD (1 << 4) #define M3D_EXP_FLIPTXTCRD (1 << 5) #define M3D_EXP_NORECALC (1 << 6) #define M3D_EXP_IDOSUCK (1 << 7) #define M3D_EXP_NOBONE (1 << 8) #define M3D_EXP_NOACTION (1 << 9) #define M3D_EXP_INLINE (1 << 10) #define M3D_EXP_EXTRA (1 << 11) #define M3D_EXP_NOZLIB (1 << 14) #define M3D_EXP_ASCII (1 << 15) /*** error codes ***/ #define M3D_SUCCESS 0 #define M3D_ERR_ALLOC -1 #define M3D_ERR_BADFILE -2 #define M3D_ERR_UNIMPL -65 #define M3D_ERR_UNKPROP -66 #define M3D_ERR_UNKMESH -67 #define M3D_ERR_UNKIMG -68 #define M3D_ERR_UNKFRAME -69 #define M3D_ERR_UNKCMD -70 #define M3D_ERR_TRUNC -71 #define M3D_ERR_CMAP -72 #define M3D_ERR_TMAP -73 #define M3D_ERR_VRTS -74 #define M3D_ERR_BONE -75 #define M3D_ERR_MTRL -76 #define M3D_ERR_SHPE -77 #define M3D_ERR_ISFATAL(x) ((x) < 0 && (x) > -65) /* callbacks */ typedef unsigned char *(*m3dread_t)(char *filename, unsigned int *size); /* read file contents into buffer */ typedef void (*m3dfree_t)(void *buffer); /* free file contents buffer */ typedef int (*m3dtxsc_t)(const char *name, const void *script, uint32_t len, m3dtx_t *output); /* interpret texture script */ typedef int (*m3dprsc_t)(const char *name, const void *script, uint32_t len, m3d_t *model); /* interpret surface script */ #endif /* ifndef M3D_APIVERSION */ /*** C prototypes ***/ /* import / export */ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib); unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size); void m3d_free(m3d_t *model); /* generate animation pose skeleton */ m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton); m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec); /* private prototypes used by both importer and exporter */ char *_m3d_safestr(char *in, int morelines); /*** C implementation ***/ #ifdef M3D_IMPLEMENTATION #if !defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER) /* material property definitions */ static m3dpd_t m3d_propertytypes[] = { M3D_PROPERTYDEF(m3dpf_color, m3dp_Kd, "Kd"), /* diffuse color */ M3D_PROPERTYDEF(m3dpf_color, m3dp_Ka, "Ka"), /* ambient color */ M3D_PROPERTYDEF(m3dpf_color, m3dp_Ks, "Ks"), /* specular color */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Ns, "Ns"), /* specular exponent */ M3D_PROPERTYDEF(m3dpf_color, m3dp_Ke, "Ke"), /* emissive (emitting light of this color) */ M3D_PROPERTYDEF(m3dpf_color, m3dp_Tf, "Tf"), /* transmission color */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Km, "Km"), /* bump strength */ M3D_PROPERTYDEF(m3dpf_float, m3dp_d, "d"), /* dissolve (transparency) */ M3D_PROPERTYDEF(m3dpf_uint8, m3dp_il, "il"), /* illumination model (informational, ignored by PBR-shaders) */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Pr, "Pr"), /* roughness */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Pm, "Pm"), /* metallic, also reflection */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Ps, "Ps"), /* sheen */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Ni, "Ni"), /* index of refraction (optical density) */ M3D_PROPERTYDEF(m3dpf_float, m3dp_Nt, "Nt"), /* thickness of face in millimeter, for printing */ /* aliases, note that "map_*" aliases are handled automatically */ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"), M3D_PROPERTYDEF(m3dpf_map, m3dp_map_N, "map_N"), /* as normal map has no scalar version, it's counterpart is 'il' */ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl") }; /* shape command definitions. if more commands start with the same string, the longer must come first */ static m3dcd_t m3d_commandtypes[] = { /* technical */ M3D_CMDDEF(m3dc_use, "use", 1, m3dcp_mi_t, 0, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_inc, "inc", 3, m3dcp_hi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0, 0), M3D_CMDDEF(m3dc_mesh, "mesh", 1, m3dcp_fi_t, m3dcp_fi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0), /* approximations */ M3D_CMDDEF(m3dc_div, "div", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_sub, "sub", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_len, "len", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_dist, "dist", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), /* modifiers */ M3D_CMDDEF(m3dc_degu, "degu", 1, m3dcp_i1_t, 0, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_deg, "deg", 2, m3dcp_i1_t, m3dcp_i1_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_rangeu, "rangeu", 1, m3dcp_ti_t, 0, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_range, "range", 2, m3dcp_ti_t, m3dcp_ti_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_paru, "paru", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_parv, "parv", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_trim, "trim", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_hole, "hole", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_scrv, "scrv", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_sp, "sp", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), /* helper curves */ M3D_CMDDEF(m3dc_bez1, "bez1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_bsp1, "bsp1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_bez2, "bez2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_bsp2, "bsp2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), /* surfaces */ M3D_CMDDEF(m3dc_bezun, "bezun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), M3D_CMDDEF(m3dc_bezu, "bezu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_bezn, "bezn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_bez, "bez", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_nurbsun, "nurbsun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), M3D_CMDDEF(m3dc_nurbsu, "nurbsu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_nurbsn, "nurbsn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_nurbs, "nurbs", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_conn, "conn", 6, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0), /* geometrical */ M3D_CMDDEF(m3dc_line, "line", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_polygon, "polygon", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_circle, "circle", 3, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_cylinder, "cylinder", 6, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0), M3D_CMDDEF(m3dc_shpere, "shpere", 2, m3dcp_vi_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_torus, "torus", 4, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0), M3D_CMDDEF(m3dc_cone, "cone", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), M3D_CMDDEF(m3dc_cube, "cube", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0) }; #endif #include #include #if !defined(M3D_NOIMPORTER) && !defined(STBI_INCLUDE_STB_IMAGE_H) /* PNG decompressor from stb_image - v2.23 - public domain image loader - http://nothings.org/stb_image.h */ static const char *_m3dstbi__g_failure_reason; enum { STBI_default = 0, STBI_grey = 1, STBI_grey_alpha = 2, STBI_rgb = 3, STBI_rgb_alpha = 4 }; enum { STBI__SCAN_load = 0, STBI__SCAN_type, STBI__SCAN_header }; typedef unsigned short _m3dstbi_us; typedef uint16_t _m3dstbi__uint16; typedef int16_t _m3dstbi__int16; typedef uint32_t _m3dstbi__uint32; typedef int32_t _m3dstbi__int32; typedef struct { _m3dstbi__uint32 img_x, img_y; int img_n, img_out_n; void *io_user_data; int read_from_callbacks; int buflen; unsigned char buffer_start[128]; unsigned char *img_buffer, *img_buffer_end; unsigned char *img_buffer_original, *img_buffer_original_end; } _m3dstbi__context; typedef struct { int bits_per_channel; int num_channels; int channel_order; } _m3dstbi__result_info; #define STBI_ASSERT(v) #define STBI_NOTUSED(v) (void)sizeof(v) #define STBI__BYTECAST(x) ((unsigned char)((x)&255)) #define STBI_MALLOC(sz) M3D_MALLOC(sz) #define STBI_REALLOC(p, newsz) M3D_REALLOC(p, newsz) #define STBI_FREE(p) M3D_FREE(p) #define STBI_REALLOC_SIZED(p, oldsz, newsz) STBI_REALLOC(p, newsz) _inline static unsigned char _m3dstbi__get8(_m3dstbi__context *s) { if (s->img_buffer < s->img_buffer_end) return *s->img_buffer++; return 0; } _inline static int _m3dstbi__at_eof(_m3dstbi__context *s) { return s->img_buffer >= s->img_buffer_end; } static void _m3dstbi__skip(_m3dstbi__context *s, int n) { if (n < 0) { s->img_buffer = s->img_buffer_end; return; } s->img_buffer += n; } static int _m3dstbi__getn(_m3dstbi__context *s, unsigned char *buffer, int n) { if (s->img_buffer + n <= s->img_buffer_end) { memcpy(buffer, s->img_buffer, n); s->img_buffer += n; return 1; } else return 0; } static int _m3dstbi__get16be(_m3dstbi__context *s) { int z = _m3dstbi__get8(s); return (z << 8) + _m3dstbi__get8(s); } static _m3dstbi__uint32 _m3dstbi__get32be(_m3dstbi__context *s) { _m3dstbi__uint32 z = _m3dstbi__get16be(s); return (z << 16) + _m3dstbi__get16be(s); } #define _m3dstbi__err(x, y) _m3dstbi__errstr(y) static int _m3dstbi__errstr(const char *str) { _m3dstbi__g_failure_reason = str; return 0; } _inline static void *_m3dstbi__malloc(size_t size) { return STBI_MALLOC(size); } static int _m3dstbi__addsizes_valid(int a, int b) { if (b < 0) return 0; return a <= 2147483647 - b; } static int _m3dstbi__mul2sizes_valid(int a, int b) { if (a < 0 || b < 0) return 0; if (b == 0) return 1; return a <= 2147483647 / b; } static int _m3dstbi__mad2sizes_valid(int a, int b, int add) { return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__addsizes_valid(a * b, add); } static int _m3dstbi__mad3sizes_valid(int a, int b, int c, int add) { return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__mul2sizes_valid(a * b, c) && _m3dstbi__addsizes_valid(a * b * c, add); } static void *_m3dstbi__malloc_mad2(int a, int b, int add) { if (!_m3dstbi__mad2sizes_valid(a, b, add)) return NULL; return _m3dstbi__malloc(a * b + add); } static void *_m3dstbi__malloc_mad3(int a, int b, int c, int add) { if (!_m3dstbi__mad3sizes_valid(a, b, c, add)) return NULL; return _m3dstbi__malloc(a * b * c + add); } static unsigned char _m3dstbi__compute_y(int r, int g, int b) { return (unsigned char)(((r * 77) + (g * 150) + (29 * b)) >> 8); } static unsigned char *_m3dstbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i, j; unsigned char *good; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); good = (unsigned char *)_m3dstbi__malloc_mad3(req_comp, x, y, 0); if (good == NULL) { STBI_FREE(data); _m3dstbi__err("outofmem", "Out of memory"); return NULL; } for (j = 0; j < (int)y; ++j) { unsigned char *src = data + j * x * img_n; unsigned char *dest = good + j * x * req_comp; #define STBI__COMBO(a, b) ((a)*8 + (b)) #define STBI__CASE(a, b) \ case STBI__COMBO(a, b): \ for (i = x - 1; i >= 0; --i, src += a, dest += b) switch (STBI__COMBO(img_n, req_comp)) { STBI__CASE(1, 2) { dest[0] = src[0], dest[1] = 255; } break; STBI__CASE(1, 3) { dest[0] = dest[1] = dest[2] = src[0]; } break; STBI__CASE(1, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = 255; } break; STBI__CASE(2, 1) { dest[0] = src[0]; } break; STBI__CASE(2, 3) { dest[0] = dest[1] = dest[2] = src[0]; } break; STBI__CASE(2, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = src[1]; } break; STBI__CASE(3, 4) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2], dest[3] = 255; } break; STBI__CASE(3, 1) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]); } break; STBI__CASE(3, 2) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]), dest[1] = 255; } break; STBI__CASE(4, 1) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]); } break; STBI__CASE(4, 2) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]), dest[1] = src[3]; } break; STBI__CASE(4, 3) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2]; } break; default: STBI_ASSERT(0); } #undef STBI__CASE } STBI_FREE(data); return good; } static _m3dstbi__uint16 _m3dstbi__compute_y_16(int r, int g, int b) { return (_m3dstbi__uint16)(((r * 77) + (g * 150) + (29 * b)) >> 8); } static _m3dstbi__uint16 *_m3dstbi__convert_format16(_m3dstbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i, j; _m3dstbi__uint16 *good; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); good = (_m3dstbi__uint16 *)_m3dstbi__malloc(req_comp * x * y * 2); if (good == NULL) { STBI_FREE(data); _m3dstbi__err("outofmem", "Out of memory"); return NULL; } for (j = 0; j < (int)y; ++j) { _m3dstbi__uint16 *src = data + j * x * img_n; _m3dstbi__uint16 *dest = good + j * x * req_comp; #define STBI__COMBO(a, b) ((a)*8 + (b)) #define STBI__CASE(a, b) \ case STBI__COMBO(a, b): \ for (i = x - 1; i >= 0; --i, src += a, dest += b) switch (STBI__COMBO(img_n, req_comp)) { STBI__CASE(1, 2) { dest[0] = src[0], dest[1] = 0xffff; } break; STBI__CASE(1, 3) { dest[0] = dest[1] = dest[2] = src[0]; } break; STBI__CASE(1, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = 0xffff; } break; STBI__CASE(2, 1) { dest[0] = src[0]; } break; STBI__CASE(2, 3) { dest[0] = dest[1] = dest[2] = src[0]; } break; STBI__CASE(2, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = src[1]; } break; STBI__CASE(3, 4) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2], dest[3] = 0xffff; } break; STBI__CASE(3, 1) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]); } break; STBI__CASE(3, 2) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]), dest[1] = 0xffff; } break; STBI__CASE(4, 1) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]); } break; STBI__CASE(4, 2) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]), dest[1] = src[3]; } break; STBI__CASE(4, 3) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2]; } break; default: STBI_ASSERT(0); } #undef STBI__CASE } STBI_FREE(data); return good; } #define STBI__ZFAST_BITS 9 #define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) typedef struct { _m3dstbi__uint16 fast[1 << STBI__ZFAST_BITS]; _m3dstbi__uint16 firstcode[16]; int maxcode[17]; _m3dstbi__uint16 firstsymbol[16]; unsigned char size[288]; _m3dstbi__uint16 value[288]; } _m3dstbi__zhuffman; _inline static int _m3dstbi__bitreverse16(int n) { n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); return n; } _inline static int _m3dstbi__bit_reverse(int v, int bits) { STBI_ASSERT(bits <= 16); return _m3dstbi__bitreverse16(v) >> (16 - bits); } static int _m3dstbi__zbuild_huffman(_m3dstbi__zhuffman *z, unsigned char *sizelist, int num) { int i, k = 0; int code, next_code[16], sizes[17]; memset(sizes, 0, sizeof(sizes)); memset(z->fast, 0, sizeof(z->fast)); for (i = 0; i < num; ++i) ++sizes[sizelist[i]]; sizes[0] = 0; for (i = 1; i < 16; ++i) if (sizes[i] > (1 << i)) return _m3dstbi__err("bad sizes", "Corrupt PNG"); code = 0; for (i = 1; i < 16; ++i) { next_code[i] = code; z->firstcode[i] = (_m3dstbi__uint16)code; z->firstsymbol[i] = (_m3dstbi__uint16)k; code = (code + sizes[i]); if (sizes[i]) if (code - 1 >= (1 << i)) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); z->maxcode[i] = code << (16 - i); code <<= 1; k += sizes[i]; } z->maxcode[16] = 0x10000; for (i = 0; i < num; ++i) { int s = sizelist[i]; if (s) { int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; _m3dstbi__uint16 fastv = (_m3dstbi__uint16)((s << 9) | i); z->size[c] = (unsigned char)s; z->value[c] = (_m3dstbi__uint16)i; if (s <= STBI__ZFAST_BITS) { int j = _m3dstbi__bit_reverse(next_code[s], s); while (j < (1 << STBI__ZFAST_BITS)) { z->fast[j] = fastv; j += (1 << s); } } ++next_code[s]; } } return 1; } typedef struct { unsigned char *zbuffer, *zbuffer_end; int num_bits; _m3dstbi__uint32 code_buffer; char *zout; char *zout_start; char *zout_end; int z_expandable; _m3dstbi__zhuffman z_length, z_distance; } _m3dstbi__zbuf; _inline static unsigned char _m3dstbi__zget8(_m3dstbi__zbuf *z) { if (z->zbuffer >= z->zbuffer_end) return 0; return *z->zbuffer++; } static void _m3dstbi__fill_bits(_m3dstbi__zbuf *z) { do { STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); z->code_buffer |= (unsigned int)_m3dstbi__zget8(z) << z->num_bits; z->num_bits += 8; } while (z->num_bits <= 24); } _inline static unsigned int _m3dstbi__zreceive(_m3dstbi__zbuf *z, int n) { unsigned int k; if (z->num_bits < n) _m3dstbi__fill_bits(z); k = z->code_buffer & ((1 << n) - 1); z->code_buffer >>= n; z->num_bits -= n; return k; } static int _m3dstbi__zhuffman_decode_slowpath(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) { int b, s, k; k = _m3dstbi__bit_reverse(a->code_buffer, 16); for (s = STBI__ZFAST_BITS + 1;; ++s) if (k < z->maxcode[s]) break; if (s == 16) return -1; b = (k >> (16 - s)) - z->firstcode[s] + z->firstsymbol[s]; STBI_ASSERT(z->size[b] == s); a->code_buffer >>= s; a->num_bits -= s; return z->value[b]; } _inline static int _m3dstbi__zhuffman_decode(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) { int b, s; if (a->num_bits < 16) _m3dstbi__fill_bits(a); b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { s = b >> 9; a->code_buffer >>= s; a->num_bits -= s; return b & 511; } return _m3dstbi__zhuffman_decode_slowpath(a, z); } static int _m3dstbi__zexpand(_m3dstbi__zbuf *z, char *zout, int n) { char *q; int cur, limit, old_limit; z->zout = zout; if (!z->z_expandable) return _m3dstbi__err("output buffer limit", "Corrupt PNG"); cur = (int)(z->zout - z->zout_start); limit = old_limit = (int)(z->zout_end - z->zout_start); while (cur + n > limit) limit *= 2; q = (char *)STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); STBI_NOTUSED(old_limit); if (q == NULL) return _m3dstbi__err("outofmem", "Out of memory"); z->zout_start = q; z->zout = q + cur; z->zout_end = q + limit; return 1; } static int _m3dstbi__zlength_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; static int _m3dstbi__zlength_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; static int _m3dstbi__zdist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; static int _m3dstbi__zdist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static int _m3dstbi__parse_huffman_block(_m3dstbi__zbuf *a) { char *zout = a->zout; for (;;) { int z = _m3dstbi__zhuffman_decode(a, &a->z_length); if (z < 256) { if (z < 0) return _m3dstbi__err("bad huffman code", "Corrupt PNG"); if (zout >= a->zout_end) { if (!_m3dstbi__zexpand(a, zout, 1)) return 0; zout = a->zout; } *zout++ = (char)z; } else { unsigned char *p; int len, dist; if (z == 256) { a->zout = zout; return 1; } z -= 257; len = _m3dstbi__zlength_base[z]; if (_m3dstbi__zlength_extra[z]) len += _m3dstbi__zreceive(a, _m3dstbi__zlength_extra[z]); z = _m3dstbi__zhuffman_decode(a, &a->z_distance); if (z < 0) return _m3dstbi__err("bad huffman code", "Corrupt PNG"); dist = _m3dstbi__zdist_base[z]; if (_m3dstbi__zdist_extra[z]) dist += _m3dstbi__zreceive(a, _m3dstbi__zdist_extra[z]); if (zout - a->zout_start < dist) return _m3dstbi__err("bad dist", "Corrupt PNG"); if (zout + len > a->zout_end) { if (!_m3dstbi__zexpand(a, zout, len)) return 0; zout = a->zout; } p = (unsigned char *)(zout - dist); if (dist == 1) { unsigned char v = *p; if (len) { do *zout++ = v; while (--len); } } else { if (len) { do *zout++ = *p++; while (--len); } } } } } static int _m3dstbi__compute_huffman_codes(_m3dstbi__zbuf *a) { static unsigned char length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; _m3dstbi__zhuffman z_codelength; unsigned char lencodes[286 + 32 + 137]; unsigned char codelength_sizes[19]; int i, n; int hlit = _m3dstbi__zreceive(a, 5) + 257; int hdist = _m3dstbi__zreceive(a, 5) + 1; int hclen = _m3dstbi__zreceive(a, 4) + 4; int ntot = hlit + hdist; memset(codelength_sizes, 0, sizeof(codelength_sizes)); for (i = 0; i < hclen; ++i) { int s = _m3dstbi__zreceive(a, 3); codelength_sizes[length_dezigzag[i]] = (unsigned char)s; } if (!_m3dstbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; n = 0; while (n < ntot) { int c = _m3dstbi__zhuffman_decode(a, &z_codelength); if (c < 0 || c >= 19) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (unsigned char)c; else { unsigned char fill = 0; if (c == 16) { c = _m3dstbi__zreceive(a, 2) + 3; if (n == 0) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); fill = lencodes[n - 1]; } else if (c == 17) c = _m3dstbi__zreceive(a, 3) + 3; else { STBI_ASSERT(c == 18); c = _m3dstbi__zreceive(a, 7) + 11; } if (ntot - n < c) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); memset(lencodes + n, fill, c); n += c; } } if (n != ntot) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); if (!_m3dstbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; if (!_m3dstbi__zbuild_huffman(&a->z_distance, lencodes + hlit, hdist)) return 0; return 1; } _inline static int _m3dstbi__parse_uncompressed_block(_m3dstbi__zbuf *a) { unsigned char header[4]; int len, nlen, k; if (a->num_bits & 7) _m3dstbi__zreceive(a, a->num_bits & 7); k = 0; while (a->num_bits > 0) { header[k++] = (unsigned char)(a->code_buffer & 255); a->code_buffer >>= 8; a->num_bits -= 8; } STBI_ASSERT(a->num_bits == 0); while (k < 4) header[k++] = _m3dstbi__zget8(a); len = header[1] * 256 + header[0]; nlen = header[3] * 256 + header[2]; if (nlen != (len ^ 0xffff)) return _m3dstbi__err("zlib corrupt", "Corrupt PNG"); if (a->zbuffer + len > a->zbuffer_end) return _m3dstbi__err("read past buffer", "Corrupt PNG"); if (a->zout + len > a->zout_end) if (!_m3dstbi__zexpand(a, a->zout, len)) return 0; memcpy(a->zout, a->zbuffer, len); a->zbuffer += len; a->zout += len; return 1; } static int _m3dstbi__parse_zlib_header(_m3dstbi__zbuf *a) { int cmf = _m3dstbi__zget8(a); int cm = cmf & 15; /* int cinfo = cmf >> 4; */ int flg = _m3dstbi__zget8(a); if ((cmf * 256 + flg) % 31 != 0) return _m3dstbi__err("bad zlib header", "Corrupt PNG"); if (flg & 32) return _m3dstbi__err("no preset dict", "Corrupt PNG"); if (cm != 8) return _m3dstbi__err("bad compression", "Corrupt PNG"); return 1; } static unsigned char _m3dstbi__zdefault_length[288], _m3dstbi__zdefault_distance[32]; static void _m3dstbi__init_zdefaults(void) { int i; for (i = 0; i <= 143; ++i) _m3dstbi__zdefault_length[i] = 8; for (; i <= 255; ++i) _m3dstbi__zdefault_length[i] = 9; for (; i <= 279; ++i) _m3dstbi__zdefault_length[i] = 7; for (; i <= 287; ++i) _m3dstbi__zdefault_length[i] = 8; for (i = 0; i <= 31; ++i) _m3dstbi__zdefault_distance[i] = 5; } static int _m3dstbi__parse_zlib(_m3dstbi__zbuf *a, int parse_header) { int final, type; if (parse_header) if (!_m3dstbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; do { final = _m3dstbi__zreceive(a, 1); type = _m3dstbi__zreceive(a, 2); if (type == 0) { if (!_m3dstbi__parse_uncompressed_block(a)) return 0; } else if (type == 3) { return 0; } else { if (type == 1) { if (!_m3dstbi__zbuild_huffman(&a->z_length, _m3dstbi__zdefault_length, 288)) return 0; if (!_m3dstbi__zbuild_huffman(&a->z_distance, _m3dstbi__zdefault_distance, 32)) return 0; } else { if (!_m3dstbi__compute_huffman_codes(a)) return 0; } if (!_m3dstbi__parse_huffman_block(a)) return 0; } } while (!final); return 1; } static int _m3dstbi__do_zlib(_m3dstbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) { a->zout_start = obuf; a->zout = obuf; a->zout_end = obuf + olen; a->z_expandable = exp; _m3dstbi__init_zdefaults(); return _m3dstbi__parse_zlib(a, parse_header); } char *_m3dstbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) { _m3dstbi__zbuf a; char *p = (char *)_m3dstbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (unsigned char *)buffer; a.zbuffer_end = (unsigned char *)buffer + len; if (_m3dstbi__do_zlib(&a, p, initial_size, 1, parse_header)) { if (outlen) *outlen = (int)(a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } typedef struct { _m3dstbi__uint32 length; _m3dstbi__uint32 type; } _m3dstbi__pngchunk; static _m3dstbi__pngchunk _m3dstbi__get_chunk_header(_m3dstbi__context *s) { _m3dstbi__pngchunk c; c.length = _m3dstbi__get32be(s); c.type = _m3dstbi__get32be(s); return c; } _inline static int _m3dstbi__check_png_header(_m3dstbi__context *s) { static unsigned char png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; int i; for (i = 0; i < 8; ++i) if (_m3dstbi__get8(s) != png_sig[i]) return _m3dstbi__err("bad png sig", "Not a PNG"); return 1; } typedef struct { _m3dstbi__context *s; unsigned char *idata, *expanded, *out; int depth; } _m3dstbi__png; enum { STBI__F_none = 0, STBI__F_sub = 1, STBI__F_up = 2, STBI__F_avg = 3, STBI__F_paeth = 4, STBI__F_avg_first, STBI__F_paeth_first }; static unsigned char first_row_filter[5] = { STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_paeth_first }; static int _m3dstbi__paeth(int a, int b, int c) { int p = a + b - c; int pa = abs(p - a); int pb = abs(p - b); int pc = abs(p - c); if (pa <= pb && pa <= pc) return a; if (pb <= pc) return b; return c; } static unsigned char _m3dstbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0, 0, 0, 0x01 }; static int _m3dstbi__create_png_image_raw(_m3dstbi__png *a, unsigned char *raw, _m3dstbi__uint32 raw_len, int out_n, _m3dstbi__uint32 x, _m3dstbi__uint32 y, int depth, int color) { int bytes = (depth == 16 ? 2 : 1); _m3dstbi__context *s = a->s; _m3dstbi__uint32 i, j, stride = x * out_n * bytes; _m3dstbi__uint32 img_len, img_width_bytes; int k; int img_n = s->img_n; int output_bytes = out_n * bytes; int filter_bytes = img_n * bytes; int width = x; STBI_ASSERT(out_n == s->img_n || out_n == s->img_n + 1); a->out = (unsigned char *)_m3dstbi__malloc_mad3(x, y, output_bytes, 0); if (!a->out) return _m3dstbi__err("outofmem", "Out of memory"); if (!_m3dstbi__mad3sizes_valid(img_n, x, depth, 7)) return _m3dstbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); img_len = (img_width_bytes + 1) * y; if (s->img_x == x && s->img_y == y) { if (raw_len != img_len) return _m3dstbi__err("not enough pixels", "Corrupt PNG"); } else { if (raw_len < img_len) return _m3dstbi__err("not enough pixels", "Corrupt PNG"); } for (j = 0; j < y; ++j) { unsigned char *cur = a->out + stride * j; unsigned char *prior = cur - stride; int filter = *raw++; if (filter > 4) return _m3dstbi__err("invalid filter", "Corrupt PNG"); if (depth < 8) { STBI_ASSERT(img_width_bytes <= x); cur += x * out_n - img_width_bytes; filter_bytes = 1; width = img_width_bytes; } prior = cur - stride; if (j == 0) filter = first_row_filter[filter]; for (k = 0; k < filter_bytes; ++k) { switch (filter) { case STBI__F_none: cur[k] = raw[k]; break; case STBI__F_sub: cur[k] = raw[k]; break; case STBI__F_up: cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; case STBI__F_avg: cur[k] = STBI__BYTECAST(raw[k] + (prior[k] >> 1)); break; case STBI__F_paeth: cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(0, prior[k], 0)); break; case STBI__F_avg_first: cur[k] = raw[k]; break; case STBI__F_paeth_first: cur[k] = raw[k]; break; } } if (depth == 8) { if (img_n != out_n) cur[img_n] = 255; raw += img_n; cur += out_n; prior += out_n; } else if (depth == 16) { if (img_n != out_n) { cur[filter_bytes] = 255; cur[filter_bytes + 1] = 255; } raw += filter_bytes; cur += output_bytes; prior += output_bytes; } else { raw += 1; cur += 1; prior += 1; } if (depth < 8 || img_n == out_n) { int nk = (width - 1) * filter_bytes; #define STBI__CASE(f) \ case f: \ for (k = 0; k < nk; ++k) switch (filter) { case STBI__F_none: memcpy(cur, raw, nk); break; STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k - filter_bytes]); } break; STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k - filter_bytes]) >> 1)); } break; STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - filter_bytes], prior[k], prior[k - filter_bytes])); } break; STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k - filter_bytes] >> 1)); } break; STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - filter_bytes], 0, 0)); } break; } #undef STBI__CASE raw += nk; } else { STBI_ASSERT(img_n + 1 == out_n); #define STBI__CASE(f) \ case f: \ for (i = x - 1; i >= 1; --i, cur[filter_bytes] = 255, raw += filter_bytes, cur += output_bytes, prior += output_bytes) \ for (k = 0; k < filter_bytes; ++k) switch (filter) { STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k - output_bytes]); } break; STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k - output_bytes]) >> 1)); } break; STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - output_bytes], prior[k], prior[k - output_bytes])); } break; STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k - output_bytes] >> 1)); } break; STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - output_bytes], 0, 0)); } break; } #undef STBI__CASE if (depth == 16) { cur = a->out + stride * j; for (i = 0; i < x; ++i, cur += output_bytes) { cur[filter_bytes + 1] = 255; } } } } if (depth < 8) { for (j = 0; j < y; ++j) { unsigned char *cur = a->out + stride * j; unsigned char *in = a->out + stride * j + x * out_n - img_width_bytes; unsigned char scale = (color == 0) ? _m3dstbi__depth_scale_table[depth] : 1; if (depth == 4) { for (k = x * img_n; k >= 2; k -= 2, ++in) { *cur++ = scale * ((*in >> 4)); *cur++ = scale * ((*in) & 0x0f); } if (k > 0) *cur++ = scale * ((*in >> 4)); } else if (depth == 2) { for (k = x * img_n; k >= 4; k -= 4, ++in) { *cur++ = scale * ((*in >> 6)); *cur++ = scale * ((*in >> 4) & 0x03); *cur++ = scale * ((*in >> 2) & 0x03); *cur++ = scale * ((*in) & 0x03); } if (k > 0) *cur++ = scale * ((*in >> 6)); if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); } else if (depth == 1) { for (k = x * img_n; k >= 8; k -= 8, ++in) { *cur++ = scale * ((*in >> 7)); *cur++ = scale * ((*in >> 6) & 0x01); *cur++ = scale * ((*in >> 5) & 0x01); *cur++ = scale * ((*in >> 4) & 0x01); *cur++ = scale * ((*in >> 3) & 0x01); *cur++ = scale * ((*in >> 2) & 0x01); *cur++ = scale * ((*in >> 1) & 0x01); *cur++ = scale * ((*in) & 0x01); } if (k > 0) *cur++ = scale * ((*in >> 7)); if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } if (img_n != out_n) { int q; cur = a->out + stride * j; if (img_n == 1) { for (q = x - 1; q >= 0; --q) { cur[q * 2 + 1] = 255; cur[q * 2 + 0] = cur[q]; } } else { STBI_ASSERT(img_n == 3); for (q = x - 1; q >= 0; --q) { cur[q * 4 + 3] = 255; cur[q * 4 + 2] = cur[q * 3 + 2]; cur[q * 4 + 1] = cur[q * 3 + 1]; cur[q * 4 + 0] = cur[q * 3 + 0]; } } } } } else if (depth == 16) { unsigned char *cur = a->out; _m3dstbi__uint16 *cur16 = (_m3dstbi__uint16 *)cur; for (i = 0; i < x * y * out_n; ++i, cur16++, cur += 2) { *cur16 = (cur[0] << 8) | cur[1]; } } return 1; } static int _m3dstbi__create_png_image(_m3dstbi__png *a, unsigned char *image_data, _m3dstbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { int bytes = (depth == 16 ? 2 : 1); int out_bytes = out_n * bytes; unsigned char *final; int p; if (!interlaced) return _m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); final = (unsigned char *)_m3dstbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); for (p = 0; p < 7; ++p) { int xorig[] = { 0, 4, 0, 2, 0, 1, 0 }; int yorig[] = { 0, 0, 4, 0, 2, 0, 1 }; int xspc[] = { 8, 8, 4, 4, 2, 2, 1 }; int yspc[] = { 8, 8, 8, 4, 4, 2, 2 }; int i, j, x, y; x = (a->s->img_x - xorig[p] + xspc[p] - 1) / xspc[p]; y = (a->s->img_y - yorig[p] + yspc[p] - 1) / yspc[p]; if (x && y) { _m3dstbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; if (!_m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { STBI_FREE(final); return 0; } for (j = 0; j < y; ++j) { for (i = 0; i < x; ++i) { int out_y = j * yspc[p] + yorig[p]; int out_x = i * xspc[p] + xorig[p]; memcpy(final + out_y * a->s->img_x * out_bytes + out_x * out_bytes, a->out + (j * x + i) * out_bytes, out_bytes); } } STBI_FREE(a->out); image_data += img_len; image_data_len -= img_len; } } a->out = final; return 1; } static int _m3dstbi__compute_transparency(_m3dstbi__png *z, unsigned char* tc, int out_n) { _m3dstbi__context *s = z->s; _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; unsigned char *p = z->out; STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i = 0; i < pixel_count; ++i) { p[1] = (p[0] == tc[0] ? 0 : 255); p += 2; } } else { for (i = 0; i < pixel_count; ++i) { if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) p[3] = 0; p += 4; } } return 1; } static int _m3dstbi__compute_transparency16(_m3dstbi__png *z, _m3dstbi__uint16 tc[3], int out_n) { _m3dstbi__context *s = z->s; _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; _m3dstbi__uint16 *p = (_m3dstbi__uint16 *)z->out; STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i = 0; i < pixel_count; ++i) { p[1] = (p[0] == tc[0] ? 0 : 65535); p += 2; } } else { for (i = 0; i < pixel_count; ++i) { if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) p[3] = 0; p += 4; } } return 1; } static int _m3dstbi__expand_png_palette(_m3dstbi__png *a, unsigned char *palette, int len, int pal_img_n) { _m3dstbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; unsigned char *p, *temp_out, *orig = a->out; p = (unsigned char *)_m3dstbi__malloc_mad2(pixel_count, pal_img_n, 0); if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); temp_out = p; if (pal_img_n == 3) { for (i = 0; i < pixel_count; ++i) { int n = orig[i] * 4; p[0] = palette[n]; p[1] = palette[n + 1]; p[2] = palette[n + 2]; p += 3; } } else { for (i = 0; i < pixel_count; ++i) { int n = orig[i] * 4; p[0] = palette[n]; p[1] = palette[n + 1]; p[2] = palette[n + 2]; p[3] = palette[n + 3]; p += 4; } } STBI_FREE(a->out); a->out = temp_out; STBI_NOTUSED(len); return 1; } #define STBI__PNG_TYPE(a, b, c, d) (((unsigned)(a) << 24) + ((unsigned)(b) << 16) + ((unsigned)(c) << 8) + (unsigned)(d)) static int _m3dstbi__parse_png_file(_m3dstbi__png *z, int scan, int req_comp) { unsigned char palette[1024], pal_img_n = 0; unsigned char has_trans = 0, tc[3] = {}; _m3dstbi__uint16 tc16[3]; _m3dstbi__uint32 ioff = 0, idata_limit = 0, i, pal_len = 0; int first = 1, k, interlace = 0, color = 0; _m3dstbi__context *s = z->s; z->expanded = NULL; z->idata = NULL; z->out = NULL; if (!_m3dstbi__check_png_header(s)) return 0; if (scan == STBI__SCAN_type) return 1; for (;;) { _m3dstbi__pngchunk c = _m3dstbi__get_chunk_header(s); switch (c.type) { case STBI__PNG_TYPE('C', 'g', 'B', 'I'): _m3dstbi__skip(s, c.length); break; case STBI__PNG_TYPE('I', 'H', 'D', 'R'): { int comp, filter; if (!first) return _m3dstbi__err("multiple IHDR", "Corrupt PNG"); first = 0; if (c.length != 13) return _m3dstbi__err("bad IHDR len", "Corrupt PNG"); s->img_x = _m3dstbi__get32be(s); if (s->img_x > (1 << 24)) return _m3dstbi__err("too large", "Very large image (corrupt?)"); s->img_y = _m3dstbi__get32be(s); if (s->img_y > (1 << 24)) return _m3dstbi__err("too large", "Very large image (corrupt?)"); z->depth = _m3dstbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return _m3dstbi__err("1/2/4/8/16-bit only", "PNG not supported: 1/2/4/8/16-bit only"); color = _m3dstbi__get8(s); if (color > 6) return _m3dstbi__err("bad ctype", "Corrupt PNG"); if (color == 3 && z->depth == 16) return _m3dstbi__err("bad ctype", "Corrupt PNG"); if (color == 3) pal_img_n = 3; else if (color & 1) return _m3dstbi__err("bad ctype", "Corrupt PNG"); comp = _m3dstbi__get8(s); if (comp) return _m3dstbi__err("bad comp method", "Corrupt PNG"); filter = _m3dstbi__get8(s); if (filter) return _m3dstbi__err("bad filter method", "Corrupt PNG"); interlace = _m3dstbi__get8(s); if (interlace > 1) return _m3dstbi__err("bad interlace method", "Corrupt PNG"); if (!s->img_x || !s->img_y) return _m3dstbi__err("0-pixel image", "Corrupt PNG"); if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return _m3dstbi__err("too large", "Image too large to decode"); if (scan == STBI__SCAN_header) return 1; } else { s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return _m3dstbi__err("too large", "Corrupt PNG"); } break; } case STBI__PNG_TYPE('P', 'L', 'T', 'E'): { if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); if (c.length > 256 * 3) return _m3dstbi__err("invalid PLTE", "Corrupt PNG"); pal_len = c.length / 3; if (pal_len * 3 != c.length) return _m3dstbi__err("invalid PLTE", "Corrupt PNG"); for (i = 0; i < pal_len; ++i) { palette[i * 4 + 0] = _m3dstbi__get8(s); palette[i * 4 + 1] = _m3dstbi__get8(s); palette[i * 4 + 2] = _m3dstbi__get8(s); palette[i * 4 + 3] = 255; } break; } case STBI__PNG_TYPE('t', 'R', 'N', 'S'): { if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); if (z->idata) return _m3dstbi__err("tRNS after IDAT", "Corrupt PNG"); if (pal_img_n) { if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } if (pal_len == 0) return _m3dstbi__err("tRNS before PLTE", "Corrupt PNG"); if (c.length > pal_len) return _m3dstbi__err("bad tRNS len", "Corrupt PNG"); pal_img_n = 4; for (i = 0; i < c.length; ++i) palette[i * 4 + 3] = _m3dstbi__get8(s); } else { if (!(s->img_n & 1)) return _m3dstbi__err("tRNS with alpha", "Corrupt PNG"); if (c.length != (_m3dstbi__uint32)s->img_n * 2) return _m3dstbi__err("bad tRNS len", "Corrupt PNG"); has_trans = 1; if (z->depth == 16) { for (k = 0; k < s->img_n; ++k) tc16[k] = (_m3dstbi__uint16)_m3dstbi__get16be(s); } else { for (k = 0; k < s->img_n; ++k) tc[k] = (unsigned char)(_m3dstbi__get16be(s) & 255) * _m3dstbi__depth_scale_table[z->depth]; } } break; } case STBI__PNG_TYPE('I', 'D', 'A', 'T'): { if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return _m3dstbi__err("no PLTE", "Corrupt PNG"); if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { _m3dstbi__uint32 idata_limit_old = idata_limit; unsigned char *p; if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; while (ioff + c.length > idata_limit) idata_limit *= 2; STBI_NOTUSED(idata_limit_old); p = (unsigned char *)STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); z->idata = p; } if (!_m3dstbi__getn(s, z->idata + ioff, c.length)) return _m3dstbi__err("outofdata", "Corrupt PNG"); ioff += c.length; break; } case STBI__PNG_TYPE('I', 'E', 'N', 'D'): { _m3dstbi__uint32 raw_len, bpl; if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); if (scan != STBI__SCAN_load) return 1; if (z->idata == NULL) return _m3dstbi__err("no IDAT", "Corrupt PNG"); bpl = (s->img_x * z->depth + 7) / 8; raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; z->expanded = (unsigned char *)_m3dstbi_zlib_decode_malloc_guesssize_headerflag((char *)z->idata, ioff, raw_len, (int *)&raw_len, 1); if (z->expanded == NULL) return 0; STBI_FREE(z->idata); z->idata = NULL; if ((req_comp == s->img_n + 1 && req_comp != 3 && !pal_img_n) || has_trans) s->img_out_n = s->img_n + 1; else s->img_out_n = s->img_n; if (!_m3dstbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; if (has_trans) { if (z->depth == 16) { if (!_m3dstbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; } else { if (!_m3dstbi__compute_transparency(z, tc, s->img_out_n)) return 0; } } if (pal_img_n) { s->img_n = pal_img_n; s->img_out_n = pal_img_n; if (req_comp >= 3) s->img_out_n = req_comp; if (!_m3dstbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) return 0; } else if (has_trans) { ++s->img_n; } STBI_FREE(z->expanded); z->expanded = NULL; return 1; } default: if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); if ((c.type & (1 << 29)) == 0) { return _m3dstbi__err("invalid_chunk", "PNG not supported: unknown PNG chunk type"); } _m3dstbi__skip(s, c.length); break; } _m3dstbi__get32be(s); } } static void *_m3dstbi__do_png(_m3dstbi__png *p, int *x, int *y, int *n, int req_comp, _m3dstbi__result_info *ri) { void *result = NULL; if (req_comp < 0 || req_comp > 4) { _m3dstbi__err("bad req_comp", "Internal error"); return NULL; } if (_m3dstbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { if (p->depth < 8) ri->bits_per_channel = 8; else ri->bits_per_channel = p->depth; result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { if (ri->bits_per_channel == 8) result = _m3dstbi__convert_format((unsigned char *)result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); else result = _m3dstbi__convert_format16((_m3dstbi__uint16 *)result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); p->s->img_out_n = req_comp; if (result == NULL) return result; } *x = p->s->img_x; *y = p->s->img_y; if (n) *n = p->s->img_n; } STBI_FREE(p->out); p->out = NULL; STBI_FREE(p->expanded); p->expanded = NULL; STBI_FREE(p->idata); p->idata = NULL; return result; } static void *_m3dstbi__png_load(_m3dstbi__context *s, int *x, int *y, int *comp, int req_comp, _m3dstbi__result_info *ri) { _m3dstbi__png p; p.s = s; return _m3dstbi__do_png(&p, x, y, comp, req_comp, ri); } #define stbi__context _m3dstbi__context #define stbi__result_info _m3dstbi__result_info #define stbi__png_load _m3dstbi__png_load #define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag #endif #if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H) /* zlib_compressor from stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h */ typedef unsigned char _m3dstbiw__uc; typedef unsigned short _m3dstbiw__us; typedef uint16_t _m3dstbiw__uint16; typedef int16_t _m3dstbiw__int16; typedef uint32_t _m3dstbiw__uint32; typedef int32_t _m3dstbiw__int32; #define STBIW_MALLOC(s) M3D_MALLOC(s) #define STBIW_REALLOC(p, ns) M3D_REALLOC(p, ns) #define STBIW_REALLOC_SIZED(p, oldsz, newsz) STBIW_REALLOC(p, newsz) #define STBIW_FREE M3D_FREE #define STBIW_MEMMOVE memmove #define STBIW_UCHAR (uint8_t) #define STBIW_ASSERT(x) #define _m3dstbiw___sbraw(a) ((int *)(a)-2) #define _m3dstbiw___sbm(a) _m3dstbiw___sbraw(a)[0] #define _m3dstbiw___sbn(a) _m3dstbiw___sbraw(a)[1] #define _m3dstbiw___sbneedgrow(a, n) ((a) == 0 || _m3dstbiw___sbn(a) + n >= _m3dstbiw___sbm(a)) #define _m3dstbiw___sbmaybegrow(a, n) (_m3dstbiw___sbneedgrow(a, (n)) ? _m3dstbiw___sbgrow(a, n) : 0) #define _m3dstbiw___sbgrow(a, n) _m3dstbiw___sbgrowf((void **)&(a), (n), sizeof(*(a))) #define _m3dstbiw___sbpush(a, v) (_m3dstbiw___sbmaybegrow(a, 1), (a)[_m3dstbiw___sbn(a)++] = (v)) #define _m3dstbiw___sbcount(a) ((a) ? _m3dstbiw___sbn(a) : 0) #define _m3dstbiw___sbfree(a) ((a) ? STBIW_FREE(_m3dstbiw___sbraw(a)), 0 : 0) static void *_m3dstbiw___sbgrowf(void **arr, int increment, int itemsize) { int m = *arr ? 2 * _m3dstbiw___sbm(*arr) + increment : increment + 1; void *p = STBIW_REALLOC_SIZED(*arr ? _m3dstbiw___sbraw(*arr) : 0, *arr ? (_m3dstbiw___sbm(*arr) * itemsize + sizeof(int) * 2) : 0, itemsize * m + sizeof(int) * 2); STBIW_ASSERT(p); if (p) { if (!*arr) ((int *)p)[1] = 0; *arr = (void *)((int *)p + 2); _m3dstbiw___sbm(*arr) = m; } return *arr; } static unsigned char *_m3dstbiw___zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) { while (*bitcount >= 8) { _m3dstbiw___sbpush(data, STBIW_UCHAR(*bitbuffer)); *bitbuffer >>= 8; *bitcount -= 8; } return data; } static int _m3dstbiw___zlib_bitrev(int code, int codebits) { int res = 0; while (codebits--) { res = (res << 1) | (code & 1); code >>= 1; } return res; } static unsigned int _m3dstbiw___zlib_countm(unsigned char *a, unsigned char *b, int limit) { int i; for (i = 0; i < limit && i < 258; ++i) if (a[i] != b[i]) break; return i; } static unsigned int _m3dstbiw___zhash(unsigned char *data) { _m3dstbiw__uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } #define _m3dstbiw___zlib_flush() (out = _m3dstbiw___zlib_flushf(out, &bitbuf, &bitcount)) #define _m3dstbiw___zlib_add(code, codebits) \ (bitbuf |= (code) << bitcount, bitcount += (codebits), _m3dstbiw___zlib_flush()) #define _m3dstbiw___zlib_huffa(b, c) _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(b, c), c) #define _m3dstbiw___zlib_huff1(n) _m3dstbiw___zlib_huffa(0x30 + (n), 8) #define _m3dstbiw___zlib_huff2(n) _m3dstbiw___zlib_huffa(0x190 + (n)-144, 9) #define _m3dstbiw___zlib_huff3(n) _m3dstbiw___zlib_huffa(0 + (n)-256, 7) #define _m3dstbiw___zlib_huff4(n) _m3dstbiw___zlib_huffa(0xc0 + (n)-280, 8) #define _m3dstbiw___zlib_huff(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : (n) <= 255 ? _m3dstbiw___zlib_huff2(n) : (n) <= 279 ? _m3dstbiw___zlib_huff3(n) : _m3dstbiw___zlib_huff4(n)) #define _m3dstbiw___zlib_huffb(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : _m3dstbiw___zlib_huff2(n)) #define _m3dstbiw___ZHASH 16384 unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) { static unsigned short lengthc[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 259 }; static unsigned char lengtheb[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; static unsigned short distc[] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32768 }; static unsigned char disteb[] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; unsigned int bitbuf = 0; int i, j, bitcount = 0; unsigned char *out = NULL; unsigned char ***hash_table = (unsigned char ***)STBIW_MALLOC(_m3dstbiw___ZHASH * sizeof(char **)); if (hash_table == NULL) return NULL; if (quality < 5) quality = 5; _m3dstbiw___sbpush(out, 0x78); _m3dstbiw___sbpush(out, 0x5e); _m3dstbiw___zlib_add(1, 1); _m3dstbiw___zlib_add(1, 2); for (i = 0; i < _m3dstbiw___ZHASH; ++i) hash_table[i] = NULL; i = 0; while (i < data_len - 3) { int h = _m3dstbiw___zhash(data + i) & (_m3dstbiw___ZHASH - 1), best = 3; unsigned char *bestloc = 0; unsigned char **hlist = hash_table[h]; int n = _m3dstbiw___sbcount(hlist); for (j = 0; j < n; ++j) { if (hlist[j] - data > i - 32768) { int d = _m3dstbiw___zlib_countm(hlist[j], data + i, data_len - i); if (d >= best) best = d, bestloc = hlist[j]; } } if (hash_table[h] && _m3dstbiw___sbn(hash_table[h]) == 2 * quality) { STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, sizeof(hash_table[h][0]) * quality); _m3dstbiw___sbn(hash_table[h]) = quality; } _m3dstbiw___sbpush(hash_table[h], data + i); if (bestloc) { h = _m3dstbiw___zhash(data + i + 1) & (_m3dstbiw___ZHASH - 1); hlist = hash_table[h]; n = _m3dstbiw___sbcount(hlist); for (j = 0; j < n; ++j) { if (hlist[j] - data > i - 32767) { int e = _m3dstbiw___zlib_countm(hlist[j], data + i + 1, data_len - i - 1); if (e > best) { bestloc = NULL; break; } } } } if (bestloc) { int d = (int)(data + i - bestloc); STBIW_ASSERT(d <= 32767 && best <= 258); for (j = 0; best > lengthc[j + 1] - 1; ++j) ; _m3dstbiw___zlib_huff(j + 257); if (lengtheb[j]) _m3dstbiw___zlib_add(best - lengthc[j], lengtheb[j]); for (j = 0; d > distc[j + 1] - 1; ++j) ; _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(j, 5), 5); if (disteb[j]) _m3dstbiw___zlib_add(d - distc[j], disteb[j]); i += best; } else { _m3dstbiw___zlib_huffb(data[i]); ++i; } } for (; i < data_len; ++i) _m3dstbiw___zlib_huffb(data[i]); _m3dstbiw___zlib_huff(256); while (bitcount) _m3dstbiw___zlib_add(0, 1); for (i = 0; i < _m3dstbiw___ZHASH; ++i) (void)_m3dstbiw___sbfree(hash_table[i]); STBIW_FREE(hash_table); { unsigned int s1 = 1, s2 = 0; int blocklen = (int)(data_len % 5552); j = 0; while (j < data_len) { for (i = 0; i < blocklen; ++i) s1 += data[j + i], s2 += s1; s1 %= 65521, s2 %= 65521; j += blocklen; blocklen = 5552; } _m3dstbiw___sbpush(out, STBIW_UCHAR(s2 >> 8)); _m3dstbiw___sbpush(out, STBIW_UCHAR(s2)); _m3dstbiw___sbpush(out, STBIW_UCHAR(s1 >> 8)); _m3dstbiw___sbpush(out, STBIW_UCHAR(s1)); } *out_len = _m3dstbiw___sbn(out); STBIW_MEMMOVE(_m3dstbiw___sbraw(out), out, *out_len); return (unsigned char *)_m3dstbiw___sbraw(out); } #define stbi_zlib_compress _m3dstbi_zlib_compress #else unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); #endif #define M3D_CHUNKMAGIC(m, a, b, c, d) ((m)[0] == (a) && (m)[1] == (b) && (m)[2] == (c) && (m)[3] == (d)) #ifdef M3D_ASCII #include /* sprintf and strtod cares about number locale */ #include /* get sprintf */ #endif #ifdef M3D_PROFILING #include #endif #if !defined(M3D_NOIMPORTER) && defined(M3D_ASCII) /* helper functions for the ASCII parser */ static char *_m3d_findarg(char *s) { while (s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') s++; while (s && *s && (*s == ' ' || *s == '\t')) s++; return s; } static char *_m3d_findnl(char *s) { while (s && *s && *s != '\r' && *s != '\n') s++; if (*s == '\r') s++; if (*s == '\n') s++; return s; } static char *_m3d_gethex(char *s, uint32_t *ret) { if (*s == '#') s++; *ret = 0; for (; *s; s++) { if (*s >= '0' && *s <= '9') { *ret <<= 4; *ret += (uint32_t)(*s - '0'); } else if (*s >= 'a' && *s <= 'f') { *ret <<= 4; *ret += (uint32_t)(*s - 'a' + 10); } else if (*s >= 'A' && *s <= 'F') { *ret <<= 4; *ret += (uint32_t)(*s - 'A' + 10); } else break; } return _m3d_findarg(s); } static char *_m3d_getint(char *s, uint32_t *ret) { char *e = s; if (!s || !*s || *s == '\r' || *s == '\n') return s; for (; *e >= '0' && *e <= '9'; e++) ; *ret = atoi(s); return e; } static char *_m3d_getfloat(char *s, M3D_FLOAT *ret) { char *e = s; if (!s || !*s || *s == '\r' || *s == '\n') return s; for (; *e == '-' || *e == '+' || *e == '.' || (*e >= '0' && *e <= '9') || *e == 'e' || *e == 'E'; e++) ; *ret = (M3D_FLOAT)strtod(s, NULL); return _m3d_findarg(e); } #endif #if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_ASCII) || defined(M3D_EXPORTER)) /* helper function to create safe strings */ char *_m3d_safestr(char *in, int morelines) { char *out, *o, *i = in; int l; if (!in || !*in) { out = (char *)M3D_MALLOC(1); if (!out) return NULL; out[0] = 0; } else { for (o = in, l = 0; *o && ((morelines & 1) || (*o != '\r' && *o != '\n')) && l < 256; o++, l++) ; out = o = (char *)M3D_MALLOC(l + 1); if (!out) return NULL; while (*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n')) i++; for (; *i && (morelines || (*i != '\r' && *i != '\n')); i++) { if (*i == '\r') continue; if (*i == '\n') { if (morelines >= 3 && o > out && *(o - 1) == '\n') break; if (i > in && *(i - 1) == '\n') continue; if (morelines & 1) { if (morelines == 1) *o++ = '\r'; *o++ = '\n'; } else break; } else if (*i == ' ' || *i == '\t') { *o++ = morelines ? ' ' : '_'; } else *o++ = !morelines && (*i == '/' || *i == '\\') ? '_' : *i; } for (; o > out && (*(o - 1) == ' ' || *(o - 1) == '\t' || *(o - 1) == '\r' || *(o - 1) == '\n'); o--) ; *o = 0; out = (char *)M3D_REALLOC(out, (uintptr_t)o - (uintptr_t)out + 1); } return out; } #endif #ifndef M3D_NOIMPORTER /* helper function to load and decode/generate a texture */ M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char *fn) { unsigned int i, len = 0; unsigned char *buff = NULL; char *fn2; #ifdef STBI__PNG_TYPE unsigned int w, h; stbi__context s; stbi__result_info ri; #endif /* do we have loaded this texture already? */ for (i = 0; i < model->numtexture; i++) if (!strcmp(fn, model->texture[i].name)) return i; /* see if it's inlined in the model */ if (model->inlined) { for (i = 0; i < model->numinlined; i++) if (!strcmp(fn, model->inlined[i].name)) { buff = model->inlined[i].data; len = model->inlined[i].length; freecb = NULL; break; } } /* try to load from external source */ if (!buff && readfilecb) { i = (unsigned int)strlen(fn); if (i < 5 || fn[i - 4] != '.') { fn2 = (char *)M3D_MALLOC(i + 5); if (!fn2) { model->errcode = M3D_ERR_ALLOC; return M3D_UNDEF; } memcpy(fn2, fn, i); memcpy(fn2 + i, ".png", 5); buff = (*readfilecb)(fn2, &len); M3D_FREE(fn2); } if (!buff) { buff = (*readfilecb)(fn, &len); if (!buff) return M3D_UNDEF; } } /* add to textures array */ i = model->numtexture++; model->texture = (m3dtx_t *)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t)); if (!model->texture) { if (buff && freecb) (*freecb)(buff); model->errcode = M3D_ERR_ALLOC; return M3D_UNDEF; } model->texture[i].name = fn; model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL; if (buff) { if (buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') { #ifdef STBI__PNG_TYPE s.read_from_callbacks = 0; s.img_buffer = s.img_buffer_original = (unsigned char *)buff; s.img_buffer_end = s.img_buffer_original_end = (unsigned char *)buff + len; /* don't use model->texture[i].w directly, it's a uint16_t */ w = h = len = 0; ri.bits_per_channel = 8; model->texture[i].d = (uint8_t *)stbi__png_load(&s, (int *)&w, (int *)&h, (int *)&len, 0, &ri); model->texture[i].w = (uint16_t)w; model->texture[i].h = (uint16_t)h; model->texture[i].f = (uint8_t)len; #endif } else { #ifdef M3D_TX_INTERP if ((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) { M3D_LOG("Unable to generate texture"); M3D_LOG(fn); } #else M3D_LOG("Unimplemented interpreter"); M3D_LOG(fn); #endif } if (freecb) (*freecb)(buff); } if (!model->texture[i].d) model->errcode = M3D_ERR_UNKIMG; return i; } /* helper function to load and generate a procedural surface */ void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused m3dfree_t freecb, _unused char *fn) { #ifdef M3D_PR_INTERP unsigned int i, len = 0; unsigned char *buff = readfilecb ? (*readfilecb)(fn, &len) : NULL; if (!buff && model->inlined) { for (i = 0; i < model->numinlined; i++) if (!strcmp(fn, model->inlined[i].name)) { buff = model->inlined[i].data; len = model->inlined[i].length; freecb = NULL; break; } } if (!buff || !len || (model->errcode = M3D_PR_INTERP(fn, buff, len, model)) != M3D_SUCCESS) { M3D_LOG("Unable to generate procedural surface"); M3D_LOG(fn); model->errcode = M3D_ERR_UNKIMG; } if (freecb && buff) (*freecb)(buff); #else (void)readfilecb; (void)freecb; (void)fn; M3D_LOG("Unimplemented interpreter"); M3D_LOG(fn); model->errcode = M3D_ERR_UNIMPL; #endif } /* helpers to read indices from data stream */ #define M3D_GETSTR(x) \ do { \ offs = 0; \ data = _m3d_getidx(data, model->si_s, &offs); \ x = offs ? ((char *)model->raw + 16 + offs) : NULL; \ } while (0) _inline static unsigned char *_m3d_getidx(unsigned char *data, char type, M3D_INDEX *idx) { switch (type) { case 1: *idx = data[0] > 253 ? (int8_t)data[0] : data[0]; data++; break; case 2: *idx = *((uint16_t *)data) > 65533 ? *((int16_t *)data) : *((uint16_t *)data); data += 2; break; case 4: *idx = *((int32_t *)data); data += 4; break; } return data; } #ifndef M3D_NOANIMATION /* multiply 4 x 4 matrices. Do not use float *r[16] as argument, because some compilers misinterpret that as * 16 pointers each pointing to a float, but we need a single pointer to 16 floats. */ void _m3d_mul(M3D_FLOAT *r, M3D_FLOAT *a, M3D_FLOAT *b) { r[0] = b[0] * a[0] + b[4] * a[1] + b[8] * a[2] + b[12] * a[3]; r[1] = b[1] * a[0] + b[5] * a[1] + b[9] * a[2] + b[13] * a[3]; r[2] = b[2] * a[0] + b[6] * a[1] + b[10] * a[2] + b[14] * a[3]; r[3] = b[3] * a[0] + b[7] * a[1] + b[11] * a[2] + b[15] * a[3]; r[4] = b[0] * a[4] + b[4] * a[5] + b[8] * a[6] + b[12] * a[7]; r[5] = b[1] * a[4] + b[5] * a[5] + b[9] * a[6] + b[13] * a[7]; r[6] = b[2] * a[4] + b[6] * a[5] + b[10] * a[6] + b[14] * a[7]; r[7] = b[3] * a[4] + b[7] * a[5] + b[11] * a[6] + b[15] * a[7]; r[8] = b[0] * a[8] + b[4] * a[9] + b[8] * a[10] + b[12] * a[11]; r[9] = b[1] * a[8] + b[5] * a[9] + b[9] * a[10] + b[13] * a[11]; r[10] = b[2] * a[8] + b[6] * a[9] + b[10] * a[10] + b[14] * a[11]; r[11] = b[3] * a[8] + b[7] * a[9] + b[11] * a[10] + b[15] * a[11]; r[12] = b[0] * a[12] + b[4] * a[13] + b[8] * a[14] + b[12] * a[15]; r[13] = b[1] * a[12] + b[5] * a[13] + b[9] * a[14] + b[13] * a[15]; r[14] = b[2] * a[12] + b[6] * a[13] + b[10] * a[14] + b[14] * a[15]; r[15] = b[3] * a[12] + b[7] * a[13] + b[11] * a[14] + b[15] * a[15]; } /* calculate 4 x 4 matrix inverse */ void _m3d_inv(M3D_FLOAT *m) { M3D_FLOAT r[16]; M3D_FLOAT det = m[0] * m[5] * m[10] * m[15] - m[0] * m[5] * m[11] * m[14] + m[0] * m[6] * m[11] * m[13] - m[0] * m[6] * m[9] * m[15] + m[0] * m[7] * m[9] * m[14] - m[0] * m[7] * m[10] * m[13] - m[1] * m[6] * m[11] * m[12] + m[1] * m[6] * m[8] * m[15] - m[1] * m[7] * m[8] * m[14] + m[1] * m[7] * m[10] * m[12] - m[1] * m[4] * m[10] * m[15] + m[1] * m[4] * m[11] * m[14] + m[2] * m[7] * m[8] * m[13] - m[2] * m[7] * m[9] * m[12] + m[2] * m[4] * m[9] * m[15] - m[2] * m[4] * m[11] * m[13] + m[2] * m[5] * m[11] * m[12] - m[2] * m[5] * m[8] * m[15] - m[3] * m[4] * m[9] * m[14] + m[3] * m[4] * m[10] * m[13] - m[3] * m[5] * m[10] * m[12] + m[3] * m[5] * m[8] * m[14] - m[3] * m[6] * m[8] * m[13] + m[3] * m[6] * m[9] * m[12]; if (det == (M3D_FLOAT)0.0 || det == (M3D_FLOAT)-0.0) det = (M3D_FLOAT)1.0; else det = (M3D_FLOAT)1.0 / det; r[0] = det * (m[5] * (m[10] * m[15] - m[11] * m[14]) + m[6] * (m[11] * m[13] - m[9] * m[15]) + m[7] * (m[9] * m[14] - m[10] * m[13])); r[1] = -det * (m[1] * (m[10] * m[15] - m[11] * m[14]) + m[2] * (m[11] * m[13] - m[9] * m[15]) + m[3] * (m[9] * m[14] - m[10] * m[13])); r[2] = det * (m[1] * (m[6] * m[15] - m[7] * m[14]) + m[2] * (m[7] * m[13] - m[5] * m[15]) + m[3] * (m[5] * m[14] - m[6] * m[13])); r[3] = -det * (m[1] * (m[6] * m[11] - m[7] * m[10]) + m[2] * (m[7] * m[9] - m[5] * m[11]) + m[3] * (m[5] * m[10] - m[6] * m[9])); r[4] = -det * (m[4] * (m[10] * m[15] - m[11] * m[14]) + m[6] * (m[11] * m[12] - m[8] * m[15]) + m[7] * (m[8] * m[14] - m[10] * m[12])); r[5] = det * (m[0] * (m[10] * m[15] - m[11] * m[14]) + m[2] * (m[11] * m[12] - m[8] * m[15]) + m[3] * (m[8] * m[14] - m[10] * m[12])); r[6] = -det * (m[0] * (m[6] * m[15] - m[7] * m[14]) + m[2] * (m[7] * m[12] - m[4] * m[15]) + m[3] * (m[4] * m[14] - m[6] * m[12])); r[7] = det * (m[0] * (m[6] * m[11] - m[7] * m[10]) + m[2] * (m[7] * m[8] - m[4] * m[11]) + m[3] * (m[4] * m[10] - m[6] * m[8])); r[8] = det * (m[4] * (m[9] * m[15] - m[11] * m[13]) + m[5] * (m[11] * m[12] - m[8] * m[15]) + m[7] * (m[8] * m[13] - m[9] * m[12])); r[9] = -det * (m[0] * (m[9] * m[15] - m[11] * m[13]) + m[1] * (m[11] * m[12] - m[8] * m[15]) + m[3] * (m[8] * m[13] - m[9] * m[12])); r[10] = det * (m[0] * (m[5] * m[15] - m[7] * m[13]) + m[1] * (m[7] * m[12] - m[4] * m[15]) + m[3] * (m[4] * m[13] - m[5] * m[12])); r[11] = -det * (m[0] * (m[5] * m[11] - m[7] * m[9]) + m[1] * (m[7] * m[8] - m[4] * m[11]) + m[3] * (m[4] * m[9] - m[5] * m[8])); r[12] = -det * (m[4] * (m[9] * m[14] - m[10] * m[13]) + m[5] * (m[10] * m[12] - m[8] * m[14]) + m[6] * (m[8] * m[13] - m[9] * m[12])); r[13] = det * (m[0] * (m[9] * m[14] - m[10] * m[13]) + m[1] * (m[10] * m[12] - m[8] * m[14]) + m[2] * (m[8] * m[13] - m[9] * m[12])); r[14] = -det * (m[0] * (m[5] * m[14] - m[6] * m[13]) + m[1] * (m[6] * m[12] - m[4] * m[14]) + m[2] * (m[4] * m[13] - m[5] * m[12])); r[15] = det * (m[0] * (m[5] * m[10] - m[6] * m[9]) + m[1] * (m[6] * m[8] - m[4] * m[10]) + m[2] * (m[4] * m[9] - m[5] * m[8])); memcpy(m, &r, sizeof(r)); } /* compose a coloumn major 4 x 4 matrix from vec3 position and vec4 orientation/rotation quaternion */ void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q) { if (q->x == (M3D_FLOAT)0.0 && q->y == (M3D_FLOAT)0.0 && q->z >= (M3D_FLOAT)0.7071065 && q->z <= (M3D_FLOAT)0.7071075 && q->w == (M3D_FLOAT)0.0) { r[1] = r[2] = r[4] = r[6] = r[8] = r[9] = (M3D_FLOAT)0.0; r[0] = r[5] = r[10] = (M3D_FLOAT)-1.0; } else { r[0] = 1 - 2 * (q->y * q->y + q->z * q->z); if (r[0] > -M3D_EPSILON && r[0] < M3D_EPSILON) r[0] = (M3D_FLOAT)0.0; r[1] = 2 * (q->x * q->y - q->z * q->w); if (r[1] > -M3D_EPSILON && r[1] < M3D_EPSILON) r[1] = (M3D_FLOAT)0.0; r[2] = 2 * (q->x * q->z + q->y * q->w); if (r[2] > -M3D_EPSILON && r[2] < M3D_EPSILON) r[2] = (M3D_FLOAT)0.0; r[4] = 2 * (q->x * q->y + q->z * q->w); if (r[4] > -M3D_EPSILON && r[4] < M3D_EPSILON) r[4] = (M3D_FLOAT)0.0; r[5] = 1 - 2 * (q->x * q->x + q->z * q->z); if (r[5] > -M3D_EPSILON && r[5] < M3D_EPSILON) r[5] = (M3D_FLOAT)0.0; r[6] = 2 * (q->y * q->z - q->x * q->w); if (r[6] > -M3D_EPSILON && r[6] < M3D_EPSILON) r[6] = (M3D_FLOAT)0.0; r[8] = 2 * (q->x * q->z - q->y * q->w); if (r[8] > -M3D_EPSILON && r[8] < M3D_EPSILON) r[8] = (M3D_FLOAT)0.0; r[9] = 2 * (q->y * q->z + q->x * q->w); if (r[9] > -M3D_EPSILON && r[9] < M3D_EPSILON) r[9] = (M3D_FLOAT)0.0; r[10] = 1 - 2 * (q->x * q->x + q->y * q->y); if (r[10] > -M3D_EPSILON && r[10] < M3D_EPSILON) r[10] = (M3D_FLOAT)0.0; } r[3] = p->x; r[7] = p->y; r[11] = p->z; r[12] = 0; r[13] = 0; r[14] = 0; r[15] = 1; } #endif #if !defined(M3D_NOANIMATION) || !defined(M3D_NONORMALS) /* portable fast inverse square root calculation. returns 1/sqrt(x) */ static M3D_FLOAT _m3d_rsq(M3D_FLOAT x) { #ifdef M3D_DOUBLE return ((M3D_FLOAT)15.0 / (M3D_FLOAT)8.0) + ((M3D_FLOAT)-5.0 / (M3D_FLOAT)4.0) * x + ((M3D_FLOAT)3.0 / (M3D_FLOAT)8.0) * x * x; #else /* John Carmack's */ float x2 = x * 0.5f; *((uint32_t *)&x) = (0x5f3759df - (*((uint32_t *)&x) >> 1)); return x * (1.5f - (x2 * x * x)); #endif } #endif /** * Function to decode a Model 3D into in-memory format */ m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib) { unsigned char *end, *chunk, *buff, weights[8]; unsigned int i, j, k, l, n, am, len = 0, reclen, offs; char *name, *lang; float f; m3d_t *model; M3D_INDEX mi; M3D_FLOAT w; m3dcd_t *cd; m3dtx_t *tx; m3dh_t *h; m3dm_t *m; m3da_t *a; m3di_t *t; #ifndef M3D_NONORMALS char neednorm = 0; m3dv_t *norm = NULL, *v0, *v1, *v2, va, vb; #endif #ifndef M3D_NOANIMATION M3D_FLOAT r[16]; #endif #if !defined(M3D_NOWEIGHTS) || !defined(M3D_NOANIMATION) m3db_t *b; #endif #ifndef M3D_NOWEIGHTS m3ds_t *sk; #endif #ifdef M3D_ASCII m3ds_t s; M3D_INDEX bi[M3D_BONEMAXLEVEL + 1], level; const char *ol; char *ptr, *pe, *fn; #endif #ifdef M3D_PROFILING struct timeval tv0, tv1, tvd; gettimeofday(&tv0, NULL); #endif if (!data || (!M3D_CHUNKMAGIC(data, '3', 'D', 'M', 'O') #ifdef M3D_ASCII && !M3D_CHUNKMAGIC(data, '3', 'd', 'm', 'o') #endif )) return NULL; model = (m3d_t *)M3D_MALLOC(sizeof(m3d_t)); if (!model) { M3D_LOG("Out of memory"); return NULL; } memset(model, 0, sizeof(m3d_t)); if (mtllib) { model->nummaterial = mtllib->nummaterial; model->material = mtllib->material; model->numtexture = mtllib->numtexture; model->texture = mtllib->texture; model->flags |= M3D_FLG_MTLLIB; } #ifdef M3D_ASCII /* ASCII variant? */ if (M3D_CHUNKMAGIC(data, '3', 'd', 'm', 'o')) { model->errcode = M3D_ERR_BADFILE; model->flags |= M3D_FLG_FREESTR; model->raw = (m3dhdr_t *)data; ptr = (char *)data; ol = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); /* parse header. Don't use sscanf, that's incredibly slow */ ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; pe = _m3d_findnl(ptr); model->scale = (float)strtod(ptr, NULL); ptr = pe; if (model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; model->name = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); if (!*ptr) goto asciiend; model->license = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); if (!*ptr) goto asciiend; model->author = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); if (!*ptr) goto asciiend; if (*ptr != '\r' && *ptr != '\n') model->desc = _m3d_safestr(ptr, 3); while (*ptr) { while (*ptr && *ptr != '\n') ptr++; ptr++; if (*ptr == '\r') ptr++; if (*ptr == '\n') break; } /* the main chunk reader loop */ while (*ptr) { while (*ptr && (*ptr == '\r' || *ptr == '\n')) ptr++; if (!*ptr || (ptr[0] == 'E' && ptr[1] == 'n' && ptr[2] == 'd')) break; /* make sure there's at least one data row */ pe = ptr; ptr = _m3d_findnl(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; /* Preview chunk */ if (!memcmp(pe, "Preview", 7)) { if (readfilecb) { pe = _m3d_safestr(ptr, 0); if (!pe || !*pe) goto asciiend; model->preview.data = (*readfilecb)(pe, &model->preview.length); M3D_FREE(pe); } while (*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); } else /* texture map chunk */ if (!memcmp(pe, "Textmap", 7)) { if (model->tmap) { M3D_LOG("More texture map chunks, should be unique"); goto asciiend; } while (*ptr && *ptr != '\r' && *ptr != '\n') { i = model->numtmap++; model->tmap = (m3dti_t *)M3D_REALLOC(model->tmap, model->numtmap * sizeof(m3dti_t)); if (!model->tmap) goto memerr; ptr = _m3d_getfloat(ptr, &model->tmap[i].u); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; _m3d_getfloat(ptr, &model->tmap[i].v); ptr = _m3d_findnl(ptr); } } else /* vertex chunk */ if (!memcmp(pe, "Vertex", 6)) { if (model->vertex) { M3D_LOG("More vertex chunks, should be unique"); goto asciiend; } while (*ptr && *ptr != '\r' && *ptr != '\n') { i = model->numvertex++; model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); if (!model->vertex) goto memerr; memset(&model->vertex[i], 0, sizeof(m3dv_t)); model->vertex[i].skinid = M3D_UNDEF; model->vertex[i].color = 0; model->vertex[i].w = (M3D_FLOAT)1.0; ptr = _m3d_getfloat(ptr, &model->vertex[i].x); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; ptr = _m3d_getfloat(ptr, &model->vertex[i].y); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; ptr = _m3d_getfloat(ptr, &model->vertex[i].z); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; ptr = _m3d_getfloat(ptr, &model->vertex[i].w); if (!*ptr) goto asciiend; if (*ptr == '#') { ptr = _m3d_gethex(ptr, &model->vertex[i].color); if (!*ptr) goto asciiend; } /* parse skin */ memset(&s, 0, sizeof(m3ds_t)); for (j = 0, w = (M3D_FLOAT)0.0; j < M3D_NUMBONE && *ptr && *ptr != '\r' && *ptr != '\n'; j++) { ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; ptr = _m3d_getint(ptr, &k); s.boneid[j] = (M3D_INDEX)k; if (*ptr == ':') { ptr++; ptr = _m3d_getfloat(ptr, &s.weight[j]); w += s.weight[j]; } else if (!j) s.weight[j] = (M3D_FLOAT)1.0; if (!*ptr) goto asciiend; } if (s.boneid[0] != M3D_UNDEF && s.weight[0] > (M3D_FLOAT)0.0) { if (w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) for (j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++) s.weight[j] /= w; k = M3D_NOTDEFINED; if (model->skin) { for (j = 0; j < model->numskin; j++) if (!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { k = j; break; } } if (k == M3D_NOTDEFINED) { k = model->numskin++; model->skin = (m3ds_t *)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t)); memcpy(&model->skin[k], &s, sizeof(m3ds_t)); } model->vertex[i].skinid = (M3D_INDEX)k; } ptr = _m3d_findnl(ptr); } } else /* Skeleton, bone hierarchy */ if (!memcmp(pe, "Bones", 5)) { if (model->bone) { M3D_LOG("More bones chunks, should be unique"); goto asciiend; } bi[0] = M3D_UNDEF; while (*ptr && *ptr != '\r' && *ptr != '\n') { i = model->numbone++; model->bone = (m3db_t *)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t)); if (!model->bone) goto memerr; for (level = 0; *ptr == '/'; ptr++, level++) ; if (level > M3D_BONEMAXLEVEL || !*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; bi[level + 1] = i; model->bone[i].numweight = 0; model->bone[i].weight = NULL; model->bone[i].parent = bi[level]; ptr = _m3d_getint(ptr, &k); ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; model->bone[i].pos = (M3D_INDEX)k; ptr = _m3d_getint(ptr, &k); ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; model->bone[i].ori = (M3D_INDEX)k; model->vertex[k].skinid = M3D_INDEXMAX; pe = _m3d_safestr(ptr, 0); if (!pe || !*pe) goto asciiend; model->bone[i].name = pe; ptr = _m3d_findnl(ptr); } } else /* material chunk */ if (!memcmp(pe, "Material", 8)) { pe = _m3d_findarg(pe); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; pe = _m3d_safestr(pe, 0); if (!pe || !*pe) goto asciiend; for (i = 0; i < model->nummaterial; i++) if (!strcmp(pe, model->material[i].name)) { M3D_LOG("Multiple definitions for material"); M3D_LOG(pe); M3D_FREE(pe); pe = NULL; while (*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); break; } if (!pe) continue; i = model->nummaterial++; if (model->flags & M3D_FLG_MTLLIB) { m = model->material; model->material = (m3dm_t *)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); if (!model->material) goto memerr; memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); if (model->texture) { tx = model->texture; model->texture = (m3dtx_t *)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); if (!model->texture) goto memerr; memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); } model->flags &= ~M3D_FLG_MTLLIB; } else { model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); if (!model->material) goto memerr; } m = &model->material[i]; m->name = pe; m->numprop = 0; m->prop = NULL; while (*ptr && *ptr != '\r' && *ptr != '\n') { k = n = 256; if (*ptr == 'm' && *(ptr + 1) == 'a' && *(ptr + 2) == 'p' && *(ptr + 3) == '_') { k = m3dpf_map; ptr += 4; } for (j = 0; j < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); j++) if (!memcmp(ptr, m3d_propertytypes[j].key, strlen(m3d_propertytypes[j].key))) { n = m3d_propertytypes[j].id; if (k != m3dpf_map) k = m3d_propertytypes[j].format; break; } if (n != 256 && k != 256) { ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; j = m->numprop++; m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); if (!m->prop) goto memerr; m->prop[j].type = n + (k == m3dpf_map && n < 128 ? 128 : 0); switch (k) { case m3dpf_color: ptr = _m3d_gethex(ptr, &m->prop[j].value.color); break; case m3dpf_uint8: case m3dpf_uint16: case m3dpf_uint32: ptr = _m3d_getint(ptr, &m->prop[j].value.num); break; case m3dpf_float: ptr = _m3d_getfloat(ptr, &m->prop[j].value.fnum); break; case m3dpf_map: pe = _m3d_safestr(ptr, 0); if (!pe || !*pe) goto asciiend; m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe); if (model->errcode == M3D_ERR_ALLOC) { M3D_FREE(pe); goto memerr; } /* this error code only returned if readfilecb was specified */ if (m->prop[j].value.textureid == M3D_UNDEF) { M3D_LOG("Texture not found"); M3D_LOG(pe); m->numprop--; } M3D_FREE(pe); break; } } else { M3D_LOG("Unknown material property in"); M3D_LOG(m->name); model->errcode = M3D_ERR_UNKPROP; } ptr = _m3d_findnl(ptr); } if (!m->numprop) model->nummaterial--; } else /* procedural */ if (!memcmp(pe, "Procedural", 10)) { pe = _m3d_safestr(ptr, 0); _m3d_getpr(model, readfilecb, freecb, pe); M3D_FREE(pe); while (*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); } else /* mesh */ if (!memcmp(pe, "Mesh", 4)) { mi = M3D_UNDEF; while (*ptr && *ptr != '\r' && *ptr != '\n') { if (*ptr == 'u') { ptr = _m3d_findarg(ptr); if (!*ptr) goto asciiend; mi = M3D_UNDEF; if (*ptr != '\r' && *ptr != '\n') { pe = _m3d_safestr(ptr, 0); if (!pe || !*pe) goto asciiend; for (j = 0; j < model->nummaterial; j++) if (!strcmp(pe, model->material[j].name)) { mi = (M3D_INDEX)j; break; } if (mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) { mi = model->nummaterial++; model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); if (!model->material) goto memerr; model->material[mi].name = pe; model->material[mi].numprop = 1; model->material[mi].prop = NULL; } else M3D_FREE(pe); } } else { i = model->numface++; model->face = (m3df_t *)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); if (!model->face) goto memerr; memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ model->face[i].materialid = mi; /* hardcoded triangles. */ for (j = 0; j < 3; j++) { /* vertex */ ptr = _m3d_getint(ptr, &k); model->face[i].vertex[j] = (M3D_INDEX)k; if (!*ptr) goto asciiend; if (*ptr == '/') { ptr++; if (*ptr != '/') { /* texcoord */ ptr = _m3d_getint(ptr, &k); model->face[i].texcoord[j] = (M3D_INDEX)k; if (!*ptr) goto asciiend; } if (*ptr == '/') { ptr++; /* normal */ ptr = _m3d_getint(ptr, &k); model->face[i].normal[j] = (M3D_INDEX)k; if (!*ptr) goto asciiend; } } #ifndef M3D_NONORMALS if (model->face[i].normal[j] == M3D_UNDEF) neednorm = 1; #endif ptr = _m3d_findarg(ptr); } } ptr = _m3d_findnl(ptr); } } else /* mathematical shape */ if (!memcmp(pe, "Shape", 5)) { pe = _m3d_findarg(pe); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; pe = _m3d_safestr(pe, 0); if (!pe || !*pe) goto asciiend; i = model->numshape++; model->shape = (m3dh_t *)M3D_REALLOC(model->shape, model->numshape * sizeof(m3ds_t)); if (!model->shape) goto memerr; h = &model->shape[i]; h->name = pe; h->group = M3D_UNDEF; h->numcmd = 0; h->cmd = NULL; while (*ptr && *ptr != '\r' && *ptr != '\n') { if (!memcmp(ptr, "group", 5)) { ptr = _m3d_findarg(ptr); ptr = _m3d_getint(ptr, &h->group); ptr = _m3d_findnl(ptr); if (h->group != M3D_UNDEF && h->group >= model->numbone) { M3D_LOG("Unknown bone id as shape group in shape"); M3D_LOG(pe); h->group = M3D_UNDEF; model->errcode = M3D_ERR_SHPE; } continue; } for (cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])); k++) { j = (unsigned int)strlen(m3d_commandtypes[k].key); if (!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n')) { cd = &m3d_commandtypes[k]; break; } } if (cd) { j = h->numcmd++; h->cmd = (m3dc_t *)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); if (!h->cmd) goto memerr; h->cmd[j].type = k; h->cmd[j].arg = (uint32_t *)M3D_MALLOC(cd->p * sizeof(uint32_t)); if (!h->cmd[j].arg) goto memerr; memset(h->cmd[j].arg, 0, cd->p * sizeof(uint32_t)); for (k = n = 0, l = cd->p; k < l; k++) { ptr = _m3d_findarg(ptr); if (!*ptr) goto asciiend; if (*ptr == '[') { ptr = _m3d_findarg(ptr + 1); if (!*ptr) goto asciiend; } if (*ptr == ']' || *ptr == '\r' || *ptr == '\n') break; switch (cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: mi = M3D_UNDEF; if (*ptr != '\r' && *ptr != '\n') { pe = _m3d_safestr(ptr, 0); if (!pe || !*pe) goto asciiend; for (n = 0; n < model->nummaterial; n++) if (!strcmp(pe, model->material[n].name)) { mi = (M3D_INDEX)n; break; } if (mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) { mi = model->nummaterial++; model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); if (!model->material) goto memerr; model->material[mi].name = pe; model->material[mi].numprop = 1; model->material[mi].prop = NULL; } else M3D_FREE(pe); } h->cmd[j].arg[k] = mi; break; case m3dcp_vc_t: _m3d_getfloat(ptr, &w); h->cmd[j].arg[k] = *((uint32_t *)&w); break; case m3dcp_va_t: ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); n = k + 1; l += (h->cmd[j].arg[k] - 1) * (cd->p - k - 1); h->cmd[j].arg = (uint32_t *)M3D_REALLOC(h->cmd[j].arg, l * sizeof(uint32_t)); if (!h->cmd[j].arg) goto memerr; memset(&h->cmd[j].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); break; case m3dcp_qi_t: ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); model->vertex[h->cmd[i].arg[k]].skinid = M3D_INDEXMAX; break; default: ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); break; } } } else { M3D_LOG("Unknown shape command in"); M3D_LOG(h->name); model->errcode = M3D_ERR_UNKCMD; } ptr = _m3d_findnl(ptr); } if (!h->numcmd) model->numshape--; } else /* annotation labels */ if (!memcmp(pe, "Labels", 6)) { pe = _m3d_findarg(pe); if (!*pe) goto asciiend; if (*pe == '\r' || *pe == '\n') pe = NULL; else pe = _m3d_safestr(pe, 0); k = 0; fn = NULL; while (*ptr && *ptr != '\r' && *ptr != '\n') { if (*ptr == 'c') { ptr = _m3d_findarg(ptr); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; ptr = _m3d_gethex(ptr, &k); } else if (*ptr == 'l') { ptr = _m3d_findarg(ptr); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; fn = _m3d_safestr(ptr, 2); } else { i = model->numlabel++; model->label = (m3dl_t *)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); if (!model->label) goto memerr; model->label[i].name = pe; model->label[i].lang = fn; model->label[i].color = k; ptr = _m3d_getint(ptr, &j); model->label[i].vertexid = (M3D_INDEX)j; ptr = _m3d_findarg(ptr); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; model->label[i].text = _m3d_safestr(ptr, 2); } ptr = _m3d_findnl(ptr); } } else /* action */ if (!memcmp(pe, "Action", 6)) { pe = _m3d_findarg(pe); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; pe = _m3d_getint(pe, &k); pe = _m3d_findarg(pe); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; pe = _m3d_safestr(pe, 0); if (!pe || !*pe) goto asciiend; i = model->numaction++; model->action = (m3da_t *)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); if (!model->action) goto memerr; a = &model->action[i]; a->name = pe; a->durationmsec = k; /* skip the first frame marker as there's always at least one frame */ a->numframe = 1; a->frame = (m3dfr_t *)M3D_MALLOC(sizeof(m3dfr_t)); if (!a->frame) goto memerr; a->frame[0].msec = 0; a->frame[0].numtransform = 0; a->frame[0].transform = NULL; i = 0; if (*ptr == 'f') ptr = _m3d_findnl(ptr); while (*ptr && *ptr != '\r' && *ptr != '\n') { if (*ptr == 'f') { i = a->numframe++; a->frame = (m3dfr_t *)M3D_REALLOC(a->frame, a->numframe * sizeof(m3dfr_t)); if (!a->frame) goto memerr; ptr = _m3d_findarg(ptr); ptr = _m3d_getint(ptr, &a->frame[i].msec); a->frame[i].numtransform = 0; a->frame[i].transform = NULL; } else { j = a->frame[i].numtransform++; a->frame[i].transform = (m3dtr_t *)M3D_REALLOC(a->frame[i].transform, a->frame[i].numtransform * sizeof(m3dtr_t)); if (!a->frame[i].transform) goto memerr; ptr = _m3d_getint(ptr, &k); ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; a->frame[i].transform[j].boneid = (M3D_INDEX)k; ptr = _m3d_getint(ptr, &k); ptr = _m3d_findarg(ptr); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; a->frame[i].transform[j].pos = (M3D_INDEX)k; ptr = _m3d_getint(ptr, &k); if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; a->frame[i].transform[j].ori = (M3D_INDEX)k; model->vertex[k].skinid = M3D_INDEXMAX; } ptr = _m3d_findnl(ptr); } } else /* inlined assets chunk */ if (!memcmp(pe, "Assets", 6)) { while (*ptr && *ptr != '\r' && *ptr != '\n') { if (readfilecb) { pe = _m3d_safestr(ptr, 2); if (!pe || !*pe) goto asciiend; i = model->numinlined++; model->inlined = (m3di_t *)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); if (!model->inlined) goto memerr; t = &model->inlined[i]; model->inlined[i].data = (*readfilecb)(pe, &model->inlined[i].length); if (model->inlined[i].data) { fn = strrchr(pe, '.'); if (fn && (fn[1] == 'p' || fn[1] == 'P') && (fn[2] == 'n' || fn[2] == 'N') && (fn[3] == 'g' || fn[3] == 'G')) *fn = 0; fn = strrchr(pe, '/'); if (!fn) fn = strrchr(pe, '\\'); if (!fn) fn = pe; else fn++; model->inlined[i].name = _m3d_safestr(fn, 0); } else model->numinlined--; M3D_FREE(pe); } ptr = _m3d_findnl(ptr); } } else /* extra chunks */ if (!memcmp(pe, "Extra", 5)) { pe = _m3d_findarg(pe); if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend; buff = (unsigned char *)_m3d_findnl(ptr); k = ((uint32_t)((uintptr_t)buff - (uintptr_t)ptr) / 3) + 1; i = model->numextra++; model->extra = (m3dchunk_t **)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t *)); if (!model->extra) goto memerr; model->extra[i] = (m3dchunk_t *)M3D_MALLOC(k + sizeof(m3dchunk_t)); if (!model->extra[i]) goto memerr; memcpy(&model->extra[i]->magic, pe, 4); model->extra[i]->length = sizeof(m3dchunk_t); pe = (char *)model->extra[i] + sizeof(m3dchunk_t); while (*ptr && *ptr != '\r' && *ptr != '\n') { ptr = _m3d_gethex(ptr, &k); *pe++ = (uint8_t)k; model->extra[i]->length++; } } else goto asciiend; } model->errcode = M3D_SUCCESS; asciiend: setlocale(LC_NUMERIC, ol); goto postprocess; } #endif /* Binary variant */ if (!M3D_CHUNKMAGIC(data + 8, 'H', 'E', 'A', 'D')) { buff = (unsigned char *)stbi_zlib_decode_malloc_guesssize_headerflag((const char *)data + 8, ((m3dchunk_t *)data)->length - 8, 4096, (int *)&len, 1); if (!buff || !len || !M3D_CHUNKMAGIC(buff, 'H', 'E', 'A', 'D')) { if (buff) M3D_FREE(buff); M3D_FREE(model); return NULL; } buff = (unsigned char *)M3D_REALLOC(buff, len); model->flags |= M3D_FLG_FREERAW; /* mark that we have to free the raw buffer */ data = buff; #ifdef M3D_PROFILING gettimeofday(&tv1, NULL); tvd.tv_sec = tv1.tv_sec - tv0.tv_sec; tvd.tv_usec = tv1.tv_usec - tv0.tv_usec; if (tvd.tv_usec < 0) { tvd.tv_sec--; tvd.tv_usec += 1000000L; } printf(" Deflate model %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec); memcpy(&tv0, &tv1, sizeof(struct timeval)); #endif } else { len = ((m3dhdr_t *)data)->length; data += 8; } model->raw = (m3dhdr_t *)data; end = data + len; /* parse header */ data += sizeof(m3dhdr_t); M3D_LOG(data); model->name = (char *)data; for (; data < end && *data; data++) { }; data++; model->license = (char *)data; for (; data < end && *data; data++) { }; data++; model->author = (char *)data; for (; data < end && *data; data++) { }; data++; model->desc = (char *)data; chunk = (unsigned char *)model->raw + model->raw->length; model->scale = (M3D_FLOAT)model->raw->scale; if (model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; model->vc_s = 1 << ((model->raw->types >> 0) & 3); /* vertex coordinate size */ model->vi_s = 1 << ((model->raw->types >> 2) & 3); /* vertex index size */ model->si_s = 1 << ((model->raw->types >> 4) & 3); /* string offset size */ model->ci_s = 1 << ((model->raw->types >> 6) & 3); /* color index size */ model->ti_s = 1 << ((model->raw->types >> 8) & 3); /* tmap index size */ model->bi_s = 1 << ((model->raw->types >> 10) & 3); /* bone index size */ model->nb_s = 1 << ((model->raw->types >> 12) & 3); /* number of bones per vertex */ model->sk_s = 1 << ((model->raw->types >> 14) & 3); /* skin index size */ model->fc_s = 1 << ((model->raw->types >> 16) & 3); /* frame counter size */ model->hi_s = 1 << ((model->raw->types >> 18) & 3); /* shape index size */ model->fi_s = 1 << ((model->raw->types >> 20) & 3); /* face index size */ if (model->ci_s == 8) model->ci_s = 0; /* optional indices */ if (model->ti_s == 8) model->ti_s = 0; if (model->bi_s == 8) model->bi_s = 0; if (model->sk_s == 8) model->sk_s = 0; if (model->fc_s == 8) model->fc_s = 0; if (model->hi_s == 8) model->hi_s = 0; if (model->fi_s == 8) model->fi_s = 0; /* variable limit checks */ if (sizeof(M3D_FLOAT) == 4 && model->vc_s > 4) { M3D_LOG("Double precision coordinates not supported, truncating to float..."); model->errcode = M3D_ERR_TRUNC; } if (sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 || model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) { M3D_LOG("32 bit indices not supported, unable to load model"); M3D_FREE(model); return NULL; } if (model->vi_s > 4 || model->si_s > 4) { M3D_LOG("Invalid index size, unable to load model"); M3D_FREE(model); return NULL; } if (model->nb_s > M3D_NUMBONE) { M3D_LOG("Model has more bones per vertex than what importer was configured to support"); model->errcode = M3D_ERR_TRUNC; } /* look for inlined assets in advance, material and procedural chunks may need them */ buff = chunk; while (buff < end && !M3D_CHUNKMAGIC(buff, 'O', 'M', 'D', '3')) { data = buff; len = ((m3dchunk_t *)data)->length; buff += len; if (len < sizeof(m3dchunk_t) || buff >= end) { M3D_LOG("Invalid chunk size"); break; } len -= sizeof(m3dchunk_t) + model->si_s; /* inlined assets */ if (M3D_CHUNKMAGIC(data, 'A', 'S', 'E', 'T') && len > 0) { M3D_LOG("Inlined asset"); i = model->numinlined++; model->inlined = (m3di_t *)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); if (!model->inlined) { memerr: M3D_LOG("Out of memory"); model->errcode = M3D_ERR_ALLOC; return model; } data += sizeof(m3dchunk_t); t = &model->inlined[i]; M3D_GETSTR(t->name); M3D_LOG(t->name); t->data = (uint8_t *)data; t->length = len; } } /* parse chunks */ while (chunk < end && !M3D_CHUNKMAGIC(chunk, 'O', 'M', 'D', '3')) { data = chunk; len = ((m3dchunk_t *)chunk)->length; chunk += len; if (len < sizeof(m3dchunk_t) || chunk >= end) { M3D_LOG("Invalid chunk size"); break; } len -= sizeof(m3dchunk_t); /* preview chunk */ if (M3D_CHUNKMAGIC(data, 'P', 'R', 'V', 'W') && len > 0) { model->preview.length = len; model->preview.data = data + sizeof(m3dchunk_t); } else /* color map */ if (M3D_CHUNKMAGIC(data, 'C', 'M', 'A', 'P')) { M3D_LOG("Color map"); if (model->cmap) { M3D_LOG("More color map chunks, should be unique"); model->errcode = M3D_ERR_CMAP; continue; } if (!model->ci_s) { M3D_LOG("Color map chunk, shouldn't be any"); model->errcode = M3D_ERR_CMAP; continue; } model->numcmap = len / sizeof(uint32_t); model->cmap = (uint32_t *)(data + sizeof(m3dchunk_t)); } else /* texture map */ if (M3D_CHUNKMAGIC(data, 'T', 'M', 'A', 'P')) { M3D_LOG("Texture map"); if (model->tmap) { M3D_LOG("More texture map chunks, should be unique"); model->errcode = M3D_ERR_TMAP; continue; } if (!model->ti_s) { M3D_LOG("Texture map chunk, shouldn't be any"); model->errcode = M3D_ERR_TMAP; continue; } reclen = model->vc_s + model->vc_s; model->numtmap = len / reclen; model->tmap = (m3dti_t *)M3D_MALLOC(model->numtmap * sizeof(m3dti_t)); if (!model->tmap) goto memerr; for (i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) { switch (model->vc_s) { case 1: model->tmap[i].u = (M3D_FLOAT)(data[0]) / (M3D_FLOAT)255.0; model->tmap[i].v = (M3D_FLOAT)(data[1]) / (M3D_FLOAT)255.0; break; case 2: model->tmap[i].u = (M3D_FLOAT)(*((int16_t *)(data + 0))) / (M3D_FLOAT)65535.0; model->tmap[i].v = (M3D_FLOAT)(*((int16_t *)(data + 2))) / (M3D_FLOAT)65535.0; break; case 4: model->tmap[i].u = (M3D_FLOAT)(*((float *)(data + 0))); model->tmap[i].v = (M3D_FLOAT)(*((float *)(data + 4))); break; case 8: model->tmap[i].u = (M3D_FLOAT)(*((double *)(data + 0))); model->tmap[i].v = (M3D_FLOAT)(*((double *)(data + 8))); break; } data += reclen; } } else /* vertex list */ if (M3D_CHUNKMAGIC(data, 'V', 'R', 'T', 'S')) { M3D_LOG("Vertex list"); if (model->vertex) { M3D_LOG("More vertex chunks, should be unique"); model->errcode = M3D_ERR_VRTS; continue; } if (model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; reclen = model->ci_s + model->sk_s + 4 * model->vc_s; model->numvertex = len / reclen; model->vertex = (m3dv_t *)M3D_MALLOC(model->numvertex * sizeof(m3dv_t)); if (!model->vertex) goto memerr; memset(model->vertex, 0, model->numvertex * sizeof(m3dv_t)); for (i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) { switch (model->vc_s) { case 1: model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / (M3D_FLOAT)127.0; model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / (M3D_FLOAT)127.0; model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / (M3D_FLOAT)127.0; model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / (M3D_FLOAT)127.0; data += 4; break; case 2: model->vertex[i].x = (M3D_FLOAT)(*((int16_t *)(data + 0))) / (M3D_FLOAT)32767.0; model->vertex[i].y = (M3D_FLOAT)(*((int16_t *)(data + 2))) / (M3D_FLOAT)32767.0; model->vertex[i].z = (M3D_FLOAT)(*((int16_t *)(data + 4))) / (M3D_FLOAT)32767.0; model->vertex[i].w = (M3D_FLOAT)(*((int16_t *)(data + 6))) / (M3D_FLOAT)32767.0; data += 8; break; case 4: model->vertex[i].x = (M3D_FLOAT)(*((float *)(data + 0))); model->vertex[i].y = (M3D_FLOAT)(*((float *)(data + 4))); model->vertex[i].z = (M3D_FLOAT)(*((float *)(data + 8))); model->vertex[i].w = (M3D_FLOAT)(*((float *)(data + 12))); data += 16; break; case 8: model->vertex[i].x = (M3D_FLOAT)(*((double *)(data + 0))); model->vertex[i].y = (M3D_FLOAT)(*((double *)(data + 8))); model->vertex[i].z = (M3D_FLOAT)(*((double *)(data + 16))); model->vertex[i].w = (M3D_FLOAT)(*((double *)(data + 24))); data += 32; break; } switch (model->ci_s) { case 1: model->vertex[i].color = model->cmap ? model->cmap[data[0]] : 0; data++; break; case 2: model->vertex[i].color = model->cmap ? model->cmap[*((uint16_t *)data)] : 0; data += 2; break; case 4: model->vertex[i].color = *((uint32_t *)data); data += 4; break; /* case 8: break; */ } model->vertex[i].skinid = M3D_UNDEF; data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid); } } else /* skeleton: bone hierarchy and skin */ if (M3D_CHUNKMAGIC(data, 'B', 'O', 'N', 'E')) { M3D_LOG("Skeleton"); if (model->bone) { M3D_LOG("More bone chunks, should be unique"); model->errcode = M3D_ERR_BONE; continue; } if (!model->bi_s) { M3D_LOG("Bone chunk, shouldn't be any"); model->errcode = M3D_ERR_BONE; continue; } if (!model->vertex) { M3D_LOG("No vertex chunk before bones"); model->errcode = M3D_ERR_VRTS; break; } data += sizeof(m3dchunk_t); model->numbone = 0; data = _m3d_getidx(data, model->bi_s, &model->numbone); if (model->numbone) { model->bone = (m3db_t *)M3D_MALLOC(model->numbone * sizeof(m3db_t)); if (!model->bone) goto memerr; } model->numskin = 0; data = _m3d_getidx(data, model->sk_s, &model->numskin); /* read bone hierarchy */ for (i = 0; i < model->numbone; i++) { data = _m3d_getidx(data, model->bi_s, &model->bone[i].parent); M3D_GETSTR(model->bone[i].name); data = _m3d_getidx(data, model->vi_s, &model->bone[i].pos); data = _m3d_getidx(data, model->vi_s, &model->bone[i].ori); model->bone[i].numweight = 0; model->bone[i].weight = NULL; } /* read skin definitions */ if (model->numskin) { model->skin = (m3ds_t *)M3D_MALLOC(model->numskin * sizeof(m3ds_t)); if (!model->skin) goto memerr; for (i = 0; data < chunk && i < model->numskin; i++) { for (j = 0; j < M3D_NUMBONE; j++) { model->skin[i].boneid[j] = M3D_UNDEF; model->skin[i].weight[j] = (M3D_FLOAT)0.0; } memset(&weights, 0, sizeof(weights)); if (model->nb_s == 1) weights[0] = 255; else { memcpy(&weights, data, model->nb_s); data += model->nb_s; } for (j = 0, w = (M3D_FLOAT)0.0; j < (unsigned int)model->nb_s; j++) { if (weights[j]) { if (j >= M3D_NUMBONE) data += model->bi_s; else { model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / (M3D_FLOAT)255.0; w += model->skin[i].weight[j]; data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]); } } } /* this can occur if model has more bones than what the importer is configured to handle */ if (w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) { for (j = 0; j < M3D_NUMBONE; j++) model->skin[i].weight[j] /= w; } } } } else /* material */ if (M3D_CHUNKMAGIC(data, 'M', 'T', 'R', 'L')) { data += sizeof(m3dchunk_t); M3D_GETSTR(name); M3D_LOG("Material"); M3D_LOG(name); if (model->ci_s < 4 && !model->numcmap) model->errcode = M3D_ERR_CMAP; for (i = 0; i < model->nummaterial; i++) if (!strcmp(name, model->material[i].name)) { model->errcode = M3D_ERR_MTRL; M3D_LOG("Multiple definitions for material"); M3D_LOG(name); name = NULL; break; } if (name) { i = model->nummaterial++; if (model->flags & M3D_FLG_MTLLIB) { m = model->material; model->material = (m3dm_t *)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); if (!model->material) goto memerr; memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); if (model->texture) { tx = model->texture; model->texture = (m3dtx_t *)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); if (!model->texture) goto memerr; memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); } model->flags &= ~M3D_FLG_MTLLIB; } else { model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); if (!model->material) goto memerr; } m = &model->material[i]; m->numprop = 0; m->name = name; m->prop = (m3dp_t *)M3D_MALLOC((len / 2) * sizeof(m3dp_t)); if (!m->prop) goto memerr; while (data < chunk) { i = m->numprop++; m->prop[i].type = *data++; m->prop[i].value.num = 0; if (m->prop[i].type >= 128) k = m3dpf_map; else { for (k = 256, j = 0; j < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); j++) if (m->prop[i].type == m3d_propertytypes[j].id) { k = m3d_propertytypes[j].format; break; } } switch (k) { case m3dpf_color: switch (model->ci_s) { case 1: m->prop[i].value.color = model->cmap ? model->cmap[data[0]] : 0; data++; break; case 2: m->prop[i].value.color = model->cmap ? model->cmap[*((uint16_t *)data)] : 0; data += 2; break; case 4: m->prop[i].value.color = *((uint32_t *)data); data += 4; break; } break; case m3dpf_uint8: m->prop[i].value.num = *data++; break; case m3dpf_uint16: m->prop[i].value.num = *((uint16_t *)data); data += 2; break; case m3dpf_uint32: m->prop[i].value.num = *((uint32_t *)data); data += 4; break; case m3dpf_float: m->prop[i].value.fnum = *((float *)data); data += 4; break; case m3dpf_map: M3D_GETSTR(name); m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name); if (model->errcode == M3D_ERR_ALLOC) goto memerr; /* this error code only returned if readfilecb was specified */ if (m->prop[i].value.textureid == M3D_UNDEF) { M3D_LOG("Texture not found"); M3D_LOG(m->name); m->numprop--; } break; default: M3D_LOG("Unknown material property in"); M3D_LOG(m->name); model->errcode = M3D_ERR_UNKPROP; data = chunk; break; } } m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); if (!m->prop) goto memerr; } } else /* face */ if (M3D_CHUNKMAGIC(data, 'P', 'R', 'O', 'C')) { /* procedural surface */ M3D_GETSTR(name); M3D_LOG("Procedural surface"); M3D_LOG(name); _m3d_getpr(model, readfilecb, freecb, name); } else if (M3D_CHUNKMAGIC(data, 'M', 'E', 'S', 'H')) { M3D_LOG("Mesh data"); /* mesh */ data += sizeof(m3dchunk_t); mi = M3D_UNDEF; am = model->numface; while (data < chunk) { k = *data++; n = k >> 4; k &= 15; if (!n) { /* use material */ mi = M3D_UNDEF; M3D_GETSTR(name); if (name) { for (j = 0; j < model->nummaterial; j++) if (!strcmp(name, model->material[j].name)) { mi = (M3D_INDEX)j; break; } if (mi == M3D_UNDEF) model->errcode = M3D_ERR_MTRL; } continue; } if (n != 3) { M3D_LOG("Only triangle mesh supported for now"); model->errcode = M3D_ERR_UNKMESH; return model; } i = model->numface++; if (model->numface > am) { am = model->numface + 4095; model->face = (m3df_t *)M3D_REALLOC(model->face, am * sizeof(m3df_t)); if (!model->face) goto memerr; } memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ model->face[i].materialid = mi; for (j = 0; j < n; j++) { /* vertex */ data = _m3d_getidx(data, model->vi_s, &model->face[i].vertex[j]); /* texcoord */ if (k & 1) data = _m3d_getidx(data, model->ti_s, &model->face[i].texcoord[j]); /* normal */ if (k & 2) data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]); #ifndef M3D_NONORMALS if (model->face[i].normal[j] == M3D_UNDEF) neednorm = 1; #endif } } model->face = (m3df_t *)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); } else if (M3D_CHUNKMAGIC(data, 'S', 'H', 'P', 'E')) { /* mathematical shape */ data += sizeof(m3dchunk_t); M3D_GETSTR(name); M3D_LOG("Mathematical Shape"); M3D_LOG(name); i = model->numshape++; model->shape = (m3dh_t *)M3D_REALLOC(model->shape, model->numshape * sizeof(m3dh_t)); if (!model->shape) goto memerr; h = &model->shape[i]; h->numcmd = 0; h->cmd = NULL; h->name = name; h->group = M3D_UNDEF; data = _m3d_getidx(data, model->bi_s, &h->group); if (h->group != M3D_UNDEF && h->group >= model->numbone) { M3D_LOG("Unknown bone id as shape group in shape"); M3D_LOG(name); h->group = M3D_UNDEF; model->errcode = M3D_ERR_SHPE; } while (data < chunk) { i = h->numcmd++; h->cmd = (m3dc_t *)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); if (!h->cmd) goto memerr; h->cmd[i].type = *data++; if (h->cmd[i].type & 0x80) { h->cmd[i].type &= 0x7F; h->cmd[i].type |= (*data++ << 7); } if (h->cmd[i].type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0]))) { M3D_LOG("Unknown shape command in"); M3D_LOG(h->name); model->errcode = M3D_ERR_UNKCMD; break; } cd = &m3d_commandtypes[h->cmd[i].type]; h->cmd[i].arg = (uint32_t *)M3D_MALLOC(cd->p * sizeof(uint32_t)); if (!h->cmd[i].arg) goto memerr; memset(h->cmd[i].arg, 0, cd->p * sizeof(uint32_t)); for (k = n = 0, l = cd->p; k < l; k++) switch (cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: h->cmd[i].arg[k] = M3D_NOTDEFINED; M3D_GETSTR(name); if (name) { for (n = 0; n < model->nummaterial; n++) if (!strcmp(name, model->material[n].name)) { h->cmd[i].arg[k] = n; break; } if (h->cmd[i].arg[k] == M3D_NOTDEFINED) model->errcode = M3D_ERR_MTRL; } break; case m3dcp_vc_t: f = 0.0f; switch (model->vc_s) { case 1: f = (float)((int8_t)data[0]) / 127; break; case 2: f = (float)(*((int16_t *)(data + 0))) / 32767; break; case 4: f = (float)(*((float *)(data + 0))); break; case 8: f = (float)(*((double *)(data + 0))); break; } h->cmd[i].arg[k] = *((uint32_t *)&f); data += model->vc_s; break; case m3dcp_hi_t: data = _m3d_getidx(data, model->hi_s, &h->cmd[i].arg[k]); break; case m3dcp_fi_t: data = _m3d_getidx(data, model->fi_s, &h->cmd[i].arg[k]); break; case m3dcp_ti_t: data = _m3d_getidx(data, model->ti_s, &h->cmd[i].arg[k]); break; case m3dcp_qi_t: case m3dcp_vi_t: data = _m3d_getidx(data, model->vi_s, &h->cmd[i].arg[k]); break; case m3dcp_i1_t: data = _m3d_getidx(data, 1, &h->cmd[i].arg[k]); break; case m3dcp_i2_t: data = _m3d_getidx(data, 2, &h->cmd[i].arg[k]); break; case m3dcp_i4_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); break; case m3dcp_va_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); n = k + 1; l += (h->cmd[i].arg[k] - 1) * (cd->p - k - 1); h->cmd[i].arg = (uint32_t *)M3D_REALLOC(h->cmd[i].arg, l * sizeof(uint32_t)); if (!h->cmd[i].arg) goto memerr; memset(&h->cmd[i].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); break; } } } else /* annotation label list */ if (M3D_CHUNKMAGIC(data, 'L', 'B', 'L', 'S')) { data += sizeof(m3dchunk_t); M3D_GETSTR(name); M3D_GETSTR(lang); M3D_LOG("Label list"); if (name) { M3D_LOG(name); } if (lang) { M3D_LOG(lang); } if (model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; k = 0; switch (model->ci_s) { case 1: k = model->cmap ? model->cmap[data[0]] : 0; data++; break; case 2: k = model->cmap ? model->cmap[*((uint16_t *)data)] : 0; data += 2; break; case 4: k = *((uint32_t *)data); data += 4; break; /* case 8: break; */ } reclen = model->vi_s + model->si_s; i = model->numlabel; model->numlabel += len / reclen; model->label = (m3dl_t *)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); if (!model->label) goto memerr; memset(&model->label[i], 0, (model->numlabel - i) * sizeof(m3dl_t)); for (; data < chunk && i < model->numlabel; i++) { model->label[i].name = name; model->label[i].lang = lang; model->label[i].color = k; data = _m3d_getidx(data, model->vi_s, &model->label[i].vertexid); M3D_GETSTR(model->label[i].text); } } else /* action */ if (M3D_CHUNKMAGIC(data, 'A', 'C', 'T', 'N')) { M3D_LOG("Action"); i = model->numaction++; model->action = (m3da_t *)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); if (!model->action) goto memerr; a = &model->action[i]; data += sizeof(m3dchunk_t); M3D_GETSTR(a->name); M3D_LOG(a->name); a->numframe = *((uint16_t *)data); data += 2; if (a->numframe < 1) { model->numaction--; } else { a->durationmsec = *((uint32_t *)data); data += 4; a->frame = (m3dfr_t *)M3D_MALLOC(a->numframe * sizeof(m3dfr_t)); if (!a->frame) goto memerr; for (i = 0; data < chunk && i < a->numframe; i++) { a->frame[i].msec = *((uint32_t *)data); data += 4; a->frame[i].numtransform = 0; a->frame[i].transform = NULL; data = _m3d_getidx(data, model->fc_s, &a->frame[i].numtransform); if (a->frame[i].numtransform > 0) { a->frame[i].transform = (m3dtr_t *)M3D_MALLOC(a->frame[i].numtransform * sizeof(m3dtr_t)); for (j = 0; j < a->frame[i].numtransform; j++) { data = _m3d_getidx(data, model->bi_s, &a->frame[i].transform[j].boneid); data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].pos); data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].ori); } } } } } else { i = model->numextra++; model->extra = (m3dchunk_t **)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t *)); if (!model->extra) goto memerr; model->extra[i] = (m3dchunk_t *)data; } } /* calculate normals, normalize skin weights, create bone/vertex cross-references and calculate transform matrices */ #ifdef M3D_ASCII postprocess: #endif if (model) { M3D_LOG("Post-process"); #ifdef M3D_PROFILING gettimeofday(&tv1, NULL); tvd.tv_sec = tv1.tv_sec - tv0.tv_sec; tvd.tv_usec = tv1.tv_usec - tv0.tv_usec; if (tvd.tv_usec < 0) { tvd.tv_sec--; tvd.tv_usec += 1000000L; } printf(" Parsing chunks %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec); #endif #ifndef M3D_NONORMALS if (model->numface && model->face && neednorm) { /* if they are missing, calculate triangle normals into a temporary buffer */ norm = (m3dv_t *)M3D_MALLOC(model->numface * sizeof(m3dv_t)); if (!norm) goto memerr; for (i = 0, n = model->numvertex; i < model->numface; i++) if (model->face[i].normal[0] == M3D_UNDEF) { v0 = &model->vertex[model->face[i].vertex[0]]; v1 = &model->vertex[model->face[i].vertex[1]]; v2 = &model->vertex[model->face[i].vertex[2]]; va.x = v1->x - v0->x; va.y = v1->y - v0->y; va.z = v1->z - v0->z; vb.x = v2->x - v0->x; vb.y = v2->y - v0->y; vb.z = v2->z - v0->z; v0 = &norm[i]; v0->x = (va.y * vb.z) - (va.z * vb.y); v0->y = (va.z * vb.x) - (va.x * vb.z); v0->z = (va.x * vb.y) - (va.y * vb.x); w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); v0->x *= w; v0->y *= w; v0->z *= w; model->face[i].normal[0] = model->face[i].vertex[0] + n; model->face[i].normal[1] = model->face[i].vertex[1] + n; model->face[i].normal[2] = model->face[i].vertex[2] + n; } /* this is the fast way, we don't care if a normal is repeated in model->vertex */ M3D_LOG("Generating normals"); model->flags |= M3D_FLG_GENNORM; model->numvertex <<= 1; model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); if (!model->vertex) goto memerr; memset(&model->vertex[n], 0, n * sizeof(m3dv_t)); for (i = 0; i < model->numface; i++) for (j = 0; j < 3; j++) { v0 = &model->vertex[model->face[i].vertex[j] + n]; v0->x += norm[i].x; v0->y += norm[i].y; v0->z += norm[i].z; } /* for each vertex, take the average of the temporary normals and use that */ for (i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) { w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); v0->x *= w; v0->y *= w; v0->z *= w; v0->skinid = M3D_UNDEF; } M3D_FREE(norm); } #endif if (model->numbone && model->bone && model->numskin && model->skin && model->numvertex && model->vertex) { #ifndef M3D_NOWEIGHTS M3D_LOG("Generating weight cross-reference"); for (i = 0; i < model->numvertex; i++) { if (model->vertex[i].skinid < model->numskin) { sk = &model->skin[model->vertex[i].skinid]; w = (M3D_FLOAT)0.0; for (j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) w += sk->weight[j]; for (j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) { sk->weight[j] /= w; b = &model->bone[sk->boneid[j]]; k = b->numweight++; b->weight = (m3dw_t *)M3D_REALLOC(b->weight, b->numweight * sizeof(m3da_t)); if (!b->weight) goto memerr; b->weight[k].vertexid = i; b->weight[k].weight = sk->weight[j]; } } } #endif #ifndef M3D_NOANIMATION M3D_LOG("Calculating bone transformation matrices"); for (i = 0; i < model->numbone; i++) { b = &model->bone[i]; if (model->bone[i].parent == M3D_UNDEF) { _m3d_mat((M3D_FLOAT *)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]); } else { _m3d_mat((M3D_FLOAT *)&r, &model->vertex[b->pos], &model->vertex[b->ori]); _m3d_mul((M3D_FLOAT *)&b->mat4, (M3D_FLOAT *)&model->bone[b->parent].mat4, (M3D_FLOAT *)&r); } } for (i = 0; i < model->numbone; i++) _m3d_inv((M3D_FLOAT *)&model->bone[i].mat4); #endif } #ifdef M3D_PROFILING gettimeofday(&tv0, NULL); tvd.tv_sec = tv0.tv_sec - tv1.tv_sec; tvd.tv_usec = tv0.tv_usec - tv1.tv_usec; if (tvd.tv_usec < 0) { tvd.tv_sec--; tvd.tv_usec += 1000000L; } printf(" Post-process %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec); #endif } return model; } /** * Calculates skeletons for animation frames, returns a working copy (should be freed after use) */ m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton) { unsigned int i; M3D_INDEX s = frameid; m3dfr_t *fr; if (!model || !model->numbone || !model->bone || (actionid != M3D_UNDEF && (!model->action || actionid >= model->numaction || frameid >= model->action[actionid].numframe))) { model->errcode = M3D_ERR_UNKFRAME; return skeleton; } model->errcode = M3D_SUCCESS; if (!skeleton) { skeleton = (m3dtr_t *)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); if (!skeleton) { model->errcode = M3D_ERR_ALLOC; return NULL; } goto gen; } if (actionid == M3D_UNDEF || !frameid) { gen: s = 0; for (i = 0; i < model->numbone; i++) { skeleton[i].boneid = i; skeleton[i].pos = model->bone[i].pos; skeleton[i].ori = model->bone[i].ori; } } if (actionid < model->numaction && (frameid || !model->action[actionid].frame[0].msec)) { for (; s <= frameid; s++) { fr = &model->action[actionid].frame[s]; for (i = 0; i < fr->numtransform; i++) { skeleton[fr->transform[i].boneid].pos = fr->transform[i].pos; skeleton[fr->transform[i].boneid].ori = fr->transform[i].ori; } } } return skeleton; } #ifndef M3D_NOANIMATION /** * Returns interpolated animation-pose, a working copy (should be freed after use) */ m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec) { unsigned int i, j, l; M3D_FLOAT r[16], t, c, d, s; m3db_t *ret; m3dv_t *v, *p, *f; m3dtr_t *tmp; m3dfr_t *fr; if (!model || !model->numbone || !model->bone) { model->errcode = M3D_ERR_UNKFRAME; return NULL; } ret = (m3db_t *)M3D_MALLOC(model->numbone * sizeof(m3db_t)); if (!ret) { model->errcode = M3D_ERR_ALLOC; return NULL; } memcpy(ret, model->bone, model->numbone * sizeof(m3db_t)); for (i = 0; i < model->numbone; i++) _m3d_inv((M3D_FLOAT *)&ret[i].mat4); if (!model->action || actionid >= model->numaction) { model->errcode = M3D_ERR_UNKFRAME; return ret; } msec %= model->action[actionid].durationmsec; model->errcode = M3D_SUCCESS; fr = &model->action[actionid].frame[0]; for (j = l = 0; j < model->action[actionid].numframe && model->action[actionid].frame[j].msec <= msec; j++) { fr = &model->action[actionid].frame[j]; l = fr->msec; for (i = 0; i < fr->numtransform; i++) { ret[fr->transform[i].boneid].pos = fr->transform[i].pos; ret[fr->transform[i].boneid].ori = fr->transform[i].ori; } } if (l != msec) { model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, (model->numvertex + 2 * model->numbone) * sizeof(m3dv_t)); if (!model->vertex) { free(ret); model->errcode = M3D_ERR_ALLOC; return NULL; } tmp = (m3dtr_t *)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); if (tmp) { for (i = 0; i < model->numbone; i++) { tmp[i].pos = ret[i].pos; tmp[i].ori = ret[i].ori; } fr = &model->action[actionid].frame[j % model->action[actionid].numframe]; t = l >= fr->msec ? (M3D_FLOAT)1.0 : (M3D_FLOAT)(msec - l) / (M3D_FLOAT)(fr->msec - l); for (i = 0; i < fr->numtransform; i++) { tmp[fr->transform[i].boneid].pos = fr->transform[i].pos; tmp[fr->transform[i].boneid].ori = fr->transform[i].ori; } for (i = 0, j = model->numvertex; i < model->numbone; i++) { /* interpolation of position */ if (ret[i].pos != tmp[i].pos) { p = &model->vertex[ret[i].pos]; f = &model->vertex[tmp[i].pos]; v = &model->vertex[j]; v->x = p->x + t * (f->x - p->x); v->y = p->y + t * (f->y - p->y); v->z = p->z + t * (f->z - p->z); ret[i].pos = j++; } /* interpolation of orientation */ if (ret[i].ori != tmp[i].ori) { p = &model->vertex[ret[i].ori]; f = &model->vertex[tmp[i].ori]; v = &model->vertex[j]; d = p->w * f->w + p->x * f->x + p->y * f->y + p->z * f->z; if (d < 0) { d = -d; s = (M3D_FLOAT)-1.0; } else s = (M3D_FLOAT)1.0; #if 0 /* don't use SLERP, requires two more variables, libm linkage and it is slow (but nice) */ a = (M3D_FLOAT)1.0 - t; b = t; if(d < (M3D_FLOAT)0.999999) { c = acosf(d); b = 1 / sinf(c); a = sinf(a * c) * b; b *= sinf(t * c) * s; } v->x = p->x * a + f->x * b; v->y = p->y * a + f->y * b; v->z = p->z * a + f->z * b; v->w = p->w * a + f->w * b; #else /* approximated NLERP, original approximation by Arseny Kapoulkine, heavily optimized by me */ c = t - (M3D_FLOAT)0.5; t += t * c * (t - (M3D_FLOAT)1.0) * (((M3D_FLOAT)1.0904 + d * ((M3D_FLOAT)-3.2452 + d * ((M3D_FLOAT)3.55645 - d * (M3D_FLOAT)1.43519))) * c * c + ((M3D_FLOAT)0.848013 + d * ((M3D_FLOAT)-1.06021 + d * (M3D_FLOAT)0.215638))); v->x = p->x + t * (s * f->x - p->x); v->y = p->y + t * (s * f->y - p->y); v->z = p->z + t * (s * f->z - p->z); v->w = p->w + t * (s * f->w - p->w); d = _m3d_rsq(v->w * v->w + v->x * v->x + v->y * v->y + v->z * v->z); v->x *= d; v->y *= d; v->z *= d; v->w *= d; #endif ret[i].ori = j++; } } M3D_FREE(tmp); } } for (i = 0; i < model->numbone; i++) { if (ret[i].parent == M3D_UNDEF) { _m3d_mat((M3D_FLOAT *)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); } else { _m3d_mat((M3D_FLOAT *)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); _m3d_mul((M3D_FLOAT *)&ret[i].mat4, (M3D_FLOAT *)&ret[ret[i].parent].mat4, (M3D_FLOAT *)&r); } } return ret; } #endif /* M3D_NOANIMATION */ #endif /* M3D_IMPLEMENTATION */ #if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)) /** * Free the in-memory model */ void m3d_free(m3d_t *model) { unsigned int i, j; if (!model) return; #ifdef M3D_ASCII /* if model imported from ASCII, we have to free all strings as well */ if (model->flags & M3D_FLG_FREESTR) { if (model->name) M3D_FREE(model->name); if (model->license) M3D_FREE(model->license); if (model->author) M3D_FREE(model->author); if (model->desc) M3D_FREE(model->desc); if (model->bone) for (i = 0; i < model->numbone; i++) if (model->bone[i].name) M3D_FREE(model->bone[i].name); if (model->shape) for (i = 0; i < model->numshape; i++) if (model->shape[i].name) M3D_FREE(model->shape[i].name); if (model->material) for (i = 0; i < model->nummaterial; i++) if (model->material[i].name) M3D_FREE(model->material[i].name); if (model->action) for (i = 0; i < model->numaction; i++) if (model->action[i].name) M3D_FREE(model->action[i].name); if (model->texture) for (i = 0; i < model->numtexture; i++) if (model->texture[i].name) M3D_FREE(model->texture[i].name); if (model->inlined) for (i = 0; i < model->numinlined; i++) { if (model->inlined[i].name) M3D_FREE(model->inlined[i].name); if (model->inlined[i].data) M3D_FREE(model->inlined[i].data); } if (model->extra) for (i = 0; i < model->numextra; i++) if (model->extra[i]) M3D_FREE(model->extra[i]); if (model->label) for (i = 0; i < model->numlabel; i++) { if (model->label[i].name) { for (j = i + 1; j < model->numlabel; j++) if (model->label[j].name == model->label[i].name) model->label[j].name = NULL; M3D_FREE(model->label[i].name); } if (model->label[i].lang) { for (j = i + 1; j < model->numlabel; j++) if (model->label[j].lang == model->label[i].lang) model->label[j].lang = NULL; M3D_FREE(model->label[i].lang); } if (model->label[i].text) M3D_FREE(model->label[i].text); } if (model->preview.data) M3D_FREE(model->preview.data); } #endif if (model->flags & M3D_FLG_FREERAW) M3D_FREE(model->raw); if (model->tmap) M3D_FREE(model->tmap); if (model->bone) { for (i = 0; i < model->numbone; i++) if (model->bone[i].weight) M3D_FREE(model->bone[i].weight); M3D_FREE(model->bone); } if (model->skin) M3D_FREE(model->skin); if (model->vertex) M3D_FREE(model->vertex); if (model->face) M3D_FREE(model->face); if (model->shape) { for (i = 0; i < model->numshape; i++) { if (model->shape[i].cmd) { for (j = 0; j < model->shape[i].numcmd; j++) if (model->shape[i].cmd[j].arg) M3D_FREE(model->shape[i].cmd[j].arg); M3D_FREE(model->shape[i].cmd); } } M3D_FREE(model->shape); } if (model->material && !(model->flags & M3D_FLG_MTLLIB)) { for (i = 0; i < model->nummaterial; i++) if (model->material[i].prop) M3D_FREE(model->material[i].prop); M3D_FREE(model->material); } if (model->texture) { for (i = 0; i < model->numtexture; i++) if (model->texture[i].d) M3D_FREE(model->texture[i].d); M3D_FREE(model->texture); } if (model->action) { for (i = 0; i < model->numaction; i++) { if (model->action[i].frame) { for (j = 0; j < model->action[i].numframe; j++) if (model->action[i].frame[j].transform) M3D_FREE(model->action[i].frame[j].transform); M3D_FREE(model->action[i].frame); } } M3D_FREE(model->action); } if (model->label) M3D_FREE(model->label); if (model->inlined) M3D_FREE(model->inlined); if (model->extra) M3D_FREE(model->extra); free(model); } #endif #ifdef M3D_EXPORTER typedef struct { char *str; uint32_t offs; } m3dstr_t; typedef struct { m3dti_t data; M3D_INDEX oldidx; M3D_INDEX newidx; } m3dtisave_t; typedef struct { m3dv_t data; M3D_INDEX oldidx; M3D_INDEX newidx; unsigned char norm; } m3dvsave_t; typedef struct { m3ds_t data; M3D_INDEX oldidx; M3D_INDEX newidx; } m3dssave_t; typedef struct { m3df_t data; int group; uint8_t opacity; } m3dfsave_t; /* create unique list of strings */ static m3dstr_t *_m3d_addstr(m3dstr_t *str, uint32_t *numstr, char *s) { uint32_t i; if (!s || !*s) return str; if (str) { for (i = 0; i < *numstr; i++) if (str[i].str == s || !strcmp(str[i].str, s)) return str; } str = (m3dstr_t *)M3D_REALLOC(str, ((*numstr) + 1) * sizeof(m3dstr_t)); str[*numstr].str = s; str[*numstr].offs = 0; (*numstr)++; return str; } /* add strings to header */ m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s) { int i; char *safe = _m3d_safestr(s->str, 0); i = (int)strlen(safe); h = (m3dhdr_t *)M3D_REALLOC(h, h->length + i + 1); if (!h) { M3D_FREE(safe); return NULL; } memcpy((uint8_t *)h + h->length, safe, i + 1); s->offs = h->length - 16; h->length += i + 1; M3D_FREE(safe); return h; } /* return offset of string */ static uint32_t _m3d_stridx(m3dstr_t *str, uint32_t numstr, char *s) { uint32_t i; char *safe; if (!s || !*s) return 0; if (str) { safe = _m3d_safestr(s, 0); if (!safe) return 0; if (!*safe) { free(safe); return 0; } for (i = 0; i < numstr; i++) if (!strcmp(str[i].str, s)) { free(safe); return str[i].offs; } free(safe); } return 0; } /* compare to faces by their material */ static int _m3d_facecmp(const void *a, const void *b) { const m3dfsave_t *A = (const m3dfsave_t *)a, *B = (const m3dfsave_t *)b; return A->group != B->group ? A->group - B->group : (A->opacity != B->opacity ? (int)B->opacity - (int)A->opacity : (int)A->data.materialid - (int)B->data.materialid); } /* compare face groups */ static int _m3d_grpcmp(const void *a, const void *b) { return *((uint32_t *)a) - *((uint32_t *)b); } /* compare UVs */ static int _m3d_ticmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3dti_t)); } /* compare skin groups */ static int _m3d_skincmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3ds_t)); } /* compare vertices */ static int _m3d_vrtxcmp(const void *a, const void *b) { int c = memcmp(a, b, 3 * sizeof(M3D_FLOAT)); if (c) return c; c = ((m3dvsave_t *)a)->norm - ((m3dvsave_t *)b)->norm; if (c) return c; return memcmp(a, b, sizeof(m3dv_t)); } /* compare labels */ static _inline int _m3d_strcmp(char *a, char *b) { if (a == NULL && b != NULL) return -1; if (a != NULL && b == NULL) return 1; if (a == NULL && b == NULL) return 0; return strcmp(a, b); } static int _m3d_lblcmp(const void *a, const void *b) { const m3dl_t *A = (const m3dl_t *)a, *B = (const m3dl_t *)b; int c = _m3d_strcmp(A->lang, B->lang); if (!c) c = _m3d_strcmp(A->name, B->name); if (!c) c = _m3d_strcmp(A->text, B->text); return c; } /* compare two colors by HSV value */ _inline static int _m3d_cmapcmp(const void *a, const void *b) { uint8_t *A = (uint8_t *)a, *B = (uint8_t *)b; _register int m, vA, vB; /* get HSV value for A */ m = A[2] < A[1] ? A[2] : A[1]; if (A[0] < m) m = A[0]; vA = A[2] > A[1] ? A[2] : A[1]; if (A[0] > vA) vA = A[0]; /* get HSV value for B */ m = B[2] < B[1] ? B[2] : B[1]; if (B[0] < m) m = B[0]; vB = B[2] > B[1] ? B[2] : B[1]; if (B[0] > vB) vB = B[0]; return vA - vB; } /* create sorted list of colors */ static uint32_t *_m3d_addcmap(uint32_t *cmap, uint32_t *numcmap, uint32_t color) { uint32_t i; if (cmap) { for (i = 0; i < *numcmap; i++) if (cmap[i] == color) return cmap; } cmap = (uint32_t *)M3D_REALLOC(cmap, ((*numcmap) + 1) * sizeof(uint32_t)); for (i = 0; i < *numcmap && _m3d_cmapcmp(&color, &cmap[i]) > 0; i++) ; if (i < *numcmap) memmove(&cmap[i + 1], &cmap[i], ((*numcmap) - i) * sizeof(uint32_t)); cmap[i] = color; (*numcmap)++; return cmap; } /* look up a color and return its index */ static uint32_t _m3d_cmapidx(uint32_t *cmap, uint32_t numcmap, uint32_t color) { uint32_t i; if (numcmap >= 65536) return color; for (i = 0; i < numcmap; i++) if (cmap[i] == color) return i; return 0; } /* add index to output */ static unsigned char *_m3d_addidx(unsigned char *out, char type, uint32_t idx) { switch (type) { case 1: *out++ = (uint8_t)(idx); break; case 2: *((uint16_t *)out) = (uint16_t)(idx); out += 2; break; case 4: *((uint32_t *)out) = (uint32_t)(idx); out += 4; break; /* case 0: case 8: break; */ } return out; } /* round a vertex position */ static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst) { _register int t; /* copy additional attributes */ if (src != dst) memcpy(dst, src, sizeof(m3dv_t)); /* round according to quality */ switch (quality) { case M3D_EXP_INT8: t = (int)(src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->x = (M3D_FLOAT)t / (M3D_FLOAT)127.0; t = (int)(src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->y = (M3D_FLOAT)t / (M3D_FLOAT)127.0; t = (int)(src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->z = (M3D_FLOAT)t / (M3D_FLOAT)127.0; t = (int)(src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->w = (M3D_FLOAT)t / (M3D_FLOAT)127.0; break; case M3D_EXP_INT16: t = (int)(src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->x = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; t = (int)(src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->y = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; t = (int)(src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->z = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; t = (int)(src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5)); dst->w = (M3D_FLOAT)t / (M3D_FLOAT)32767.0; break; } if (dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0; if (dst->y == (M3D_FLOAT)-0.0) dst->y = (M3D_FLOAT)0.0; if (dst->z == (M3D_FLOAT)-0.0) dst->z = (M3D_FLOAT)0.0; if (dst->w == (M3D_FLOAT)-0.0) dst->w = (M3D_FLOAT)0.0; } #ifdef M3D_ASCII /* add a bone to ascii output */ static char *_m3d_prtbone(char *ptr, m3db_t *bone, M3D_INDEX numbone, M3D_INDEX parent, uint32_t level, M3D_INDEX *vrtxidx) { uint32_t i, j; char *sn; if (level > M3D_BONEMAXLEVEL || !bone) return ptr; for (i = 0; i < numbone; i++) { if (bone[i].parent == parent) { for (j = 0; j < level; j++) *ptr++ = '/'; sn = _m3d_safestr(bone[i].name, 0); ptr += sprintf(ptr, "%d %d %s\r\n", vrtxidx[bone[i].pos], vrtxidx[bone[i].ori], sn); M3D_FREE(sn); ptr = _m3d_prtbone(ptr, bone, numbone, i, level + 1, vrtxidx); } } return ptr; } #endif /** * Function to encode an in-memory model into on storage Model 3D format */ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size) { #ifdef M3D_ASCII const char *ol; char *ptr; #endif char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; char *sn = NULL, *sl = NULL, *sa = NULL, *sd = NULL; unsigned char *out = NULL, *z = NULL, weights[M3D_NUMBONE], *norm = NULL; unsigned int i, j, k, l, n, len, chunklen, *length; M3D_FLOAT scale = (M3D_FLOAT)0.0, min_x, max_x, min_y, max_y, min_z, max_z; M3D_INDEX last, *vrtxidx = NULL, *mtrlidx = NULL, *tmapidx = NULL, *skinidx = NULL; uint32_t idx, numcmap = 0, *cmap = NULL, numvrtx = 0, maxvrtx = 0, numtmap = 0, maxtmap = 0, numproc = 0; uint32_t numskin = 0, maxskin = 0, numstr = 0, maxt = 0, maxbone = 0, numgrp = 0, maxgrp = 0, *grpidx = NULL; uint8_t *opa = nullptr; m3dcd_t *cd; m3dc_t *cmd; m3dstr_t *str = NULL; m3dvsave_t *vrtx = NULL, vertex; m3dtisave_t *tmap = NULL, tcoord; m3dssave_t *skin = NULL, sk; m3dfsave_t *face = NULL; m3dhdr_t *h = NULL; m3dm_t *m; m3da_t *a; if (!model) { if (size) *size = 0; return NULL; } model->errcode = M3D_SUCCESS; #ifdef M3D_ASCII if (flags & M3D_EXP_ASCII) quality = M3D_EXP_DOUBLE; #endif vrtxidx = (M3D_INDEX *)M3D_MALLOC(model->numvertex * sizeof(M3D_INDEX)); if (!vrtxidx) goto memerr; memset(vrtxidx, 255, model->numvertex * sizeof(M3D_INDEX)); if (model->numvertex && !(flags & M3D_EXP_NONORMAL)) { norm = (unsigned char *)M3D_MALLOC(model->numvertex * sizeof(unsigned char)); if (!norm) goto memerr; memset(norm, 0, model->numvertex * sizeof(unsigned char)); } if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { mtrlidx = (M3D_INDEX *)M3D_MALLOC(model->nummaterial * sizeof(M3D_INDEX)); if (!mtrlidx) goto memerr; memset(mtrlidx, 255, model->nummaterial * sizeof(M3D_INDEX)); opa = (uint8_t *)M3D_MALLOC(model->nummaterial * 2 * sizeof(M3D_INDEX)); if (!opa) goto memerr; memset(opa, 255, model->nummaterial * 2 * sizeof(M3D_INDEX)); } if (model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { tmapidx = (M3D_INDEX *)M3D_MALLOC(model->numtmap * sizeof(M3D_INDEX)); if (!tmapidx) goto memerr; memset(tmapidx, 255, model->numtmap * sizeof(M3D_INDEX)); } /** collect array elements that are actually referenced **/ if (!(flags & M3D_EXP_NOFACE)) { /* face */ if (model->numface && model->face) { M3D_LOG("Processing mesh face"); face = (m3dfsave_t *)M3D_MALLOC(model->numface * sizeof(m3dfsave_t)); if (!face) goto memerr; for (i = 0; i < model->numface; i++) { memcpy(&face[i].data, &model->face[i], sizeof(m3df_t)); face[i].group = 0; face[i].opacity = 255; if (!(flags & M3D_EXP_NOMATERIAL) && model->face[i].materialid < model->nummaterial) { if (model->material[model->face[i].materialid].numprop) { mtrlidx[model->face[i].materialid] = 0; if (opa[model->face[i].materialid * 2]) { m = &model->material[model->face[i].materialid]; for (j = 0; j < m->numprop; j++) if (m->prop[j].type == m3dp_Kd) { opa[model->face[i].materialid * 2 + 1] = ((uint8_t *)&m->prop[j].value.color)[3]; break; } for (j = 0; j < m->numprop; j++) if (m->prop[j].type == m3dp_d) { opa[model->face[i].materialid * 2 + 1] = (uint8_t)(m->prop[j].value.fnum * 255); break; } opa[model->face[i].materialid * 2] = 0; } face[i].opacity = opa[model->face[i].materialid * 2 + 1]; } else face[i].data.materialid = M3D_UNDEF; } for (j = 0; j < 3; j++) { k = model->face[i].vertex[j]; if (k < model->numvertex) vrtxidx[k] = 0; if (!(flags & M3D_EXP_NOCMAP)) { cmap = _m3d_addcmap(cmap, &numcmap, model->vertex[k].color); if (!cmap) goto memerr; } k = model->face[i].normal[j]; if (k < model->numvertex && !(flags & M3D_EXP_NONORMAL)) { vrtxidx[k] = 0; norm[k] = 1; } k = model->face[i].texcoord[j]; if (k < model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) tmapidx[k] = 0; } /* convert from CW to CCW */ if (flags & M3D_EXP_IDOSUCK) { j = face[i].data.vertex[1]; face[i].data.vertex[1] = face[i].data.vertex[2]; face[i].data.vertex[2] = face[i].data.vertex[1]; j = face[i].data.normal[1]; face[i].data.normal[1] = face[i].data.normal[2]; face[i].data.normal[2] = face[i].data.normal[1]; j = face[i].data.texcoord[1]; face[i].data.texcoord[1] = face[i].data.texcoord[2]; face[i].data.texcoord[2] = face[i].data.texcoord[1]; } } } if (model->numshape && model->shape) { M3D_LOG("Processing shape face"); for (i = 0; i < model->numshape; i++) { if (!model->shape[i].numcmd) continue; str = _m3d_addstr(str, &numstr, model->shape[i].name); if (!str) goto memerr; for (j = 0; j < model->shape[i].numcmd; j++) { cmd = &model->shape[i].cmd[j]; if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg) continue; if (cmd->type == m3dc_mesh) { if (numgrp + 2 < maxgrp) { maxgrp += 1024; grpidx = (uint32_t *)realloc(grpidx, maxgrp * sizeof(uint32_t)); if (!grpidx) goto memerr; if (!numgrp) { grpidx[0] = 0; grpidx[1] = model->numface; numgrp += 2; } } grpidx[numgrp + 0] = cmd->arg[0]; grpidx[numgrp + 1] = cmd->arg[0] + cmd->arg[1]; numgrp += 2; } cd = &m3d_commandtypes[cmd->type]; for (k = n = 0, l = cd->p; k < l; k++) switch (cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: if (!(flags & M3D_EXP_NOMATERIAL) && cmd->arg[k] < model->nummaterial) mtrlidx[cmd->arg[k]] = 0; break; case m3dcp_ti_t: if (!(flags & M3D_EXP_NOTXTCRD) && cmd->arg[k] < model->numtmap) tmapidx[cmd->arg[k]] = 0; break; case m3dcp_qi_t: case m3dcp_vi_t: if (cmd->arg[k] < model->numvertex) vrtxidx[cmd->arg[k]] = 0; break; case m3dcp_va_t: n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); break; } } } } if (model->numface && face) { if (numgrp && grpidx) { qsort(grpidx, numgrp, sizeof(uint32_t), _m3d_grpcmp); for (i = j = 0; i < model->numface && j < numgrp; i++) { while (j < numgrp && grpidx[j] < i) j++; face[i].group = j; } } qsort(face, model->numface, sizeof(m3dfsave_t), _m3d_facecmp); } if (grpidx) { M3D_FREE(grpidx); grpidx = NULL; } if (model->numlabel && model->label) { M3D_LOG("Processing annotation labels"); for (i = 0; i < model->numlabel; i++) { str = _m3d_addstr(str, &numstr, model->label[i].name); str = _m3d_addstr(str, &numstr, model->label[i].lang); str = _m3d_addstr(str, &numstr, model->label[i].text); if (!(flags & M3D_EXP_NOCMAP)) { cmap = _m3d_addcmap(cmap, &numcmap, model->label[i].color); if (!cmap) goto memerr; } if (model->label[i].vertexid < model->numvertex) vrtxidx[model->label[i].vertexid] = 0; } qsort(model->label, model->numlabel, sizeof(m3dl_t), _m3d_lblcmp); } } else if (!(flags & M3D_EXP_NOMATERIAL)) { /* without a face, simply add all materials, because it can be an mtllib */ for (i = 0; i < model->nummaterial; i++) mtrlidx[i] = i; } /* bind-pose skeleton */ if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { M3D_LOG("Processing bones"); for (i = 0; i < model->numbone; i++) { str = _m3d_addstr(str, &numstr, model->bone[i].name); if (!str) goto memerr; k = model->bone[i].pos; if (k < model->numvertex) vrtxidx[k] = 0; k = model->bone[i].ori; if (k < model->numvertex) vrtxidx[k] = 0; } } /* actions, animated skeleton poses */ if (model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { M3D_LOG("Processing action list"); for (j = 0; j < model->numaction; j++) { a = &model->action[j]; str = _m3d_addstr(str, &numstr, a->name); if (!str) goto memerr; if (a->numframe > 65535) a->numframe = 65535; for (i = 0; i < a->numframe; i++) { for (l = 0; l < a->frame[i].numtransform; l++) { k = a->frame[i].transform[l].pos; if (k < model->numvertex) vrtxidx[k] = 0; k = a->frame[i].transform[l].ori; if (k < model->numvertex) vrtxidx[k] = 0; } if (l > maxt) maxt = l; } } } /* add colors to color map and texture names to string table */ if (!(flags & M3D_EXP_NOMATERIAL)) { M3D_LOG("Processing materials"); for (i = k = 0; i < model->nummaterial; i++) { if (mtrlidx[i] == M3D_UNDEF || !model->material[i].numprop) continue; mtrlidx[i] = k++; m = &model->material[i]; str = _m3d_addstr(str, &numstr, m->name); if (!str) goto memerr; if (m->prop) for (j = 0; j < m->numprop; j++) { if (!(flags & M3D_EXP_NOCMAP) && m->prop[j].type < 128) { for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) { if (m->prop[j].type == m3d_propertytypes[l].id && m3d_propertytypes[l].format == m3dpf_color) { ((uint8_t *)&m->prop[j].value.color)[3] = opa[i * 2 + 1]; cmap = _m3d_addcmap(cmap, &numcmap, m->prop[j].value.color); if (!cmap) goto memerr; break; } } } if (m->prop[j].type >= 128 && m->prop[j].value.textureid < model->numtexture && model->texture[m->prop[j].value.textureid].name) { str = _m3d_addstr(str, &numstr, model->texture[m->prop[j].value.textureid].name); if (!str) goto memerr; } } } } /* if there's only one black color, don't store it */ if (numcmap == 1 && cmap && !cmap[0]) numcmap = 0; /** compress lists **/ if (model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { M3D_LOG("Compressing tmap"); tmap = (m3dtisave_t *)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t)); if (!tmap) goto memerr; for (i = 0; i < model->numtmap; i++) { if (tmapidx[i] == M3D_UNDEF) continue; switch (quality) { case M3D_EXP_INT8: l = (unsigned int)(model->tmap[i].u * 255); tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)255.0; l = (unsigned int)(model->tmap[i].v * 255); tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)255.0; break; case M3D_EXP_INT16: l = (unsigned int)(model->tmap[i].u * 65535); tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)65535.0; l = (unsigned int)(model->tmap[i].v * 65535); tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)65535.0; break; default: tcoord.data.u = model->tmap[i].u; tcoord.data.v = model->tmap[i].v; break; } if (flags & M3D_EXP_FLIPTXTCRD) tcoord.data.v = (M3D_FLOAT)1.0 - tcoord.data.v; tcoord.oldidx = i; memcpy(&tmap[numtmap++], &tcoord, sizeof(m3dtisave_t)); } if (numtmap) { qsort(tmap, numtmap, sizeof(m3dtisave_t), _m3d_ticmp); memcpy(&tcoord.data, &tmap[0], sizeof(m3dti_t)); for (i = 0; i < numtmap; i++) { if (memcmp(&tcoord.data, &tmap[i].data, sizeof(m3dti_t))) { memcpy(&tcoord.data, &tmap[i].data, sizeof(m3dti_t)); maxtmap++; } tmap[i].newidx = maxtmap; tmapidx[tmap[i].oldidx] = maxtmap; } maxtmap++; } } if (model->numskin && model->skin && !(flags & M3D_EXP_NOBONE)) { M3D_LOG("Compressing skin"); skinidx = (M3D_INDEX *)M3D_MALLOC(model->numskin * sizeof(M3D_INDEX)); if (!skinidx) goto memerr; skin = (m3dssave_t *)M3D_MALLOC(model->numskin * sizeof(m3dssave_t)); if (!skin) goto memerr; memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX)); for (i = 0; i < model->numvertex; i++) { if (vrtxidx[i] != M3D_UNDEF && model->vertex[i].skinid < model->numskin) skinidx[model->vertex[i].skinid] = 0; } for (i = 0; i < model->numskin; i++) { if (skinidx[i] == M3D_UNDEF) continue; memset(&sk, 0, sizeof(m3dssave_t)); for (j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != M3D_UNDEF && model->skin[i].weight[j] > (M3D_FLOAT)0.0; j++) { sk.data.boneid[j] = model->skin[i].boneid[j]; sk.data.weight[j] = model->skin[i].weight[j]; min_x += sk.data.weight[j]; } if (j > maxbone) maxbone = j; if (min_x != (M3D_FLOAT)1.0 && min_x != (M3D_FLOAT)0.0) for (j = 0; j < M3D_NUMBONE && sk.data.weight[j] > (M3D_FLOAT)0.0; j++) sk.data.weight[j] /= min_x; sk.oldidx = i; memcpy(&skin[numskin++], &sk, sizeof(m3dssave_t)); } if (numskin) { qsort(skin, numskin, sizeof(m3dssave_t), _m3d_skincmp); memcpy(&sk.data, &skin[0].data, sizeof(m3ds_t)); for (i = 0; i < numskin; i++) { if (memcmp(&sk.data, &skin[i].data, sizeof(m3ds_t))) { memcpy(&sk.data, &skin[i].data, sizeof(m3ds_t)); maxskin++; } skin[i].newidx = maxskin; skinidx[skin[i].oldidx] = maxskin; } maxskin++; } } M3D_LOG("Compressing vertex list"); min_x = min_y = min_z = (M3D_FLOAT)1e10; max_x = max_y = max_z = (M3D_FLOAT)-1e10; if (vrtxidx) { vrtx = (m3dvsave_t *)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t)); if (!vrtx) goto memerr; for (i = numvrtx = 0; i < model->numvertex; i++) { if (vrtxidx[i] == M3D_UNDEF) continue; _m3d_round(quality, &model->vertex[i], &vertex.data); vertex.norm = norm ? norm[i] : 0; if (vertex.data.skinid != M3D_INDEXMAX && !vertex.norm) { vertex.data.skinid = vertex.data.skinid != M3D_UNDEF && skinidx ? skinidx[vertex.data.skinid] : M3D_UNDEF; if (vertex.data.x > max_x) max_x = vertex.data.x; if (vertex.data.x < min_x) min_x = vertex.data.x; if (vertex.data.y > max_y) max_y = vertex.data.y; if (vertex.data.y < min_y) min_y = vertex.data.y; if (vertex.data.z > max_z) max_z = vertex.data.z; if (vertex.data.z < min_z) min_z = vertex.data.z; } #ifdef M3D_VERTEXTYPE vertex.data.type = 0; #endif vertex.oldidx = i; memcpy(&vrtx[numvrtx++], &vertex, sizeof(m3dvsave_t)); } if (numvrtx) { qsort(vrtx, numvrtx, sizeof(m3dvsave_t), _m3d_vrtxcmp); memcpy(&vertex.data, &vrtx[0].data, sizeof(m3dv_t)); for (i = 0; i < numvrtx; i++) { if (memcmp(&vertex.data, &vrtx[i].data, vrtx[i].norm ? 3 * sizeof(M3D_FLOAT) : sizeof(m3dv_t))) { memcpy(&vertex.data, &vrtx[i].data, sizeof(m3dv_t)); maxvrtx++; } vrtx[i].newidx = maxvrtx; vrtxidx[vrtx[i].oldidx] = maxvrtx; } maxvrtx++; } } if (skinidx) { M3D_FREE(skinidx); skinidx = NULL; } if (norm) { M3D_FREE(norm); norm = NULL; } /* normalize to bounding cube */ if (numvrtx && !(flags & M3D_EXP_NORECALC)) { M3D_LOG("Normalizing coordinates"); if (min_x < (M3D_FLOAT)0.0) min_x = -min_x; if (max_x < (M3D_FLOAT)0.0) max_x = -max_x; if (min_y < (M3D_FLOAT)0.0) min_y = -min_y; if (max_y < (M3D_FLOAT)0.0) max_y = -max_y; if (min_z < (M3D_FLOAT)0.0) min_z = -min_z; if (max_z < (M3D_FLOAT)0.0) max_z = -max_z; scale = min_x; if (max_x > scale) scale = max_x; if (min_y > scale) scale = min_y; if (max_y > scale) scale = max_y; if (min_z > scale) scale = min_z; if (max_z > scale) scale = max_z; if (scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; if (scale != (M3D_FLOAT)1.0) { for (i = 0; i < numvrtx; i++) { if (vrtx[i].data.skinid == M3D_INDEXMAX) continue; vrtx[i].data.x /= scale; vrtx[i].data.y /= scale; vrtx[i].data.z /= scale; } } } if (model->scale > (M3D_FLOAT)0.0) scale = model->scale; if (scale <= (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; /* meta info */ sn = _m3d_safestr(model->name && *model->name ? model->name : (char *)"(noname)", 2); sl = _m3d_safestr(model->license ? model->license : (char *)"MIT", 2); sa = _m3d_safestr(model->author ? model->author : getenv("LOGNAME"), 2); if (!sn || !sl || !sa) { memerr: if (vrtxidx) M3D_FREE(vrtxidx); if (mtrlidx) M3D_FREE(mtrlidx); if (tmapidx) M3D_FREE(tmapidx); if (skinidx) M3D_FREE(skinidx); if (grpidx) M3D_FREE(grpidx); if (norm) M3D_FREE(norm); if (face) M3D_FREE(face); if (cmap) M3D_FREE(cmap); if (tmap) M3D_FREE(tmap); if (skin) M3D_FREE(skin); if (str) M3D_FREE(str); if (vrtx) M3D_FREE(vrtx); if (sn) M3D_FREE(sn); if (sl) M3D_FREE(sl); if (sa) M3D_FREE(sa); if (sd) M3D_FREE(sd); if (out) M3D_FREE(out); if (h) M3D_FREE(h); M3D_LOG("Out of memory"); model->errcode = M3D_ERR_ALLOC; return NULL; } M3D_LOG("Serializing model"); #ifdef M3D_ASCII if (flags & M3D_EXP_ASCII) { /* use CRLF to make model creators on Win happy... */ sd = _m3d_safestr(model->desc, 1); if (!sd) goto memerr; ol = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); /* header */ len = 64 + (unsigned int)(strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd)); out = (unsigned char *)M3D_MALLOC(len); if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr = (char *)out; ptr += sprintf(ptr, "3dmodel %g\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", scale, sn, sl, sa, sd); M3D_FREE(sl); M3D_FREE(sa); M3D_FREE(sd); sl = sa = sd = NULL; /* preview chunk */ if (model->preview.data && model->preview.length) { sl = _m3d_safestr(sn, 0); if (sl) { ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl); M3D_FREE(sl); sl = NULL; } } M3D_FREE(sn); sn = NULL; /* texture map */ if (numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxtmap * 32) + (uintptr_t)12); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Textmap\r\n"); last = M3D_UNDEF; for (i = 0; i < numtmap; i++) { if (tmap[i].newidx == last) continue; last = tmap[i].newidx; ptr += sprintf(ptr, "%g %g\r\n", tmap[i].data.u, tmap[i].data.v); } ptr += sprintf(ptr, "\r\n"); } /* vertex chunk */ if (numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) { ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxvrtx * 128) + (uintptr_t)10); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Vertex\r\n"); last = M3D_UNDEF; for (i = 0; i < numvrtx; i++) { if (vrtx[i].newidx == last) continue; last = vrtx[i].newidx; ptr += sprintf(ptr, "%g %g %g %g", vrtx[i].data.x, vrtx[i].data.y, vrtx[i].data.z, vrtx[i].data.w); if (!(flags & M3D_EXP_NOCMAP) && vrtx[i].data.color) ptr += sprintf(ptr, " #%08x", vrtx[i].data.color); if (!(flags & M3D_EXP_NOBONE) && model->numbone && maxskin && vrtx[i].data.skinid < M3D_INDEXMAX) { if (skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0) ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]); else for (j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != M3D_UNDEF && skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0; j++) ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j], skin[vrtx[i].data.skinid].data.weight[j]); } ptr += sprintf(ptr, "\r\n"); } ptr += sprintf(ptr, "\r\n"); } /* bones chunk */ if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)9); for (i = 0; i < model->numbone; i++) { len += (unsigned int)strlen(model->bone[i].name) + 128; } out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Bones\r\n"); ptr = _m3d_prtbone(ptr, model->bone, model->numbone, M3D_UNDEF, 0, vrtxidx); ptr += sprintf(ptr, "\r\n"); } /* materials */ if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { for (j = 0; j < model->nummaterial; j++) { if (mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue; m = &model->material[j]; sn = _m3d_safestr(m->name, 0); if (!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)12); for (i = 0; i < m->numprop; i++) { if (m->prop[i].type < 128) len += 32; else if (m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name) len += (unsigned int)strlen(model->texture[m->prop[i].value.textureid].name) + 16; } out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Material %s\r\n", sn); M3D_FREE(sn); sn = NULL; for (i = 0; i < m->numprop; i++) { k = 256; if (m->prop[i].type >= 128) { for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) if (m->prop[i].type == m3d_propertytypes[l].id) { sn = m3d_propertytypes[l].key; break; } if (!sn) for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) if (m->prop[i].type - 128 == m3d_propertytypes[l].id) { sn = m3d_propertytypes[l].key; break; } k = sn ? m3dpf_map : 256; } else { for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) if (m->prop[i].type == m3d_propertytypes[l].id) { sn = m3d_propertytypes[l].key; k = m3d_propertytypes[l].format; break; } } switch (k) { case m3dpf_color: ptr += sprintf(ptr, "%s #%08x\r\n", sn, m->prop[i].value.color); break; case m3dpf_uint8: case m3dpf_uint16: case m3dpf_uint32: ptr += sprintf(ptr, "%s %d\r\n", sn, m->prop[i].value.num); break; case m3dpf_float: ptr += sprintf(ptr, "%s %g\r\n", sn, m->prop[i].value.fnum); break; case m3dpf_map: if (m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name) { sl = _m3d_safestr(model->texture[m->prop[i].value.textureid].name, 0); if (!sl) { setlocale(LC_NUMERIC, ol); goto memerr; } if (*sl) ptr += sprintf(ptr, "map_%s %s\r\n", sn, sl); M3D_FREE(sn); M3D_FREE(sl); sl = NULL; } break; } sn = NULL; } ptr += sprintf(ptr, "\r\n"); } } /* procedural face */ if (model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { /* all inlined assets which are not textures should be procedural surfaces */ for (j = 0; j < model->numinlined; j++) { if (!model->inlined[j].name || !*model->inlined[j].name || !model->inlined[j].length || !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')) continue; for (i = k = 0; i < model->numtexture; i++) { if (!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } } if (k) continue; sn = _m3d_safestr(model->inlined[j].name, 0); if (!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)18); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn); M3D_FREE(sn); sn = NULL; } } /* mesh face */ if (model->numface && face && !(flags & M3D_EXP_NOFACE)) { ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(model->numface * 128) + (uintptr_t)6); last = M3D_UNDEF; if (!(flags & M3D_EXP_NOMATERIAL)) for (i = 0; i < model->numface; i++) { j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF; if (j != last) { last = j; if (last < model->nummaterial) len += (unsigned int)strlen(model->material[last].name); len += 6; } } out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Mesh\r\n"); last = M3D_UNDEF; for (i = 0; i < model->numface; i++) { j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF; if (!(flags & M3D_EXP_NOMATERIAL) && j != last) { last = j; if (last < model->nummaterial) { sn = _m3d_safestr(model->material[last].name, 0); if (!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "use %s\r\n", sn); M3D_FREE(sn); sn = NULL; } else ptr += sprintf(ptr, "use\r\n"); } /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */ for (j = 0; j < 3; j++) { ptr += sprintf(ptr, "%s%d", j ? " " : "", vrtxidx[face[i].data.vertex[j]]); k = M3D_NOTDEFINED; if (!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != M3D_UNDEF) && (tmapidx[face[i].data.texcoord[j]] != M3D_UNDEF)) { k = tmapidx[face[i].data.texcoord[j]]; ptr += sprintf(ptr, "/%d", k); } if (!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != M3D_UNDEF)) ptr += sprintf(ptr, "%s/%d", k == M3D_NOTDEFINED ? "/" : "", vrtxidx[face[i].data.normal[j]]); } ptr += sprintf(ptr, "\r\n"); } ptr += sprintf(ptr, "\r\n"); } /* mathematical shapes face */ if (model->numshape !(flags & M3D_EXP_NOFACE)) { for (j = 0; j < model->numshape; j++) { sn = _m3d_safestr(model->shape[j].name, 0); if (!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)33); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Shape %s\r\n", sn); M3D_FREE(sn); sn = NULL; if (model->shape[j].group != M3D_UNDEF && !(flags & M3D_EXP_NOBONE)) ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group); for (i = 0; i < model->shape[j].numcmd; i++) { cmd = &model->shape[j].cmd[i]; if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg) continue; cd = &m3d_commandtypes[cmd->type]; ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(cd->key) + (uintptr_t)3); for (k = 0; k < cd->p; k++) switch (cd->a[k]) { case m3dcp_mi_t: if (cmd->arg[k] != M3D_NOTDEFINED) { len += (unsigned int)strlen(model->material[cmd->arg[k]].name) + 1; } break; case m3dcp_va_t: len += cmd->arg[k] * (cd->p - k - 1) * 16; k = cd->p; break; default: len += 16; break; } out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "%s", cd->key); for (k = n = 0, l = cd->p; k < l; k++) { switch (cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: if (cmd->arg[k] != M3D_NOTDEFINED) { sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0); if (!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, " %s", sn); M3D_FREE(sn); sn = NULL; } break; case m3dcp_vc_t: ptr += sprintf(ptr, " %g", *((float *)&cmd->arg[k])); break; case m3dcp_va_t: ptr += sprintf(ptr, " %d[", cmd->arg[k]); n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); break; default: ptr += sprintf(ptr, " %d", cmd->arg[k]); break; } } ptr += sprintf(ptr, "%s\r\n", l > cd->p ? " ]" : ""); } ptr += sprintf(ptr, "\r\n"); } } /* annotation labels */ if (model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) { for (i = 0, j = 3, length = NULL; i < model->numlabel; i++) { if (model->label[i].name) j += (unsigned int)strlen(model->label[i].name); if (model->label[i].lang) j += (unsigned int)strlen(model->label[i].lang); if (model->label[i].text) j += (unsigned int)strlen(model->label[i].text); j += 40; } ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } for (i = 0; i < model->numlabel; i++) { if (!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { sl = model->label[i].lang; sn = model->label[i].name; sd = _m3d_safestr(sn, 0); if (!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } if (i) ptr += sprintf(ptr, "\r\n"); ptr += sprintf(ptr, "Labels %s\r\n", sd); M3D_FREE(sd); sd = NULL; if (model->label[i].color) ptr += sprintf(ptr, "color #0x%08x\r\n", model->label[i].color); if (sl && *sl) { sd = _m3d_safestr(sl, 0); if (!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } ptr += sprintf(ptr, "lang %s\r\n", sd); M3D_FREE(sd); sd = NULL; } } sd = _m3d_safestr(model->label[i].text, 2); if (!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } ptr += sprintf(ptr, "%d %s\r\n", model->label[i].vertexid, sd); M3D_FREE(sd); sd = NULL; } ptr += sprintf(ptr, "\r\n"); sn = sl = NULL; } /* actions */ if (model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { for (j = 0; j < model->numaction; j++) { a = &model->action[j]; sn = _m3d_safestr(a->name, 0); if (!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)48); for (i = 0; i < a->numframe; i++) len += a->frame[i].numtransform * 128 + 8; out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Action %d %s\r\n", a->durationmsec, sn); M3D_FREE(sn); sn = NULL; for (i = 0; i < a->numframe; i++) { ptr += sprintf(ptr, "frame %d\r\n", a->frame[i].msec); for (k = 0; k < a->frame[i].numtransform; k++) { ptr += sprintf(ptr, "%d %d %d\r\n", a->frame[i].transform[k].boneid, vrtxidx[a->frame[i].transform[k].pos], vrtxidx[a->frame[i].transform[k].ori]); } } ptr += sprintf(ptr, "\r\n"); } } /* inlined assets */ if (model->numinlined && model->inlined) { for (i = j = 0; i < model->numinlined; i++) if (model->inlined[i].name) j += (unsigned int)strlen(model->inlined[i].name) + 6; if (j > 0) { ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j + (uintptr_t)16); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Assets\r\n"); for (i = 0; i < model->numinlined; i++) if (model->inlined[i].name) ptr += sprintf(ptr, "%s%s\r\n", model->inlined[i].name, strrchr(model->inlined[i].name, '.') ? "" : ".png"); ptr += sprintf(ptr, "\r\n"); } } /* extra info */ if (model->numextra && (flags & M3D_EXP_EXTRA)) { for (i = 0; i < model->numextra; i++) { if (model->extra[i]->length < 9) continue; ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)17 + (uintptr_t)(model->extra[i]->length * 3)); out = (unsigned char *)M3D_REALLOC(out, len); ptr += (uintptr_t)out; if (!out) { setlocale(LC_NUMERIC, ol); goto memerr; } ptr += sprintf(ptr, "Extra %c%c%c%c\r\n", model->extra[i]->magic[0] > ' ' ? model->extra[i]->magic[0] : '_', model->extra[i]->magic[1] > ' ' ? model->extra[i]->magic[1] : '_', model->extra[i]->magic[2] > ' ' ? model->extra[i]->magic[2] : '_', model->extra[i]->magic[3] > ' ' ? model->extra[i]->magic[3] : '_'); for (j = 0; j < model->extra[i]->length; j++) ptr += sprintf(ptr, "%02x ", *((unsigned char *)model->extra + sizeof(m3dchunk_t) + j)); ptr--; ptr += sprintf(ptr, "\r\n\r\n"); } } setlocale(LC_NUMERIC, ol); len = (unsigned int)((uintptr_t)ptr - (uintptr_t)out); out = (unsigned char *)M3D_REALLOC(out, len + 1); if (!out) goto memerr; out[len] = 0; } else #endif { /* stricly only use LF (newline) in binary */ sd = _m3d_safestr(model->desc, 3); if (!sd) goto memerr; /* header */ h = (m3dhdr_t *)M3D_MALLOC(sizeof(m3dhdr_t) + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd) + 4); if (!h) goto memerr; memcpy((uint8_t *)h, "HEAD", 4); h->length = sizeof(m3dhdr_t); h->scale = scale; i = (unsigned int)strlen(sn); memcpy((uint8_t *)h + h->length, sn, i + 1); h->length += i + 1; M3D_FREE(sn); i = (unsigned int)strlen(sl); memcpy((uint8_t *)h + h->length, sl, i + 1); h->length += i + 1; M3D_FREE(sl); i = (unsigned int)strlen(sa); memcpy((uint8_t *)h + h->length, sa, i + 1); h->length += i + 1; M3D_FREE(sa); i = (unsigned int)strlen(sd); memcpy((uint8_t *)h + h->length, sd, i + 1); h->length += i + 1; M3D_FREE(sd); sn = sl = sa = sd = NULL; if (model->inlined) for (i = 0; i < model->numinlined; i++) { if (model->inlined[i].name && *model->inlined[i].name && model->inlined[i].length > 0) { str = _m3d_addstr(str, &numstr, model->inlined[i].name); if (!str) goto memerr; } } if (str) for (i = 0; i < numstr; i++) { h = _m3d_addhdr(h, &str[i]); if (!h) goto memerr; } vc_s = quality == M3D_EXP_INT8 ? 1 : (quality == M3D_EXP_INT16 ? 2 : (quality == M3D_EXP_DOUBLE ? 8 : 4)); vi_s = maxvrtx < 254 ? 1 : (maxvrtx < 65534 ? 2 : 4); si_s = h->length - 16 < 254 ? 1 : (h->length - 16 < 65534 ? 2 : 4); ci_s = !numcmap || !cmap ? 0 : (numcmap < 254 ? 1 : (numcmap < 65534 ? 2 : 4)); ti_s = !maxtmap || !tmap ? 0 : (maxtmap < 254 ? 1 : (maxtmap < 65534 ? 2 : 4)); bi_s = !model->numbone || !model->bone || (flags & M3D_EXP_NOBONE) ? 0 : (model->numbone < 254 ? 1 : (model->numbone < 65534 ? 2 : 4)); nb_s = maxbone < 2 ? 1 : (maxbone == 2 ? 2 : (maxbone <= 4 ? 4 : 8)); sk_s = !bi_s || !maxskin || !skin ? 0 : (maxskin < 254 ? 1 : (maxskin < 65534 ? 2 : 4)); fc_s = maxt < 254 ? 1 : (maxt < 65534 ? 2 : 4); hi_s = !model->numshape || !model->shape || (flags & M3D_EXP_NOFACE) ? 0 : (model->numshape < 254 ? 1 : (model->numshape < 65534 ? 2 : 4)); fi_s = !model->numface || !model->face || (flags & M3D_EXP_NOFACE) ? 0 : (model->numface < 254 ? 1 : (model->numface < 65534 ? 2 : 4)); h->types = (vc_s == 8 ? (3 << 0) : (vc_s == 2 ? (1 << 0) : (vc_s == 1 ? (0 << 0) : (2 << 0)))) | (vi_s == 2 ? (1 << 2) : (vi_s == 1 ? (0 << 2) : (2 << 2))) | (si_s == 2 ? (1 << 4) : (si_s == 1 ? (0 << 4) : (2 << 4))) | (ci_s == 2 ? (1 << 6) : (ci_s == 1 ? (0 << 6) : (ci_s == 4 ? (2 << 6) : (3 << 6)))) | (ti_s == 2 ? (1 << 8) : (ti_s == 1 ? (0 << 8) : (ti_s == 4 ? (2 << 8) : (3 << 8)))) | (bi_s == 2 ? (1 << 10) : (bi_s == 1 ? (0 << 10) : (bi_s == 4 ? (2 << 10) : (3 << 10)))) | (nb_s == 2 ? (1 << 12) : (nb_s == 1 ? (0 << 12) : (2 << 12))) | (sk_s == 2 ? (1 << 14) : (sk_s == 1 ? (0 << 14) : (sk_s == 4 ? (2 << 14) : (3 << 14)))) | (fc_s == 2 ? (1 << 16) : (fc_s == 1 ? (0 << 16) : (2 << 16))) | (hi_s == 2 ? (1 << 18) : (hi_s == 1 ? (0 << 18) : (hi_s == 4 ? (2 << 18) : (3 << 18)))) | (fi_s == 2 ? (1 << 20) : (fi_s == 1 ? (0 << 20) : (fi_s == 4 ? (2 << 20) : (3 << 20)))); len = h->length; /* preview image chunk, must be the first if exists */ if (model->preview.data && model->preview.length) { chunklen = 8 + model->preview.length; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "PRVW", 4); *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; memcpy((uint8_t *)h + len + 8, model->preview.data, model->preview.length); len += chunklen; } /* color map */ if (numcmap && cmap && ci_s < 4 && !(flags & M3D_EXP_NOCMAP)) { chunklen = 8 + numcmap * sizeof(uint32_t); h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "CMAP", 4); *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; memcpy((uint8_t *)h + len + 8, cmap, chunklen - 8); len += chunklen; } else numcmap = 0; /* texture map */ if (numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { chunklen = 8 + maxtmap * vc_s * 2; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "TMAP", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; last = M3D_UNDEF; for (i = 0; i < numtmap; i++) { if (tmap[i].newidx == last) continue; last = tmap[i].newidx; switch (vc_s) { case 1: *out++ = (uint8_t)(tmap[i].data.u * 255); *out++ = (uint8_t)(tmap[i].data.v * 255); break; case 2: *((uint16_t *)out) = (uint16_t)(tmap[i].data.u * 65535); out += 2; *((uint16_t *)out) = (uint16_t)(tmap[i].data.v * 65535); out += 2; break; case 4: *((float *)out) = tmap[i].data.u; out += 4; *((float *)out) = tmap[i].data.v; out += 4; break; case 8: *((double *)out) = tmap[i].data.u; out += 8; *((double *)out) = tmap[i].data.v; out += 8; break; } } *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); out = NULL; len += *length; } /* vertex */ if (numvrtx && vrtx) { chunklen = 8 + maxvrtx * (ci_s + sk_s + 4 * vc_s); h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "VRTS", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; last = M3D_UNDEF; for (i = 0; i < numvrtx; i++) { if (vrtx[i].newidx == last) continue; last = vrtx[i].newidx; switch (vc_s) { case 1: *out++ = (int8_t)(vrtx[i].data.x * 127); *out++ = (int8_t)(vrtx[i].data.y * 127); *out++ = (int8_t)(vrtx[i].data.z * 127); *out++ = (int8_t)(vrtx[i].data.w * 127); break; case 2: *((int16_t *)out) = (int16_t)(vrtx[i].data.x * 32767); out += 2; *((int16_t *)out) = (int16_t)(vrtx[i].data.y * 32767); out += 2; *((int16_t *)out) = (int16_t)(vrtx[i].data.z * 32767); out += 2; *((int16_t *)out) = (int16_t)(vrtx[i].data.w * 32767); out += 2; break; case 4: memcpy(out, &vrtx[i].data.x, sizeof(float)); out += 4; memcpy(out, &vrtx[i].data.y, sizeof(float)); out += 4; memcpy(out, &vrtx[i].data.z, sizeof(float)); out += 4; memcpy(out, &vrtx[i].data.w, sizeof(float)); out += 4; break; case 8: *((double *)out) = vrtx[i].data.x; out += 8; *((double *)out) = vrtx[i].data.y; out += 8; *((double *)out) = vrtx[i].data.z; out += 8; *((double *)out) = vrtx[i].data.w; out += 8; break; } idx = _m3d_cmapidx(cmap, numcmap, vrtx[i].data.color); switch (ci_s) { case 1: *out++ = (uint8_t)(idx); break; case 2: *((uint16_t *)out) = (uint16_t)(idx); out += 2; break; case 4: *((uint32_t *)out) = vrtx[i].data.color; out += 4; break; } out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid); } uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); memcpy(length, &v, sizeof(uint32_t)); //*length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); out = NULL; len += v; } /* bones chunk */ if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { i = 8 + bi_s + sk_s + model->numbone * (bi_s + si_s + 2 * vi_s); chunklen = i + numskin * nb_s * (bi_s + 1); h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "BONE", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, bi_s, model->numbone); out = _m3d_addidx(out, sk_s, maxskin); for (i = 0; i < model->numbone; i++) { out = _m3d_addidx(out, bi_s, model->bone[i].parent); out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->bone[i].name)); out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].pos]); out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]); } if (numskin && skin && sk_s) { last = M3D_UNDEF; for (i = 0; i < numskin; i++) { if (skin[i].newidx == last) continue; last = skin[i].newidx; memset(&weights, 0, nb_s); for (j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && skin[i].data.weight[j] > (M3D_FLOAT)0.0; j++) weights[j] = (uint8_t)(skin[i].data.weight[j] * 255); switch (nb_s) { case 1: weights[0] = 255; break; case 2: *((uint16_t *)out) = *((uint16_t *)&weights[0]); out += 2; break; case 4: *((uint32_t *)out) = *((uint32_t *)&weights[0]); out += 4; break; case 8: *((uint64_t *)out) = *((uint64_t *)&weights[0]); out += 8; break; } for (j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && weights[j]; j++) { out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]); *length += bi_s; } } } *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); out = NULL; len += *length; } /* materials */ if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { for (j = 0; j < model->nummaterial; j++) { if (mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue; m = &model->material[j]; chunklen = 12 + si_s + m->numprop * 5; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "MTRL", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, m->name)); for (i = 0; i < m->numprop; i++) { if (m->prop[i].type >= 128) { if (m->prop[i].value.textureid >= model->numtexture || !model->texture[m->prop[i].value.textureid].name) continue; k = m3dpf_map; } else { for (k = 256, l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) if (m->prop[i].type == m3d_propertytypes[l].id) { k = m3d_propertytypes[l].format; break; } } if (k == 256) continue; *out++ = m->prop[i].type; switch (k) { case m3dpf_color: if (!(flags & M3D_EXP_NOCMAP)) { idx = _m3d_cmapidx(cmap, numcmap, m->prop[i].value.color); switch (ci_s) { case 1: *out++ = (uint8_t)(idx); break; case 2: *((uint16_t *)out) = (uint16_t)(idx); out += 2; break; case 4: *((uint32_t *)out) = (uint32_t)(m->prop[i].value.color); out += 4; break; } } else out--; break; case m3dpf_uint8: *out++ = m->prop[i].value.num; break; case m3dpf_uint16: *((uint16_t *)out) = m->prop[i].value.num; out += 2; break; case m3dpf_uint32: *((uint32_t *)out) = m->prop[i].value.num; out += 4; break; case m3dpf_float: *((float *)out) = m->prop[i].value.fnum; out += 4; break; case m3dpf_map: idx = _m3d_stridx(str, numstr, model->texture[m->prop[i].value.textureid].name); out = _m3d_addidx(out, si_s, idx); break; } } *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); len += *length; out = NULL; } } /* procedural face */ if (model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { /* all inlined assets which are not textures should be procedural surfaces */ for (j = 0; j < model->numinlined; j++) { if (!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')) continue; for (i = k = 0; i < model->numtexture; i++) { if (!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } } if (k) continue; numproc++; chunklen = 8 + si_s; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "PROC", 4); *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); out = NULL; len += chunklen; } } /* mesh face */ if (model->numface && face && !(flags & M3D_EXP_NOFACE)) { chunklen = 8 + si_s + model->numface * (6 * vi_s + 3 * ti_s + si_s + 1); h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "MESH", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; last = M3D_UNDEF; for (i = 0; i < model->numface; i++) { if (!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) { last = face[i].data.materialid; idx = last < model->nummaterial ? _m3d_stridx(str, numstr, model->material[last].name) : 0; *out++ = 0; out = _m3d_addidx(out, si_s, idx); } /* hardcoded triangles. */ k = (3 << 4) | (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == M3D_UNDEF || face[i].data.texcoord[1] == M3D_UNDEF || face[i].data.texcoord[2] == M3D_UNDEF) ? 0 : 1) | (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == M3D_UNDEF || face[i].data.normal[1] == M3D_UNDEF || face[i].data.normal[2] == M3D_UNDEF) ? 0 : 2); *out++ = k; for (j = 0; j < 3; j++) { out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]); if (k & 1) out = _m3d_addidx(out, ti_s, tmapidx[face[i].data.texcoord[j]]); if (k & 2) out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]); } } uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); memcpy(length, &v, sizeof(uint32_t)); len += v; out = NULL; } /* mathematical shapes face */ if (model->numshape && model->shape && !(flags & M3D_EXP_NOFACE)) { for (j = 0; j < model->numshape; j++) { chunklen = 12 + si_s + model->shape[j].numcmd * (M3D_CMDMAXARG + 1) * 4; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "SHPE", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->shape[j].name)); out = _m3d_addidx(out, bi_s, model->shape[j].group); for (i = 0; i < model->shape[j].numcmd; i++) { cmd = &model->shape[j].cmd[i]; if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg) continue; cd = &m3d_commandtypes[cmd->type]; *out++ = (cmd->type & 0x7F) | (cmd->type > 127 ? 0x80 : 0); if (cmd->type > 127) *out++ = (cmd->type >> 7) & 0xff; for (k = n = 0, l = cd->p; k < l; k++) { switch (cd->a[((k - n) % (cd->p - n)) + n]) { case m3dcp_mi_t: out = _m3d_addidx(out, si_s, cmd->arg[k] < model->nummaterial ? _m3d_stridx(str, numstr, model->material[cmd->arg[k]].name) : 0); break; case m3dcp_vc_t: min_x = *((float *)&cmd->arg[k]); switch (vc_s) { case 1: *out++ = (int8_t)(min_x * 127); break; case 2: *((int16_t *)out) = (int16_t)(min_x * 32767); out += 2; break; case 4: *((float *)out) = min_x; out += 4; break; case 8: *((double *)out) = min_x; out += 8; break; } break; case m3dcp_hi_t: out = _m3d_addidx(out, hi_s, cmd->arg[k]); break; case m3dcp_fi_t: out = _m3d_addidx(out, fi_s, cmd->arg[k]); break; case m3dcp_ti_t: out = _m3d_addidx(out, ti_s, cmd->arg[k]); break; case m3dcp_qi_t: case m3dcp_vi_t: out = _m3d_addidx(out, vi_s, cmd->arg[k]); break; case m3dcp_i1_t: out = _m3d_addidx(out, 1, cmd->arg[k]); break; case m3dcp_i2_t: out = _m3d_addidx(out, 2, cmd->arg[k]); break; case m3dcp_i4_t: out = _m3d_addidx(out, 4, cmd->arg[k]); break; case m3dcp_va_t: out = _m3d_addidx(out, 4, cmd->arg[k]); n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); break; } } } uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); memcpy( length, &v, sizeof(uint32_t)); len += v; out = NULL; } } /* annotation labels */ if (model->numlabel && model->label) { for (i = 0, length = NULL; i < model->numlabel; i++) { if (!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { sl = model->label[i].lang; sn = model->label[i].name; if (length) { *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); len += *length; } chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s); h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) { sn = NULL; sl = NULL; goto memerr; } memcpy((uint8_t *)h + len, "LBLS", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].name)); out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].lang)); idx = _m3d_cmapidx(cmap, numcmap, model->label[i].color); switch (ci_s) { case 1: *out++ = (uint8_t)(idx); break; case 2: *((uint16_t *)out) = (uint16_t)(idx); out += 2; break; case 4: *((uint32_t *)out) = model->label[i].color; out += 4; break; } } out = _m3d_addidx(out, vi_s, vrtxidx[model->label[i].vertexid]); out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text)); } if (length) { uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); memcpy( length, &v, sizeof(uint32_t)); len += v; } out = NULL; sn = sl = NULL; } /* actions */ if (model->numaction && model->action && model->numbone && model->bone && !(flags & M3D_EXP_NOACTION)) { for (j = 0; j < model->numaction; j++) { a = &model->action[j]; chunklen = 14 + si_s + a->numframe * (4 + fc_s + maxt * (bi_s + 2 * vi_s)); h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "ACTN", 4); length = (uint32_t *)((uint8_t *)h + len + 4); out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, a->name)); *((uint16_t *)out) = (uint16_t)(a->numframe); out += 2; *((uint32_t *)out) = (uint32_t)(a->durationmsec); out += 4; for (i = 0; i < a->numframe; i++) { *((uint32_t *)out) = (uint32_t)(a->frame[i].msec); out += 4; out = _m3d_addidx(out, fc_s, a->frame[i].numtransform); for (k = 0; k < a->frame[i].numtransform; k++) { out = _m3d_addidx(out, bi_s, a->frame[i].transform[k].boneid); out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].pos]); out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]); } } uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); memcpy( length, &v, sizeof(uint32_t)); len += v; out = NULL; } } /* inlined assets */ if (model->numinlined && model->inlined && (numproc || (flags & M3D_EXP_INLINE))) { for (j = 0; j < model->numinlined; j++) { if (!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || !model->inlined[j].data) continue; if (!(flags & M3D_EXP_INLINE)) { if (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G') continue; for (i = k = 0; i < model->numtexture; i++) { if (!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } } if (k) continue; } chunklen = 8 + si_s + model->inlined[j].length; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, "ASET", 4); *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen; out = (uint8_t *)h + len + 8; out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); memcpy(out, model->inlined[j].data, model->inlined[j].length); out = NULL; len += chunklen; } } /* extra chunks */ if (model->numextra && model->extra && (flags & M3D_EXP_EXTRA)) { for (j = 0; j < model->numextra; j++) { if (!model->extra[j] || model->extra[j]->length < 8) continue; chunklen = model->extra[j]->length; h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen); if (!h) goto memerr; memcpy((uint8_t *)h + len, model->extra[j], chunklen); len += chunklen; } } /* add end chunk */ h = (m3dhdr_t *)M3D_REALLOC(h, len + 4); if (!h) goto memerr; memcpy((uint8_t *)h + len, "OMD3", 4); len += 4; /* zlib compress */ if (!(flags & M3D_EXP_NOZLIB)) { M3D_LOG("Deflating chunks"); z = stbi_zlib_compress((unsigned char *)h, len, (int *)&l, 9); if (z && l > 0 && l < len) { len = l; M3D_FREE(h); h = (m3dhdr_t *)z; } } /* add file header at the begining */ len += 8; out = (unsigned char *)M3D_MALLOC(len); if (!out) goto memerr; memcpy(out, "3DMO", 4); *((uint32_t *)(out + 4)) = len; memcpy(out + 8, h, len - 8); } if (size) *size = out ? len : 0; if (vrtxidx) M3D_FREE(vrtxidx); if (mtrlidx) M3D_FREE(mtrlidx); if (tmapidx) M3D_FREE(tmapidx); if (skinidx) M3D_FREE(skinidx); if (norm) M3D_FREE(norm); if (face) M3D_FREE(face); if (cmap) M3D_FREE(cmap); if (tmap) M3D_FREE(tmap); if (skin) M3D_FREE(skin); if (str) M3D_FREE(str); if (vrtx) M3D_FREE(vrtx); if (h) M3D_FREE(h); return out; } #endif #endif #ifdef __cplusplus } #ifdef M3D_CPPWRAPPER #include #include #include /*** C++ wrapper class ***/ namespace M3D { #ifdef M3D_IMPLEMENTATION class Model { public: m3d_t *model; public: Model() { this->model = (m3d_t *)malloc(sizeof(m3d_t)); memset(this->model, 0, sizeof(m3d_t)); } Model(_unused const std::string &data, _unused m3dread_t ReadFileCB, _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { #ifndef M3D_NOIMPORTER this->model = m3d_load((unsigned char *)data.data(), ReadFileCB, FreeCB, mtllib.model); #else Model(); #endif } Model(_unused const std::vector data, _unused m3dread_t ReadFileCB, _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { #ifndef M3D_NOIMPORTER this->model = m3d_load((unsigned char *)&data[0], ReadFileCB, FreeCB, mtllib.model); #else Model(); #endif } Model(_unused const unsigned char *data, _unused m3dread_t ReadFileCB, _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { #ifndef M3D_NOIMPORTER this->model = m3d_load((unsigned char *)data, ReadFileCB, FreeCB, mtllib.model); #else Model(); #endif } ~Model() { m3d_free(this->model); } public: m3d_t *getCStruct() { return this->model; } std::string getName() { return std::string(this->model->name); } void setName(std::string name) { this->model->name = (char *)name.c_str(); } std::string getLicense() { return std::string(this->model->license); } void setLicense(std::string license) { this->model->license = (char *)license.c_str(); } std::string getAuthor() { return std::string(this->model->author); } void setAuthor(std::string author) { this->model->author = (char *)author.c_str(); } std::string getDescription() { return std::string(this->model->desc); } void setDescription(std::string desc) { this->model->desc = (char *)desc.c_str(); } float getScale() { return this->model->scale; } void setScale(float scale) { this->model->scale = scale; } std::vector getPreview() { return this->model->preview.data ? std::vector(this->model->preview.data, this->model->preview.data + this->model->preview.length) : std::vector(); } std::vector getColorMap() { return this->model->cmap ? std::vector(this->model->cmap, this->model->cmap + this->model->numcmap) : std::vector(); } std::vector getTextureMap() { return this->model->tmap ? std::vector(this->model->tmap, this->model->tmap + this->model->numtmap) : std::vector(); } std::vector getTextures() { return this->model->texture ? std::vector(this->model->texture, this->model->texture + this->model->numtexture) : std::vector(); } std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ? std::string(this->model->texture[idx].name) : nullptr; } std::vector getBones() { return this->model->bone ? std::vector(this->model->bone, this->model->bone + this->model->numbone) : std::vector(); } std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ? std::string(this->model->bone[idx].name) : nullptr; } std::vector getMaterials() { return this->model->material ? std::vector(this->model->material, this->model->material + this->model->nummaterial) : std::vector(); } std::string getMaterialName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->nummaterial ? std::string(this->model->material[idx].name) : nullptr; } int getMaterialPropertyInt(int idx, int type) { if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || !this->model->material[idx].prop) return -1; for (int i = 0; i < this->model->material[idx].numprop; i++) { if (this->model->material[idx].prop[i].type == type) return this->model->material[idx].prop[i].value.num; } return -1; } uint32_t getMaterialPropertyColor(int idx, int type) { return this->getMaterialPropertyInt(idx, type); } float getMaterialPropertyFloat(int idx, int type) { if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || !this->model->material[idx].prop) return -1.0f; for (int i = 0; i < this->model->material[idx].numprop; i++) { if (this->model->material[idx].prop[i].type == type) return this->model->material[idx].prop[i].value.fnum; } return -1.0f; } m3dtx_t *getMaterialPropertyMap(int idx, int type) { if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 128 || type > 255 || !this->model->material[idx].prop) return nullptr; for (int i = 0; i < this->model->material[idx].numprop; i++) { if (this->model->material[idx].prop[i].type == type) return this->model->material[idx].prop[i].value.textureid < this->model->numtexture ? &this->model->texture[this->model->material[idx].prop[i].value.textureid] : nullptr; } return nullptr; } std::vector getVertices() { return this->model->vertex ? std::vector(this->model->vertex, this->model->vertex + this->model->numvertex) : std::vector(); } std::vector getFace() { return this->model->face ? std::vector(this->model->face, this->model->face + this->model->numface) : std::vector(); } std::vector getShape() { return this->model->shape ? std::vector(this->model->shape, this->model->shape + this->model->numshape) : std::vector(); } std::string getShapeName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && this->model->shape[idx].name && this->model->shape[idx].name[0] ? std::string(this->model->shape[idx].name) : nullptr; } unsigned int getShapeGroup(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape ? this->model->shape[idx].group : 0xFFFFFFFF; } std::vector getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && this->model->shape[idx].cmd ? std::vector(this->model->shape[idx].cmd, this->model->shape[idx].cmd + this->model->shape[idx].numcmd) : std::vector(); } std::vector getAnnotationLabels() { return this->model->label ? std::vector(this->model->label, this->model->label + this->model->numlabel) : std::vector(); } std::vector getSkin() { return this->model->skin ? std::vector(this->model->skin, this->model->skin + this->model->numskin) : std::vector(); } std::vector getActions() { return this->model->action ? std::vector(this->model->action, this->model->action + this->model->numaction) : std::vector(); } std::string getActionName(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? std::string(this->model->action[aidx].name) : nullptr; } unsigned int getActionDuration(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? this->model->action[aidx].durationmsec : 0; } std::vector getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? std::vector(this->model->action[aidx].frame, this->model->action[aidx].frame + this->model->action[aidx].numframe) : std::vector(); } unsigned int getActionFrameTimestamp(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? (fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? this->model->action[aidx].frame[fidx].msec : 0) : 0; } std::vector getActionFrameTransforms(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? ( fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? std::vector(this->model->action[aidx].frame[fidx].transform, this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) : std::vector()) : std::vector(); } std::vector getActionFrame(int aidx, int fidx, std::vector skeleton) { m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx, skeleton.size() ? &skeleton[0] : nullptr); return std::vector(pose, pose + this->model->numbone); } std::vector getActionPose(int aidx, unsigned int msec) { m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec); return std::vector(pose, pose + this->model->numbone); } std::vector getInlinedAssets() { return this->model->inlined ? std::vector(this->model->inlined, this->model->inlined + this->model->numinlined) : std::vector(); } std::vector> getExtras() { return this->model->extra ? std::vector>(this->model->extra, this->model->extra + this->model->numextra) : std::vector>(); } std::vector Save(_unused int quality, _unused int flags) { #ifdef M3D_EXPORTER unsigned int size; unsigned char *ptr = m3d_save(this->model, quality, flags, &size); return ptr && size ? std::vector(ptr, ptr + size) : std::vector(); #else return std::vector(); #endif } }; #else class Model { public: m3d_t *model; public: Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB); Model(const std::vector data, m3dread_t ReadFileCB, m3dfree_t FreeCB); Model(const unsigned char *data, m3dread_t ReadFileCB, m3dfree_t FreeCB); Model(); ~Model(); public: m3d_t *getCStruct(); std::string getName(); void setName(std::string name); std::string getLicense(); void setLicense(std::string license); std::string getAuthor(); void setAuthor(std::string author); std::string getDescription(); void setDescription(std::string desc); float getScale(); void setScale(float scale); std::vector getPreview(); std::vector getColorMap(); std::vector getTextureMap(); std::vector getTextures(); std::string getTextureName(int idx); std::vector getBones(); std::string getBoneName(int idx); std::vector getMaterials(); std::string getMaterialName(int idx); int getMaterialPropertyInt(int idx, int type); uint32_t getMaterialPropertyColor(int idx, int type); float getMaterialPropertyFloat(int idx, int type); m3dtx_t *getMaterialPropertyMap(int idx, int type); std::vector getVertices(); std::vector getFace(); std::vector getShape(); std::string getShapeName(int idx); unsigned int getShapeGroup(int idx); std::vector getShapeCommands(int idx); std::vector getAnnotationLabels(); std::vector getSkin(); std::vector getActions(); std::string getActionName(int aidx); unsigned int getActionDuration(int aidx); std::vector getActionFrames(int aidx); unsigned int getActionFrameTimestamp(int aidx, int fidx); std::vector getActionFrameTransforms(int aidx, int fidx); std::vector getActionFrame(int aidx, int fidx, std::vector skeleton); std::vector getActionPose(int aidx, unsigned int msec); std::vector getInlinedAssets(); std::vector> getExtras(); std::vector Save(int quality, int flags); }; #endif /* impl */ } // namespace M3D #ifdef _WIN32 # pragma warning(pop) #endif // _WIN32 #endif #endif /* __cplusplus */ #endif