loader_aw

Load OBJ/MTL file formats
git clone git://git.meso-star.fr/loader_aw.git
Log | Files | Refs | README | LICENSE

aw_mtl.c (22799B)


      1 /* Copyright (C) 2014-2017, 2020-2023 Vincent Forest (vaplv@free.fr)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU Lesser General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU Lesser General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #define _POSIX_C_SOURCE 200112L /* strtok_r support */
     17 
     18 #include "aw_c.h"
     19 
     20 #include <rsys/cstr.h>
     21 #include <rsys/double3.h>
     22 #include <rsys/logger.h>
     23 #include <rsys/mem_allocator.h>
     24 #include <rsys/ref_count.h>
     25 #include <rsys/str.h>
     26 #include <rsys/text_reader.h>
     27 
     28 #include <float.h>
     29 
     30 #ifdef COMPILER_CL
     31   #pragma warning(push)
     32   #pragma warning(disable:4706) /* Assignment within a condition */
     33 #endif
     34 
     35 static const char* MSG_PREFIX_INFO = "load-mtl:info: ";
     36 static const char* MSG_PREFIX_ERROR = "load-mtl:error: ";
     37 static const char* MSG_PREFIX_WARNING = "load-mtl:warning: ";
     38 
     39 enum map_type {
     40   MAP_COMMON = BIT(0),
     41   MAP_SCALAR = BIT(1),
     42   MAP_BUMP = MAP_SCALAR | BIT(2)
     43 };
     44 
     45 /*******************************************************************************
     46  * Map data structure
     47  ******************************************************************************/
     48 struct map {
     49   struct str filename; /* str_len == 0 <=> Not defined */
     50   int options_mask;
     51   double image_bias; /* Scalar to add to the image pixels */
     52   double image_scale; /* Scalar to multiply to the image pixels */
     53   double texcoord_bias[3];
     54   double texcoord_scale[3];
     55   double texcoord_turbulence[3];
     56   size_t resolution; /* image size = resolution x resolution */
     57   enum aw_map_channel scalar; /* Channel used to create a scalar texture */
     58   double bump_multiplier; /* Only available on bump maps */
     59 };
     60 
     61 static INLINE void
     62 map_init(struct mem_allocator* allocator, struct map* map)
     63 {
     64   ASSERT(map);
     65   str_init(allocator, &map->filename);
     66   map->options_mask = 0;
     67   map->image_bias = 0.f;
     68   map->image_scale = 1.f;
     69   d3_splat(map->texcoord_bias, 0.f);
     70   d3_splat(map->texcoord_scale, 1.f);
     71   d3_splat(map->texcoord_turbulence, 0.f);
     72   map->resolution = 0;
     73   map->scalar = AW_MAP_CHANNEL_LUMINANCE;
     74   map->bump_multiplier = 1.0f;
     75 }
     76 
     77 static INLINE void
     78 map_release(struct map* map)
     79 {
     80   ASSERT(map);
     81   str_release(&map->filename);
     82 }
     83 
     84 static INLINE void
     85 map_copy_pod(struct map* dst, const struct map* src)
     86 {
     87   ASSERT(dst && src);
     88   dst->options_mask = src->options_mask;
     89   dst->image_bias = src->image_bias;
     90   dst->image_scale = src->image_scale;
     91   d3_set(dst->texcoord_bias, src->texcoord_bias);
     92   d3_set(dst->texcoord_scale, src->texcoord_scale);
     93   d3_set(dst->texcoord_turbulence, src->texcoord_turbulence);
     94   dst->resolution = src->resolution;
     95   dst->scalar = src->scalar;
     96   dst->bump_multiplier = src->bump_multiplier;
     97 }
     98 
     99 static INLINE res_T
    100 map_copy(struct map* dst, const struct map* src)
    101 {
    102   map_copy_pod(dst, src);
    103   return str_copy(&dst->filename, &src->filename);
    104 }
    105 
    106 static INLINE res_T
    107 map_copy_and_release(struct map* dst, struct map* src)
    108 {
    109   map_copy_pod(dst, src);
    110   return str_copy_and_release(&dst->filename, &src->filename);
    111 }
    112 
    113 static INLINE void
    114 map_to_aw_map(const struct map* map, struct aw_map* aw_map)
    115 {
    116   ASSERT(map && aw_map);
    117   aw_map->filename = str_is_empty(&map->filename) ? NULL : str_cget(&map->filename);
    118   aw_map->options_mask = map->options_mask;
    119   aw_map->image_bias = map->image_bias;
    120   aw_map->image_scale = map->image_scale;
    121   d3_set(aw_map->texcoord_bias, map->texcoord_bias);
    122   d3_set(aw_map->texcoord_scale, map->texcoord_scale);
    123   d3_set(aw_map->texcoord_turbulence, map->texcoord_turbulence);
    124   aw_map->resolution = map->resolution;
    125   aw_map->scalar = map->scalar;
    126   aw_map->bump_multiplier = map->bump_multiplier;
    127 }
    128 
    129 /*******************************************************************************
    130  * Material API
    131  ******************************************************************************/
    132 struct material {
    133   struct str name;
    134   struct aw_color ambient;
    135   struct aw_color diffuse;
    136   struct aw_color specular;
    137   struct aw_color transmission;
    138   double specular_exponent;
    139   double refraction_index;
    140   size_t illumination_model; /* In [0, 10] */
    141   struct map ambient_map;
    142   struct map diffuse_map;
    143   struct map specular_map;
    144   struct map specular_exponent_map; /* Scalar texture */
    145   struct map bump_map; /* Scalar texture with valid bump multiplier */
    146 };
    147 
    148 static INLINE void
    149 material_init(struct mem_allocator* allocator, struct material* mtl)
    150 {
    151   ASSERT(mtl);
    152   memset(mtl, 0, sizeof(struct aw_material));
    153   str_init(allocator, &mtl->name);
    154   map_init(allocator, &mtl->ambient_map);
    155   map_init(allocator, &mtl->diffuse_map);
    156   map_init(allocator, &mtl->specular_map);
    157   map_init(allocator, &mtl->specular_exponent_map);
    158   map_init(allocator, &mtl->bump_map);
    159 }
    160 
    161 static INLINE void
    162 material_release(struct material* mtl)
    163 {
    164   ASSERT(mtl);
    165   str_release(&mtl->name);
    166   map_release(&mtl->ambient_map);
    167   map_release(&mtl->diffuse_map);
    168   map_release(&mtl->specular_map);
    169   map_release(&mtl->specular_exponent_map);
    170   map_release(&mtl->bump_map);
    171 }
    172 
    173 static INLINE void
    174 material_copy_pod(struct material* dst, const struct material* src)
    175 {
    176   ASSERT(dst && src);
    177   dst->ambient = src->ambient;
    178   dst->diffuse = src->diffuse;
    179   dst->specular = src->specular;
    180   dst->transmission = src->transmission;
    181   dst->specular_exponent = src->specular_exponent;
    182   dst->refraction_index = src->refraction_index;
    183   dst->illumination_model = src->illumination_model;
    184 }
    185 
    186 static INLINE res_T
    187 material_copy(struct material* dst, const struct material* src)
    188 {
    189   res_T res = RES_OK;
    190   ASSERT(dst && src);
    191   material_copy_pod(dst, src);
    192   #define CALL(Func) if(RES_OK != (res = Func)) return res
    193   CALL(str_copy(&dst->name, &src->name));
    194   CALL(map_copy(&dst->ambient_map, &src->ambient_map));
    195   CALL(map_copy(&dst->diffuse_map, &src->diffuse_map));
    196   CALL(map_copy(&dst->specular_map, &src->specular_map));
    197   CALL(map_copy(&dst->specular_exponent_map, &src->specular_exponent_map));
    198   CALL(map_copy(&dst->bump_map, &src->bump_map));
    199   #undef CALL
    200   return RES_OK;
    201 }
    202 
    203 static INLINE res_T
    204 material_copy_and_release(struct material* dst, struct material* src)
    205 {
    206   res_T res = RES_OK;
    207   ASSERT(dst && src);
    208   material_copy_pod(dst, src);
    209   #define CALL(Func) if(RES_OK != (res = Func)) return res
    210   CALL(str_copy_and_release(&dst->name, &src->name));
    211   CALL(map_copy_and_release(&dst->ambient_map, &src->ambient_map));
    212   CALL(map_copy_and_release(&dst->diffuse_map, &src->diffuse_map));
    213   CALL(map_copy_and_release(&dst->specular_map, &src->specular_map));
    214   CALL(map_copy_and_release
    215     (&dst->specular_exponent_map, &src->specular_exponent_map));
    216   CALL(map_copy_and_release(&dst->bump_map, &src->bump_map));
    217   #undef CALL
    218   return RES_OK;
    219 }
    220 
    221 /* Generate the darray_mtl data structure */
    222 #define DARRAY_NAME material
    223 #define DARRAY_DATA struct material
    224 #define DARRAY_FUNCTOR_INIT material_init
    225 #define DARRAY_FUNCTOR_RELEASE material_release
    226 #define DARRAY_FUNCTOR_COPY material_copy
    227 #define DARRAY_FUNCTOR_COPY_AND_RELEASE material_copy_and_release
    228 #include <rsys/dynamic_array.h>
    229 
    230 /*******************************************************************************
    231  * Definition of the aw_mtl opaque data structure
    232  ******************************************************************************/
    233 struct aw_mtl {
    234   struct darray_material materials;
    235   struct material* newmtl; /* Pointer toward the current material */
    236 
    237   ref_T ref;
    238   struct mem_allocator* allocator;
    239   struct logger* logger;
    240   struct logger logger__; /* Default logger */
    241   int verbose;
    242 };
    243 
    244 /*******************************************************************************
    245  * Helper functions
    246  ******************************************************************************/
    247 static INLINE void
    248 log_msg
    249   (const struct aw_mtl* mtl,
    250    const enum log_type stream,
    251    const char* msg,
    252    va_list vargs)
    253 {
    254   ASSERT(mtl && msg);
    255   if(mtl->verbose) {
    256     res_T res; (void)res;
    257     res = logger_vprint(mtl->logger, stream, msg, vargs);
    258     ASSERT(res == RES_OK);
    259   }
    260 }
    261 
    262 static INLINE void
    263 log_err(const struct aw_mtl* mtl, const char* msg, ...)
    264 {
    265   va_list vargs_list;
    266   ASSERT(mtl && msg);
    267 
    268   va_start(vargs_list, msg);
    269   log_msg(mtl, LOG_ERROR, msg, vargs_list);
    270   va_end(vargs_list);
    271 }
    272 
    273 static INLINE void
    274 log_warn(const struct aw_mtl* mtl, const char* msg, ...)
    275 {
    276   va_list vargs_list;
    277   ASSERT(mtl && msg);
    278 
    279   va_start(vargs_list, msg);
    280   log_msg(mtl, LOG_WARNING, msg, vargs_list);
    281   va_end(vargs_list);
    282 }
    283 
    284 static res_T
    285 parse_newmtl(struct aw_mtl* mtl, char** tk_ctx)
    286 {
    287   struct material* newmtl = NULL;
    288   char* tk = NULL;
    289   size_t nmtls;
    290   res_T res = RES_OK;
    291   ASSERT(mtl && tk_ctx);
    292 
    293   tk = strtok_r(NULL, " \t", tk_ctx); /* Blanks are prohibited in mtl name */
    294   if(!tk) {
    295     res = RES_BAD_ARG;
    296     goto error;
    297   }
    298 
    299   nmtls = darray_material_size_get(&mtl->materials);
    300 
    301   /* Allocate the new material */
    302   res = darray_material_resize(&mtl->materials, nmtls + 1);
    303   if(res != RES_OK) goto error;
    304 
    305   /* Fetch the new material */
    306   newmtl = darray_material_data_get(&mtl->materials) + nmtls;
    307 
    308   /* Setup the material name */
    309   res = str_set(&newmtl->name, tk);
    310   if(res != RES_OK) goto error;
    311 
    312   /* Set the new material as the material to parse */
    313   mtl->newmtl = newmtl;
    314 
    315 exit:
    316   return RES_OK;
    317 error:
    318   if(newmtl) darray_material_pop_back(&mtl->materials);
    319   goto exit;
    320 }
    321 
    322 static res_T
    323 parse_color(struct aw_mtl* mtl, struct aw_color* col, char** tk_ctx)
    324 {
    325   char* tk = NULL;
    326   res_T res = RES_OK;
    327   ASSERT(mtl && col && tk_ctx);
    328 
    329   tk = strtok_r(NULL, " \t", tk_ctx);
    330   if(!tk) {
    331     res = RES_BAD_ARG;
    332     goto error;
    333   }
    334 
    335   if(!strcmp(tk, "spectral")) {
    336     log_err(mtl, "spectral colors are not supported\n");
    337     res = RES_BAD_ARG;
    338     goto error;
    339   }
    340 
    341   if(strcmp(tk, "xyz")) {
    342     col->color_space = AW_COLOR_RGB;
    343   } else {
    344     col->color_space = AW_COLOR_XYZ;
    345     tk = strtok_r(NULL, " \t", tk_ctx);
    346     if(!tk) {
    347       res = RES_BAD_ARG;
    348       goto error;
    349     }
    350   }
    351 
    352   res = cstr_to_double(tk, &col->value[0]);
    353   if(res != RES_OK) goto error;
    354 
    355   /* If only the first component is defined the second and third components are
    356    * assumed to be equal to the first one */
    357   tk = strtok_r(NULL, " \t", tk_ctx);
    358   if(!tk) {
    359     col->value[1] = col->value[0];
    360     col->value[2] = col->value[0];
    361   } else {
    362     res = cstr_to_double(tk, &col->value[1]);
    363     if(res != RES_OK) goto error;
    364 
    365     tk = strtok_r(NULL, " \t", tk_ctx);
    366     if(!tk) { res = RES_BAD_ARG; goto error; }
    367 
    368     res = cstr_to_double(tk, &col->value[2]);
    369     if(res == RES_OK) goto error;
    370   }
    371 
    372 exit:
    373   return res;
    374 error:
    375   goto exit;
    376 }
    377 
    378 static INLINE res_T
    379 parse_size_t
    380   (size_t* s, const size_t range_min, const size_t range_max, char** tk_ctx)
    381 {
    382   unsigned long ul;
    383   char* tk = NULL;
    384   res_T res = RES_OK;
    385   ASSERT(s && tk_ctx && range_min < range_max);
    386 
    387   tk = strtok_r(NULL, "\n", tk_ctx);
    388   if(!tk) return res;
    389 
    390   res = cstr_to_ulong(tk, &ul);
    391   if(res != RES_OK) return res;
    392   if((unsigned long)range_min > ul || (unsigned long)range_max < ul) return res;
    393   *s = ul;
    394   return RES_OK;
    395 }
    396 
    397 static res_T
    398 parse_bool_option
    399   (int* options_mask,
    400    const enum aw_map_flag option,
    401    char** tk_ctx)
    402 {
    403   char* tk;
    404   ASSERT(options_mask && tk_ctx);
    405 
    406   tk = strtok_r(NULL, " \t", tk_ctx);
    407   if(!tk) return RES_BAD_ARG;
    408 
    409   if(!strcmp(tk, "on")) {
    410     *options_mask |= (int)option;
    411   } else if(!strcmp(tk, "off")) {
    412     *options_mask &=  ~((int)option);
    413   } else {
    414     return RES_BAD_ARG;
    415   }
    416   return RES_OK;
    417 }
    418 
    419 static FINLINE res_T
    420 parse_imfchan_option(enum aw_map_channel* channel, char** tk_ctx)
    421 {
    422   char* tk = NULL;
    423   ASSERT(channel && tk_ctx);
    424 
    425   tk = strtok_r(NULL, " \t", tk_ctx);
    426   if(!tk) return RES_BAD_ARG;
    427 
    428   if(!strcmp(tk, "r")) {
    429     *channel = AW_MAP_CHANNEL_RED;
    430   } else if(!strcmp(tk, "g")) {
    431     *channel = AW_MAP_CHANNEL_GREEN;
    432   } else if(!strcmp(tk, "b")) {
    433     *channel = AW_MAP_CHANNEL_BLUE;
    434   } else if(!strcmp(tk, "m")) {
    435     *channel = AW_MAP_CHANNEL_MATTE;
    436   } else if(!strcmp(tk, "l")) {
    437     *channel = AW_MAP_CHANNEL_LUMINANCE;
    438   } else if(!strcmp(tk, "z")) {
    439     *channel = AW_MAP_CHANNEL_DEPTH;
    440   } else {
    441     return RES_BAD_ARG;
    442   }
    443   return RES_OK;
    444 }
    445 
    446 static res_T
    447 parse_map(struct map* map, const enum map_type type, char** tk_ctx)
    448 {
    449   char* tk = NULL;
    450   res_T res = RES_OK;
    451   ASSERT(map && tk_ctx);
    452 
    453   for(;;) {
    454     tk = strtok_r(NULL, " \t", tk_ctx);
    455     if(!tk) {
    456       res = RES_BAD_ARG;
    457       goto error;
    458     }
    459 
    460     if(!strcmp(tk, "-blendu")) {
    461       res = parse_bool_option(&map->options_mask, AW_MAP_BLEND_U, tk_ctx);
    462       if(res != RES_OK) goto error;
    463     } else if(!strcmp(tk, "-blendv")) {
    464       res = parse_bool_option(&map->options_mask, AW_MAP_BLEND_V, tk_ctx);
    465       if(res != RES_OK) goto error;
    466     } else if(!strcmp(tk, "-cc")) {
    467       res = parse_bool_option
    468         (&map->options_mask, AW_MAP_COLOR_CORRECTION, tk_ctx);
    469       if(res != RES_OK) goto error;
    470     } else if(!strcmp(tk, "-clamp")) {
    471       res = parse_bool_option(&map->options_mask, AW_MAP_CLAMP, tk_ctx);
    472       if(res != RES_OK) goto error;
    473     } else if(!strcmp(tk, "-imfchan") && (type & MAP_SCALAR)) {
    474       res = parse_imfchan_option(&map->scalar, tk_ctx);
    475       if(res != RES_OK) goto error;
    476     } else if(!strcmp(tk, "-mm")) { /* Image bias and scale */
    477       res = parse_doubleX
    478         (&map->image_bias, 1, 1, -DBL_MAX, DBL_MAX, 0.f, tk_ctx);
    479       if(res != RES_OK) goto error;
    480       res = parse_doubleX
    481         (&map->image_scale, 1, 1, -DBL_MAX, DBL_MAX, 0.f, tk_ctx);
    482       if(res != RES_OK) goto error;
    483     } else if(!strcmp(tk, "-o")) { /* Texcoord offset */
    484       res = parse_doubleX
    485         (map->texcoord_bias, 1, 3, -DBL_MAX, DBL_MAX, 0.f, tk_ctx);
    486       if(res != RES_OK) goto error;
    487     } else if(!strcmp(tk, "-s")) { /* Texcoord scale */
    488       res = parse_doubleX
    489         (map->texcoord_scale, 1, 3, -DBL_MAX, DBL_MAX, 1.f, tk_ctx);
    490       if(res != RES_OK) goto error;
    491     } else if(!strcmp(tk, "-t")) { /* Texcoord turbulence */
    492       res = parse_doubleX
    493         (map->texcoord_turbulence, 1, 3, -DBL_MAX, DBL_MAX, 0.f, tk_ctx);
    494       if(res != RES_OK) goto error;
    495     } else if(!strcmp(tk, "-texres")) { /* Texture resolution */
    496       res = parse_size_t(&map->resolution, 1, SIZE_MAX, tk_ctx);
    497       if(res != RES_OK) goto error;
    498     } else if(!strcmp(tk, "-bm") && (type == MAP_BUMP)) {/* Bump multiplier */
    499       res = parse_doubleX
    500         (&map->bump_multiplier, 1, 1, -DBL_MAX, DBL_MAX, 0.f, tk_ctx);
    501       if(res != RES_OK) goto error;
    502     } else { /* Map filename */
    503       res = str_set(&map->filename, tk);
    504       if(res != RES_OK) goto error;
    505       tk = strtok_r(NULL, "", tk_ctx);
    506       if(tk) {
    507         res = str_append_char(&map->filename, ' ');
    508         if(res != RES_OK) goto error;
    509         res = str_append(&map->filename, strtok_r(NULL, "", tk_ctx));
    510         if(res != RES_OK) goto error;
    511       }
    512       break; /* The map is fully parsed */
    513     }
    514   }
    515 
    516 exit:
    517   return res;
    518 error:
    519   goto exit;
    520 }
    521 
    522 static res_T
    523 parse_mtl_line(struct aw_mtl* mtl, struct txtrdr* txtrdr)
    524 {
    525   char* tk = NULL;
    526   char* tk_ctx = NULL;
    527   res_T res = RES_OK;
    528   ASSERT(mtl && txtrdr);
    529 
    530   tk = strtok_r(txtrdr_get_line(txtrdr), " \t", &tk_ctx);
    531   ASSERT(tk); /* A line should exist since it was parsed by txtrdr */
    532 
    533   if(!strcmp(tk, "newmtl")) { /* Material name declaration */
    534     res = parse_newmtl(mtl, &tk_ctx);
    535   } else if(mtl->newmtl == NULL) {
    536     res = RES_BAD_ARG;
    537   } else if(!strcmp(tk, "Ka")) { /* Ambient reflectivity */
    538     res = parse_color(mtl, &mtl->newmtl->ambient, &tk_ctx);
    539   } else if(!strcmp(tk, "Kd")) { /* Diffuse reflectivity */
    540     res = parse_color(mtl, &mtl->newmtl->diffuse, &tk_ctx);
    541   } else if(!strcmp(tk, "Ks")) { /* Specular reflectivity */
    542     res = parse_color(mtl, &mtl->newmtl->specular, &tk_ctx);
    543   } else if(!strcmp(tk, "Tf")) { /* Transimission filter */
    544     res = parse_color(mtl, &mtl->newmtl->transmission, &tk_ctx);
    545   } else if(!strcmp(tk, "Ns")) { /* Specular exponent */
    546     res = parse_doubleX
    547       (&mtl->newmtl->specular_exponent, 1, 1, 0, DBL_MAX, 0, &tk_ctx);
    548   } else if(!strcmp(tk, "Ni")) { /* Refraction index */
    549     res = parse_doubleX
    550       (&mtl->newmtl->refraction_index, 1, 1, 0.001, 10, 0.001, &tk_ctx);
    551   } else if(!strcmp(tk, "illum")) { /* Illumination model */
    552     res = parse_size_t(&mtl->newmtl->illumination_model, 0, 10, &tk_ctx);
    553   } else if(!strcmp(tk, "map_Ka")) { /* Ambient texture */
    554     res = parse_map(&mtl->newmtl->ambient_map, MAP_COMMON, &tk_ctx);
    555   } else if(!strcmp(tk, "map_Kd")) { /* Diffuse texture */
    556     res = parse_map(&mtl->newmtl->diffuse_map, MAP_COMMON, &tk_ctx);
    557   } else if(!strcmp(tk, "map_Ks")) { /* Specular texture */
    558     res = parse_map(&mtl->newmtl->specular_map, MAP_COMMON, &tk_ctx);
    559   } else if(!strcmp(tk, "map_Ns")) { /* Specular exponent texture */
    560     res = parse_map
    561       (&mtl->newmtl->specular_exponent_map, MAP_SCALAR, &tk_ctx);
    562   } else if(!strcmp(tk, "bump") || !strcmp(tk, "map_bump")) { /* Bump map */
    563     res = parse_map(&mtl->newmtl->bump_map, MAP_BUMP, &tk_ctx);
    564   } else {
    565     log_warn(mtl,
    566       "%s:%lu: warning: ignored or malformed directive `%s'\n",
    567       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk);
    568     strtok_r(NULL, "", &tk_ctx); /* Discard remaining text */
    569   }
    570   if(res != RES_OK) {
    571     log_err(mtl, "%s:%lu: parsing failed.\n",
    572       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk);
    573     goto error;
    574   }
    575 
    576   tk = strtok_r(NULL, "", &tk_ctx);
    577   if(tk) {
    578     log_err(mtl, "%s:%lu: unexpected text `%s'.\n",
    579       txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr), tk);
    580     res = RES_BAD_ARG;
    581     goto error;
    582   }
    583 
    584 exit:
    585   return res;
    586 error:
    587   goto exit;
    588 }
    589 
    590 static res_T
    591 load_stream(struct aw_mtl* mtl, FILE* stream, const char* stream_name)
    592 {
    593   struct txtrdr* txtrdr = NULL;
    594   res_T res = RES_OK;
    595   ASSERT(mtl && stream && stream_name);
    596 
    597   res = txtrdr_stream(mtl->allocator, stream, stream_name, '#', &txtrdr);
    598   if(res != RES_OK) {
    599     log_err(mtl, "Could not create the text reader.\n");
    600     goto error;
    601   }
    602 
    603   for(;;) {
    604     res = txtrdr_read_line(txtrdr);
    605     if(res != RES_OK) {
    606       log_err(mtl, "%s: could not read the line `%lu'.\n",
    607         txtrdr_get_name(txtrdr), (unsigned long)txtrdr_get_line_num(txtrdr));
    608       goto error;
    609     }
    610 
    611     if(!txtrdr_get_cline(txtrdr)) break; /* No more parsed line */
    612 
    613     res = parse_mtl_line(mtl, txtrdr);
    614     if(res != RES_OK) goto error;
    615   }
    616 
    617 exit:
    618   if(txtrdr) txtrdr_ref_put(txtrdr);
    619   return res;
    620 error:
    621   AW(mtl_clear(mtl));
    622   goto exit;
    623 }
    624 
    625 static void
    626 mtl_release(ref_T* ref)
    627 {
    628   struct aw_mtl* mtl = CONTAINER_OF(ref, struct aw_mtl, ref);
    629   ASSERT(ref);
    630   darray_material_release(&mtl->materials);
    631   if(mtl->logger == &mtl->logger__) logger_release(&mtl->logger__);
    632   MEM_RM(mtl->allocator, mtl);
    633 }
    634 
    635 /*******************************************************************************
    636  * Exported functions
    637  ******************************************************************************/
    638 res_T
    639 aw_mtl_create
    640   (struct logger* logger,
    641    struct mem_allocator* mem_allocator,
    642    const int verbose,
    643    struct aw_mtl** mtl_out)
    644 {
    645   struct mem_allocator* allocator;
    646   struct aw_mtl* mtl = NULL;
    647   res_T res = RES_OK;
    648 
    649   if(!mtl_out) {
    650     res = RES_BAD_ARG;
    651     goto error;
    652   }
    653   allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
    654   mtl = MEM_CALLOC(allocator, 1, sizeof(struct aw_mtl));
    655   if(!mtl) {
    656     res = RES_MEM_ERR;
    657     goto error;
    658   }
    659   ref_init(&mtl->ref);
    660   mtl->allocator = allocator;
    661   mtl->verbose = verbose;
    662   darray_material_init(allocator, &mtl->materials);
    663 
    664   if(logger) {
    665     mtl->logger = logger;
    666   } else {
    667     res = setup_default_logger(mtl->allocator, &mtl->logger__,
    668       MSG_PREFIX_INFO, MSG_PREFIX_ERROR, MSG_PREFIX_WARNING);
    669     if(res != RES_OK) goto error;
    670     mtl->logger = &mtl->logger__;
    671   }
    672 
    673 exit:
    674   if(mtl_out) *mtl_out = mtl;
    675   return res;
    676 error:
    677   if(mtl) {
    678     AW(mtl_ref_put(mtl));
    679     mtl = NULL;
    680   }
    681   goto exit;
    682 }
    683 
    684 res_T
    685 aw_mtl_ref_get(struct aw_mtl* mtl)
    686 {
    687   if(!mtl) return RES_BAD_ARG;
    688   ref_get(&mtl->ref);
    689   return RES_OK;
    690 }
    691 
    692 res_T
    693 aw_mtl_ref_put(struct aw_mtl* mtl)
    694 {
    695   if(!mtl) return RES_BAD_ARG;
    696   ref_put(&mtl->ref, mtl_release);
    697   return RES_OK;
    698 }
    699 
    700 res_T
    701 aw_mtl_load(struct aw_mtl* mtl, const char* filename)
    702 {
    703   FILE* file = NULL;
    704   res_T res = RES_OK;
    705 
    706   if(!mtl || !filename) {
    707     res = RES_BAD_ARG;
    708     goto error;
    709   }
    710 
    711   file = fopen(filename, "r");
    712   if(!file) {
    713     log_err(mtl, "Error opening `%s'\n", filename);
    714     res = RES_IO_ERR;
    715     goto error;
    716   }
    717 
    718   res = load_stream(mtl, file, filename);
    719   if(res != RES_OK) goto error;
    720 
    721 exit:
    722   if(file) fclose(file);
    723   return res;
    724 error:
    725   goto exit;
    726 }
    727 
    728 res_T
    729 aw_mtl_load_stream(struct aw_mtl* mtl, FILE* stream, const char* stream_name)
    730 {
    731   res_T res = RES_OK;
    732 
    733   if(!mtl || !stream) {
    734     res = RES_BAD_ARG;
    735     goto error;
    736   }
    737 
    738   res = load_stream(mtl, stream, stream_name ? stream_name : "stream");
    739   if(res != RES_OK) goto error;
    740 
    741 exit:
    742   return res;
    743 error:
    744   goto exit;
    745 }
    746 
    747 res_T
    748 aw_mtl_clear(struct aw_mtl* mtl)
    749 {
    750   if(!mtl) return RES_BAD_ARG;
    751   darray_material_clear(&mtl->materials);
    752   mtl->newmtl = NULL;
    753   return RES_OK;
    754 }
    755 
    756 res_T
    757 aw_mtl_purge(struct aw_mtl* mtl)
    758 {
    759   if(!mtl) return RES_BAD_ARG;
    760   darray_material_purge(&mtl->materials);
    761   mtl->newmtl = NULL;
    762   return RES_OK;
    763 }
    764 
    765 res_T
    766 aw_mtl_get_materials_count(struct aw_mtl* mtl, size_t* nmtls)
    767 {
    768   if(!mtl || !nmtls)
    769     return RES_BAD_ARG;
    770   *nmtls = darray_material_size_get(&mtl->materials);
    771   return RES_OK;
    772 }
    773 
    774 res_T
    775 aw_mtl_get_material
    776   (struct aw_mtl* mtl,
    777    const size_t imaterial,
    778    struct aw_material* material)
    779 {
    780   const struct material* mat = NULL;
    781 
    782   if(!mtl || !material || imaterial>=darray_material_size_get(&mtl->materials))
    783     return RES_BAD_ARG;
    784 
    785   mat = darray_material_cdata_get(&mtl->materials) + imaterial;
    786   material->name = str_cget(&mat->name);
    787   material->ambient = mat->ambient;
    788   material->diffuse = mat->diffuse;
    789   material->specular = mat->specular;
    790   material->transmission = mat->transmission;
    791   material->specular_exponent = mat->specular_exponent;
    792   material->refraction_index = mat->refraction_index;
    793   material->illumination_model = mat->illumination_model;
    794   map_to_aw_map(&mat->ambient_map, &material->ambient_map);
    795   map_to_aw_map(&mat->diffuse_map, &material->diffuse_map);
    796   map_to_aw_map(&mat->specular_map, &material->specular_map);
    797   map_to_aw_map(&mat->specular_exponent_map, &material->specular_exponent_map);
    798   map_to_aw_map(&mat->bump_map, &material->bump_map);
    799   return RES_OK;
    800 }
    801 
    802 #ifdef COMPILER_CL
    803   #pragma warning(pop)
    804 #endif
    805