rply-1.1.4/000755 000765 000024 00000000000 12565712037 012745 5ustar00diegostaff000000 000000 rply-1.1.4/etc/000755 000765 000024 00000000000 12565712037 013520 5ustar00diegostaff000000 000000 rply-1.1.4/LICENSE000644 000765 000024 00000002066 12565712037 013756 0ustar00diegostaff000000 000000 RPly 1.1.4 license Copyright 2003-2015 Diego Nehab. 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. rply-1.1.4/manual/000755 000765 000024 00000000000 12565712037 014222 5ustar00diegostaff000000 000000 rply-1.1.4/rply.c000644 000765 000024 00000154324 12565712037 014110 0ustar00diegostaff000000 000000 /* ---------------------------------------------------------------------- * RPly library, read/write PLY files * Diego Nehab, IMPA * http://www.impa.br/~diego/software/rply * * This library is distributed under the MIT License. See notice * at the end of this file. * ---------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include "rply.h" #include "rplyfile.h" /* ---------------------------------------------------------------------- * Make sure we get our integer types right * ---------------------------------------------------------------------- */ #if defined(_MSC_VER) && (_MSC_VER < 1600) /* C99 stdint.h only supported in MSVC++ 10.0 and up */ typedef __int8 t_ply_int8; typedef __int16 t_ply_int16; typedef __int32 t_ply_int32; typedef unsigned __int8 t_ply_uint8; typedef unsigned __int16 t_ply_uint16; typedef unsigned __int32 t_ply_uint32; #define PLY_INT8_MAX (127) #define PLY_INT8_MIN (-PLY_INT8_MAX-1) #define PLY_INT16_MAX (32767) #define PLY_INT16_MIN (-PLY_INT16_MAX-1) #define PLY_INT32_MAX (2147483647) #define PLY_INT32_MIN (-PLY_INT32_MAX-1) #define PLY_UINT8_MAX (255) #define PLY_UINT16_MAX (65535) #define PLY_UINT32_MAX (4294967295) #else #include typedef int8_t t_ply_int8; typedef int16_t t_ply_int16; typedef int32_t t_ply_int32; typedef uint8_t t_ply_uint8; typedef uint16_t t_ply_uint16; typedef uint32_t t_ply_uint32; #define PLY_INT8_MIN INT8_MIN #define PLY_INT8_MAX INT8_MAX #define PLY_INT16_MIN INT16_MIN #define PLY_INT16_MAX INT16_MAX #define PLY_INT32_MIN INT32_MIN #define PLY_INT32_MAX INT32_MAX #define PLY_UINT8_MAX UINT8_MAX #define PLY_UINT16_MAX UINT16_MAX #define PLY_UINT32_MAX UINT32_MAX #endif /* ---------------------------------------------------------------------- * Constants * ---------------------------------------------------------------------- */ #define WORDSIZE 256 #define LINESIZE 1024 #define BUFFERSIZE (8*1024) typedef enum e_ply_io_mode_ { PLY_READ, PLY_WRITE } e_ply_io_mode; static const char *const ply_storage_mode_list[] = { "binary_big_endian", "binary_little_endian", "ascii", NULL }; /* order matches e_ply_storage_mode enum */ static const char *const ply_type_list[] = { "int8", "uint8", "int16", "uint16", "int32", "uint32", "float32", "float64", "char", "uchar", "short", "ushort", "int", "uint", "float", "double", "list", NULL }; /* order matches e_ply_type enum */ /* ---------------------------------------------------------------------- * Property reading callback argument * * element: name of element being processed * property: name of property being processed * nelements: number of elements of this kind in file * instance_index: index current element of this kind being processed * length: number of values in current list (or 1 for scalars) * value_index: index of current value int this list (or 0 for scalars) * value: value of property * pdata/idata: user data defined with ply_set_cb * * Returns handle to PLY file if succesful, NULL otherwise. * ---------------------------------------------------------------------- */ typedef struct t_ply_argument_ { p_ply_element element; long instance_index; p_ply_property property; long length, value_index; double value; void *pdata; long idata; } t_ply_argument; /* ---------------------------------------------------------------------- * Property information * * name: name of this property * type: type of this property (list or type of scalar value) * length_type, value_type: type of list property count and values * read_cb: function to be called when this property is called * * Returns 1 if should continue processing file, 0 if should abort. * ---------------------------------------------------------------------- */ typedef struct t_ply_property_ { char name[WORDSIZE]; e_ply_type type, value_type, length_type; p_ply_read_cb read_cb; void *pdata; long idata; } t_ply_property; /* ---------------------------------------------------------------------- * Element information * * name: name of this property * ninstances: number of elements of this type in file * property: property descriptions for this element * nproperty: number of properties in this element * * Returns 1 if should continue processing file, 0 if should abort. * ---------------------------------------------------------------------- */ typedef struct t_ply_element_ { char name[WORDSIZE]; long ninstances; p_ply_property property; long nproperties; } t_ply_element; /* ---------------------------------------------------------------------- * Input/output driver * * Depending on file mode, different functions are used to read/write * property fields. The drivers make it transparent to read/write in ascii, * big endian or little endian cases. * ---------------------------------------------------------------------- */ typedef int (*p_ply_ihandler)(p_ply ply, double *value); typedef int (*p_ply_ichunk)(p_ply ply, void *anydata, size_t size); typedef struct t_ply_idriver_ { p_ply_ihandler ihandler[16]; p_ply_ichunk ichunk; const char *name; } t_ply_idriver; typedef t_ply_idriver *p_ply_idriver; typedef int (*p_ply_ohandler)(p_ply ply, double value); typedef int (*p_ply_ochunk)(p_ply ply, void *anydata, size_t size); typedef struct t_ply_odriver_ { p_ply_ohandler ohandler[16]; p_ply_ochunk ochunk; const char *name; } t_ply_odriver; typedef t_ply_odriver *p_ply_odriver; /* ---------------------------------------------------------------------- * Ply file handle. * * io_mode: read or write (from e_ply_io_mode) * storage_mode: mode of file associated with handle (from e_ply_storage_mode) * element: elements description for this file * nelement: number of different elements in file * comment: comments for this file * ncomments: number of comments in file * obj_info: obj_info items for this file * nobj_infos: number of obj_info items in file * fp: file pointer associated with ply file * rn: skip extra char after end_header? * buffer: last word/chunck of data read from ply file * buffer_first, buffer_last: interval of untouched good data in buffer * buffer_token: start of parsed token (line or word) in buffer * idriver, odriver: input driver used to get property fields from file * argument: storage space for callback arguments * welement, wproperty: element/property type being written * winstance_index: index of instance of current element being written * wvalue_index: index of list property value being written * wlength: number of values in list property being written * error_cb: error callback * pdata/idata: user data defined with ply_open/ply_create * ---------------------------------------------------------------------- */ typedef struct t_ply_ { e_ply_io_mode io_mode; e_ply_storage_mode storage_mode; p_ply_element element; long nelements; char *comment; long ncomments; char *obj_info; long nobj_infos; FILE *fp; int own_fp; int rn; char buffer[BUFFERSIZE]; size_t buffer_first, buffer_token, buffer_last; p_ply_idriver idriver; p_ply_odriver odriver; t_ply_argument argument; long welement, wproperty; long winstance_index, wvalue_index, wlength; p_ply_error_cb error_cb; void *pdata; long idata; } t_ply; /* ---------------------------------------------------------------------- * I/O functions and drivers * ---------------------------------------------------------------------- */ static t_ply_idriver ply_idriver_ascii; static t_ply_idriver ply_idriver_binary; static t_ply_idriver ply_idriver_binary_reverse; static t_ply_odriver ply_odriver_ascii; static t_ply_odriver ply_odriver_binary; static t_ply_odriver ply_odriver_binary_reverse; static int ply_read_word(p_ply ply); static int ply_check_word(p_ply ply); static void ply_finish_word(p_ply ply, size_t size); static int ply_read_line(p_ply ply); static int ply_check_line(p_ply ply); static int ply_read_chunk(p_ply ply, void *anybuffer, size_t size); static int ply_read_chunk_reverse(p_ply ply, void *anybuffer, size_t size); static int ply_write_chunk(p_ply ply, void *anybuffer, size_t size); static int ply_write_chunk_reverse(p_ply ply, void *anybuffer, size_t size); static void ply_reverse(void *anydata, size_t size); /* ---------------------------------------------------------------------- * String functions * ---------------------------------------------------------------------- */ static int ply_find_string(const char *item, const char* const list[]); static p_ply_element ply_find_element(p_ply ply, const char *name); static p_ply_property ply_find_property(p_ply_element element, const char *name); /* ---------------------------------------------------------------------- * Header parsing * ---------------------------------------------------------------------- */ static int ply_read_header_magic(p_ply ply); static int ply_read_header_format(p_ply ply); static int ply_read_header_comment(p_ply ply); static int ply_read_header_obj_info(p_ply ply); static int ply_read_header_property(p_ply ply); static int ply_read_header_element(p_ply ply); /* ---------------------------------------------------------------------- * Error handling * ---------------------------------------------------------------------- */ static void ply_error_cb(p_ply ply, const char *message); static void ply_ferror(p_ply ply, const char *fmt, ...); /* ---------------------------------------------------------------------- * Memory allocation and initialization * ---------------------------------------------------------------------- */ static void ply_init(p_ply ply); static void ply_element_init(p_ply_element element); static void ply_property_init(p_ply_property property); static p_ply ply_alloc(void); static p_ply_element ply_grow_element(p_ply ply); static p_ply_property ply_grow_property(p_ply ply, p_ply_element element); static void *ply_grow_array(p_ply ply, void **pointer, long *nmemb, long size); /* ---------------------------------------------------------------------- * Special functions * ---------------------------------------------------------------------- */ static e_ply_storage_mode ply_arch_endian(void); static int ply_type_check(void); /* ---------------------------------------------------------------------- * Auxiliary read functions * ---------------------------------------------------------------------- */ static int ply_read_element(p_ply ply, p_ply_element element, p_ply_argument argument); static int ply_read_property(p_ply ply, p_ply_element element, p_ply_property property, p_ply_argument argument); static int ply_read_list_property(p_ply ply, p_ply_element element, p_ply_property property, p_ply_argument argument); static int ply_read_scalar_property(p_ply ply, p_ply_element element, p_ply_property property, p_ply_argument argument); /* ---------------------------------------------------------------------- * Buffer support functions * ---------------------------------------------------------------------- */ /* pointers to tokenized word and line in buffer */ #define BWORD(p) (p->buffer + p->buffer_token) #define BLINE(p) (p->buffer + p->buffer_token) /* pointer to start of untouched bytes in buffer */ #define BFIRST(p) (p->buffer + p->buffer_first) /* number of bytes untouched in buffer */ #define BSIZE(p) (p->buffer_last - p->buffer_first) /* consumes data from buffer */ #define BSKIP(p, s) (p->buffer_first += s) /* refills the buffer */ static int BREFILL(p_ply ply) { /* move untouched data to beginning of buffer */ size_t size = BSIZE(ply); memmove(ply->buffer, BFIRST(ply), size); ply->buffer_last = size; ply->buffer_first = ply->buffer_token = 0; /* fill remaining with new data */ size = fread(ply->buffer+size, 1, BUFFERSIZE-size-1, ply->fp); /* increase size to account for new data */ ply->buffer_last += size; /* place sentinel so we can use str* functions with buffer */ ply->buffer[ply->buffer_last] = '\0'; /* check if read failed */ return size > 0; } /* We don't care about end-of-line, generally, because we * separate words by any white-space character. * Unfortunately, in binary mode, right after 'end_header', * we have to know *exactly* how many characters to skip */ /* We use the end-of-line marker after the 'ply' magic * number to figure out what to do */ static int ply_read_header_magic(p_ply ply) { char *magic = ply->buffer; if (!BREFILL(ply)) { ply->error_cb(ply, "Unable to read magic number from file"); return 0; } /* check if it is ply */ if (magic[0] != 'p' || magic[1] != 'l' || magic[2] != 'y' || !isspace(magic[3])) { ply->error_cb(ply, "Wrong magic number. Expected 'ply'"); return 0; } /* figure out if we have to skip the extra character * after header when we reach the binary part of file */ ply->rn = magic[3] == '\r' && magic[4] == '\n'; BSKIP(ply, 3); return 1; } /* ---------------------------------------------------------------------- * Exported functions * ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- * Read support functions * ---------------------------------------------------------------------- */ p_ply ply_open(const char *name, p_ply_error_cb error_cb, long idata, void *pdata) { FILE *fp; p_ply ply; if (error_cb == NULL) error_cb = ply_error_cb; assert(name); fp = fopen(name, "rb"); if (!fp) { error_cb(NULL, "Unable to open file"); return NULL; } ply = ply_open_from_file(fp, error_cb, idata, pdata); if (ply) ply->own_fp = 1; else fclose(fp); return ply; } p_ply ply_open_from_file(FILE *fp, p_ply_error_cb error_cb, long idata, void *pdata) { p_ply ply; if (error_cb == NULL) error_cb = ply_error_cb; assert(fp); if (!ply_type_check()) { error_cb(ply, "Incompatible type system"); return NULL; } ply = ply_alloc(); if (!ply) { error_cb(NULL, "Out of memory"); return NULL; } ply->idata = idata; ply->pdata = pdata; ply->io_mode = PLY_READ; ply->error_cb = error_cb; ply->fp = fp; ply->own_fp = 0; return ply; } int ply_read_header(p_ply ply) { assert(ply && ply->fp && ply->io_mode == PLY_READ); if (!ply_read_header_magic(ply)) return 0; if (!ply_read_word(ply)) return 0; /* parse file format */ if (!ply_read_header_format(ply)) { ply_ferror(ply, "Invalid file format"); return 0; } /* parse elements, comments or obj_infos until the end of header */ while (strcmp(BWORD(ply), "end_header")) { if (!ply_read_header_comment(ply) && !ply_read_header_element(ply) && !ply_read_header_obj_info(ply)) { ply_ferror(ply, "Unexpected token '%s'", BWORD(ply)); return 0; } } /* skip extra character? */ if (ply->rn) { if (BSIZE(ply) < 1 && !BREFILL(ply)) { ply_ferror(ply, "Unexpected end of file"); return 0; } BSKIP(ply, 1); } return 1; } long ply_set_read_cb(p_ply ply, const char *element_name, const char* property_name, p_ply_read_cb read_cb, void *pdata, long idata) { p_ply_element element = NULL; p_ply_property property = NULL; assert(ply && element_name && property_name); element = ply_find_element(ply, element_name); if (!element) return 0; property = ply_find_property(element, property_name); if (!property) return 0; property->read_cb = read_cb; property->pdata = pdata; property->idata = idata; return (int) element->ninstances; } int ply_read(p_ply ply) { long i; p_ply_argument argument; assert(ply && ply->fp && ply->io_mode == PLY_READ); argument = &ply->argument; /* for each element type */ for (i = 0; i < ply->nelements; i++) { p_ply_element element = &ply->element[i]; argument->element = element; if (!ply_read_element(ply, element, argument)) return 0; } return 1; } /* ---------------------------------------------------------------------- * Write support functions * ---------------------------------------------------------------------- */ p_ply ply_create(const char *name, e_ply_storage_mode storage_mode, p_ply_error_cb error_cb, long idata, void *pdata) { p_ply ply = NULL; FILE *fp = NULL; assert(name && storage_mode <= PLY_DEFAULT); if (error_cb == NULL) error_cb = ply_error_cb; fp = fopen(name, "wb"); if (!fp) { error_cb(ply, "Unable to create file"); return NULL; } ply = ply_create_to_file(fp, storage_mode, error_cb, idata, pdata); if (ply) ply->own_fp = 1; else fclose(fp); return ply; } p_ply ply_create_to_file(FILE *fp, e_ply_storage_mode storage_mode, p_ply_error_cb error_cb, long idata, void *pdata) { p_ply ply; assert(fp && storage_mode <= PLY_DEFAULT); if (!ply_type_check()) { error_cb(ply, "Incompatible type system"); return NULL; } ply = ply_alloc(); if (!ply) { error_cb(NULL, "Out of memory"); return NULL; } ply->idata = idata; ply->pdata = pdata; ply->io_mode = PLY_WRITE; if (storage_mode == PLY_DEFAULT) storage_mode = ply_arch_endian(); if (storage_mode == PLY_ASCII) ply->odriver = &ply_odriver_ascii; else if (storage_mode == ply_arch_endian()) ply->odriver = &ply_odriver_binary; else ply->odriver = &ply_odriver_binary_reverse; ply->storage_mode = storage_mode; ply->fp = fp; ply->own_fp = 0; ply->error_cb = error_cb; return ply; } int ply_add_element(p_ply ply, const char *name, long ninstances) { p_ply_element element = NULL; assert(ply && ply->fp && ply->io_mode == PLY_WRITE); assert(name && strlen(name) < WORDSIZE && ninstances >= 0); if (strlen(name) >= WORDSIZE || ninstances < 0) { ply_ferror(ply, "Invalid arguments"); return 0; } element = ply_grow_element(ply); if (!element) return 0; strcpy(element->name, name); element->ninstances = ninstances; return 1; } int ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type) { p_ply_element element = NULL; p_ply_property property = NULL; assert(ply && ply->fp && ply->io_mode == PLY_WRITE); assert(name && strlen(name) < WORDSIZE); assert(type < PLY_LIST); if (strlen(name) >= WORDSIZE || type >= PLY_LIST) { ply_ferror(ply, "Invalid arguments"); return 0; } element = &ply->element[ply->nelements-1]; property = ply_grow_property(ply, element); if (!property) return 0; strcpy(property->name, name); property->type = type; return 1; } int ply_add_list_property(p_ply ply, const char *name, e_ply_type length_type, e_ply_type value_type) { p_ply_element element = NULL; p_ply_property property = NULL; assert(ply && ply->fp && ply->io_mode == PLY_WRITE); assert(name && strlen(name) < WORDSIZE); if (strlen(name) >= WORDSIZE) { ply_ferror(ply, "Invalid arguments"); return 0; } assert(length_type < PLY_LIST); assert(value_type < PLY_LIST); if (length_type >= PLY_LIST || value_type >= PLY_LIST) { ply_ferror(ply, "Invalid arguments"); return 0; } element = &ply->element[ply->nelements-1]; property = ply_grow_property(ply, element); if (!property) return 0; strcpy(property->name, name); property->type = PLY_LIST; property->length_type = length_type; property->value_type = value_type; return 1; } int ply_add_property(p_ply ply, const char *name, e_ply_type type, e_ply_type length_type, e_ply_type value_type) { if (type == PLY_LIST) return ply_add_list_property(ply, name, length_type, value_type); else return ply_add_scalar_property(ply, name, type); } int ply_add_comment(p_ply ply, const char *comment) { char *new_comment = NULL; assert(ply && comment && strlen(comment) < LINESIZE); if (!comment || strlen(comment) >= LINESIZE) { ply_ferror(ply, "Invalid arguments"); return 0; } new_comment = (char *) ply_grow_array(ply, (void **) &ply->comment, &ply->ncomments, LINESIZE); if (!new_comment) return 0; strcpy(new_comment, comment); return 1; } int ply_add_obj_info(p_ply ply, const char *obj_info) { char *new_obj_info = NULL; assert(ply && obj_info && strlen(obj_info) < LINESIZE); if (!obj_info || strlen(obj_info) >= LINESIZE) { ply_ferror(ply, "Invalid arguments"); return 0; } new_obj_info = (char *) ply_grow_array(ply, (void **) &ply->obj_info, &ply->nobj_infos, LINESIZE); if (!new_obj_info) return 0; strcpy(new_obj_info, obj_info); return 1; } int ply_write_header(p_ply ply) { long i, j; assert(ply && ply->fp && ply->io_mode == PLY_WRITE); assert(ply->element || ply->nelements == 0); assert(!ply->element || ply->nelements > 0); if (fprintf(ply->fp, "ply\nformat %s 1.0\n", ply_storage_mode_list[ply->storage_mode]) <= 0) goto error; for (i = 0; i < ply->ncomments; i++) if (fprintf(ply->fp, "comment %s\n", ply->comment + LINESIZE*i) <= 0) goto error; for (i = 0; i < ply->nobj_infos; i++) if (fprintf(ply->fp, "obj_info %s\n", ply->obj_info + LINESIZE*i) <= 0) goto error; for (i = 0; i < ply->nelements; i++) { p_ply_element element = &ply->element[i]; assert(element->property || element->nproperties == 0); assert(!element->property || element->nproperties > 0); if (fprintf(ply->fp, "element %s %ld\n", element->name, element->ninstances) <= 0) goto error; for (j = 0; j < element->nproperties; j++) { p_ply_property property = &element->property[j]; if (property->type == PLY_LIST) { if (fprintf(ply->fp, "property list %s %s %s\n", ply_type_list[property->length_type], ply_type_list[property->value_type], property->name) <= 0) goto error; } else { if (fprintf(ply->fp, "property %s %s\n", ply_type_list[property->type], property->name) <= 0) goto error; } } } return fprintf(ply->fp, "end_header\n") > 0; error: ply_ferror(ply, "Error writing to file"); return 0; } int ply_write(p_ply ply, double value) { p_ply_element element = NULL; p_ply_property property = NULL; int type = -1; int breakafter = 0; int spaceafter = 1; if (ply->welement > ply->nelements) return 0; element = &ply->element[ply->welement]; if (ply->wproperty > element->nproperties) return 0; property = &element->property[ply->wproperty]; if (property->type == PLY_LIST) { if (ply->wvalue_index == 0) { type = property->length_type; ply->wlength = (long) value; } else type = property->value_type; } else { type = property->type; ply->wlength = 0; } if (!ply->odriver->ohandler[type](ply, value)) { ply_ferror(ply, "Failed writing %s of %s %d (%s: %s)", property->name, element->name, ply->winstance_index, ply->odriver->name, ply_type_list[type]); return 0; } ply->wvalue_index++; if (ply->wvalue_index > ply->wlength) { ply->wvalue_index = 0; ply->wproperty++; } if (ply->wproperty >= element->nproperties) { ply->wproperty = 0; ply->winstance_index++; breakafter = 1; spaceafter = 0; } if (ply->winstance_index >= element->ninstances) { ply->winstance_index = 0; do { ply->welement++; element = &ply->element[ply->welement]; } while (ply->welement < ply->nelements && !element->ninstances); } if (ply->storage_mode == PLY_ASCII) { return (!spaceafter || putc(' ', ply->fp) > 0) && (!breakafter || putc('\n', ply->fp) > 0); } else { return 1; } } int ply_close(p_ply ply) { long i; assert(ply && ply->fp); assert(ply->element || ply->nelements == 0); assert(!ply->element || ply->nelements > 0); /* write last chunk to file */ if (ply->io_mode == PLY_WRITE && fwrite(ply->buffer, 1, ply->buffer_last, ply->fp) < ply->buffer_last) { ply_ferror(ply, "Error closing up"); return 0; } if (ply->own_fp) fclose(ply->fp); /* free all memory used by handle */ if (ply->element) { for (i = 0; i < ply->nelements; i++) { p_ply_element element = &ply->element[i]; if (element->property) free(element->property); } free(ply->element); } if (ply->obj_info) free(ply->obj_info); if (ply->comment) free(ply->comment); free(ply); return 1; } /* ---------------------------------------------------------------------- * Query support functions * ---------------------------------------------------------------------- */ p_ply_element ply_get_next_element(p_ply ply, p_ply_element last) { assert(ply); if (!last) return ply->element; last++; if (last < ply->element + ply->nelements) return last; else return NULL; } int ply_get_element_info(p_ply_element element, const char** name, long *ninstances) { assert(element); if (name) *name = element->name; if (ninstances) *ninstances = (long) element->ninstances; return 1; } p_ply_property ply_get_next_property(p_ply_element element, p_ply_property last) { assert(element); if (!last) return element->property; last++; if (last < element->property + element->nproperties) return last; else return NULL; } int ply_get_property_info(p_ply_property property, const char** name, e_ply_type *type, e_ply_type *length_type, e_ply_type *value_type) { assert(property); if (name) *name = property->name; if (type) *type = property->type; if (length_type) *length_type = property->length_type; if (value_type) *value_type = property->value_type; return 1; } const char *ply_get_next_comment(p_ply ply, const char *last) { assert(ply); if (!last) return ply->comment; last += LINESIZE; if (last < ply->comment + LINESIZE*ply->ncomments) return last; else return NULL; } const char *ply_get_next_obj_info(p_ply ply, const char *last) { assert(ply); if (!last) return ply->obj_info; last += LINESIZE; if (last < ply->obj_info + LINESIZE*ply->nobj_infos) return last; else return NULL; } /* ---------------------------------------------------------------------- * Callback argument support functions * ---------------------------------------------------------------------- */ int ply_get_argument_element(p_ply_argument argument, p_ply_element *element, long *instance_index) { assert(argument); if (!argument) return 0; if (element) *element = argument->element; if (instance_index) *instance_index = argument->instance_index; return 1; } int ply_get_argument_property(p_ply_argument argument, p_ply_property *property, long *length, long *value_index) { assert(argument); if (!argument) return 0; if (property) *property = argument->property; if (length) *length = argument->length; if (value_index) *value_index = argument->value_index; return 1; } int ply_get_argument_user_data(p_ply_argument argument, void **pdata, long *idata) { assert(argument); if (!argument) return 0; if (pdata) *pdata = argument->pdata; if (idata) *idata = argument->idata; return 1; } double ply_get_argument_value(p_ply_argument argument) { assert(argument); if (!argument) return 0.0; return argument->value; } int ply_get_ply_user_data(p_ply ply, void **pdata, long *idata) { assert(ply); if (!ply) return 0; if (pdata) *pdata = ply->pdata; if (idata) *idata = ply->idata; return 1; } /* ---------------------------------------------------------------------- * Internal functions * ---------------------------------------------------------------------- */ static int ply_read_list_property(p_ply ply, p_ply_element element, p_ply_property property, p_ply_argument argument) { int l; p_ply_read_cb read_cb = property->read_cb; p_ply_ihandler *driver = ply->idriver->ihandler; /* get list length */ p_ply_ihandler handler = driver[property->length_type]; double length; if (!handler(ply, &length)) { ply_ferror(ply, "Error reading '%s' of '%s' number %d", property->name, element->name, argument->instance_index); return 0; } /* invoke callback to pass length in value field */ argument->length = (long) length; argument->value_index = -1; argument->value = length; if (read_cb && !read_cb(argument)) { ply_ferror(ply, "Aborted by user"); return 0; } /* read list values */ handler = driver[property->value_type]; /* for each value in list */ for (l = 0; l < (long) length; l++) { /* read value from file */ argument->value_index = l; if (!handler(ply, &argument->value)) { ply_ferror(ply, "Error reading value number %d of '%s' of " "'%s' number %d", l+1, property->name, element->name, argument->instance_index); return 0; } /* invoke callback to pass value */ if (read_cb && !read_cb(argument)) { ply_ferror(ply, "Aborted by user"); return 0; } } return 1; } static int ply_read_scalar_property(p_ply ply, p_ply_element element, p_ply_property property, p_ply_argument argument) { p_ply_read_cb read_cb = property->read_cb; p_ply_ihandler *driver = ply->idriver->ihandler; p_ply_ihandler handler = driver[property->type]; argument->length = 1; argument->value_index = 0; if (!handler(ply, &argument->value)) { ply_ferror(ply, "Error reading '%s' of '%s' number %d", property->name, element->name, argument->instance_index); return 0; } if (read_cb && !read_cb(argument)) { ply_ferror(ply, "Aborted by user"); return 0; } return 1; } static int ply_read_property(p_ply ply, p_ply_element element, p_ply_property property, p_ply_argument argument) { if (property->type == PLY_LIST) return ply_read_list_property(ply, element, property, argument); else return ply_read_scalar_property(ply, element, property, argument); } static int ply_read_element(p_ply ply, p_ply_element element, p_ply_argument argument) { long j, k; /* for each element of this type */ for (j = 0; j < element->ninstances; j++) { argument->instance_index = j; /* for each property */ for (k = 0; k < element->nproperties; k++) { p_ply_property property = &element->property[k]; argument->property = property; argument->pdata = property->pdata; argument->idata = property->idata; if (!ply_read_property(ply, element, property, argument)) return 0; } } return 1; } static int ply_find_string(const char *item, const char* const list[]) { int i; assert(item && list); for (i = 0; list[i]; i++) if (!strcmp(list[i], item)) return i; return -1; } static p_ply_element ply_find_element(p_ply ply, const char *name) { p_ply_element element; int i, nelements; assert(ply && name); element = ply->element; nelements = ply->nelements; assert(element || nelements == 0); assert(!element || nelements > 0); for (i = 0; i < nelements; i++) if (!strcmp(element[i].name, name)) return &element[i]; return NULL; } static p_ply_property ply_find_property(p_ply_element element, const char *name) { p_ply_property property; int i, nproperties; assert(element && name); property = element->property; nproperties = element->nproperties; assert(property || nproperties == 0); assert(!property || nproperties > 0); for (i = 0; i < nproperties; i++) if (!strcmp(property[i].name, name)) return &property[i]; return NULL; } static int ply_check_word(p_ply ply) { size_t size = strlen(BWORD(ply)); if (size >= WORDSIZE) { ply_ferror(ply, "Word too long"); return 0; } else if (size == 0) { ply_ferror(ply, "Unexpected end of file"); return 0; } return 1; } static int ply_read_word(p_ply ply) { size_t t = 0; assert(ply && ply->fp && ply->io_mode == PLY_READ); /* skip leading blanks */ while (1) { t = strspn(BFIRST(ply), " \n\r\t"); /* check if all buffer was made of blanks */ if (t >= BSIZE(ply)) { if (!BREFILL(ply)) { ply_ferror(ply, "Unexpected end of file"); return 0; } } else break; } BSKIP(ply, t); /* look for a space after the current word */ t = strcspn(BFIRST(ply), " \n\r\t"); /* if we didn't reach the end of the buffer, we are done */ if (t < BSIZE(ply)) { ply_finish_word(ply, t); return ply_check_word(ply); } /* otherwise, try to refill buffer */ if (!BREFILL(ply)) { /* if we reached the end of file, try to do with what we have */ ply_finish_word(ply, t); return ply_check_word(ply); /* ply_ferror(ply, "Unexpected end of file"); */ /* return 0; */ } /* keep looking from where we left */ t += strcspn(BFIRST(ply) + t, " \n\r\t"); /* check if the token is too large for our buffer */ if (t >= BSIZE(ply)) { ply_ferror(ply, "Token too large"); return 0; } /* we are done */ ply_finish_word(ply, t); return ply_check_word(ply); } static void ply_finish_word(p_ply ply, size_t size) { ply->buffer_token = ply->buffer_first; BSKIP(ply, size); *BFIRST(ply) = '\0'; BSKIP(ply, 1); } static int ply_check_line(p_ply ply) { if (strlen(BLINE(ply)) >= LINESIZE) { ply_ferror(ply, "Line too long"); return 0; } return 1; } static int ply_read_line(p_ply ply) { const char *end = NULL; assert(ply && ply->fp && ply->io_mode == PLY_READ); /* look for a end of line */ end = strchr(BFIRST(ply), '\n'); /* if we didn't reach the end of the buffer, we are done */ if (end) { ply->buffer_token = ply->buffer_first; BSKIP(ply, end - BFIRST(ply)); *BFIRST(ply) = '\0'; BSKIP(ply, 1); return ply_check_line(ply); } else { end = ply->buffer + BSIZE(ply); /* otherwise, try to refill buffer */ if (!BREFILL(ply)) { ply_ferror(ply, "Unexpected end of file"); return 0; } } /* keep looking from where we left */ end = strchr(end, '\n'); /* check if the token is too large for our buffer */ if (!end) { ply_ferror(ply, "Token too large"); return 0; } /* we are done */ ply->buffer_token = ply->buffer_first; BSKIP(ply, end - BFIRST(ply)); *BFIRST(ply) = '\0'; BSKIP(ply, 1); return ply_check_line(ply); } static int ply_read_chunk(p_ply ply, void *anybuffer, size_t size) { char *buffer = (char *) anybuffer; size_t i = 0; assert(ply && ply->fp && ply->io_mode == PLY_READ); assert(ply->buffer_first <= ply->buffer_last); while (i < size) { if (ply->buffer_first < ply->buffer_last) { buffer[i] = ply->buffer[ply->buffer_first]; ply->buffer_first++; i++; } else { ply->buffer_first = 0; ply->buffer_last = fread(ply->buffer, 1, BUFFERSIZE, ply->fp); if (ply->buffer_last <= 0) return 0; } } return 1; } static int ply_write_chunk(p_ply ply, void *anybuffer, size_t size) { char *buffer = (char *) anybuffer; size_t i = 0; assert(ply && ply->fp && ply->io_mode == PLY_WRITE); assert(ply->buffer_last <= BUFFERSIZE); while (i < size) { if (ply->buffer_last < BUFFERSIZE) { ply->buffer[ply->buffer_last] = buffer[i]; ply->buffer_last++; i++; } else { ply->buffer_last = 0; if (fwrite(ply->buffer, 1, BUFFERSIZE, ply->fp) < BUFFERSIZE) return 0; } } return 1; } static int ply_write_chunk_reverse(p_ply ply, void *anybuffer, size_t size) { int ret = 0; ply_reverse(anybuffer, size); ret = ply_write_chunk(ply, anybuffer, size); ply_reverse(anybuffer, size); return ret; } static int ply_read_chunk_reverse(p_ply ply, void *anybuffer, size_t size) { if (!ply_read_chunk(ply, anybuffer, size)) return 0; ply_reverse(anybuffer, size); return 1; } static void ply_reverse(void *anydata, size_t size) { char *data = (char *) anydata; char temp; size_t i; for (i = 0; i < size/2; i++) { temp = data[i]; data[i] = data[size-i-1]; data[size-i-1] = temp; } } static void ply_init(p_ply ply) { ply->element = NULL; ply->nelements = 0; ply->comment = NULL; ply->ncomments = 0; ply->obj_info = NULL; ply->nobj_infos = 0; ply->idriver = NULL; ply->odriver = NULL; ply->buffer[0] = '\0'; ply->buffer_first = ply->buffer_last = ply->buffer_token = 0; ply->welement = 0; ply->wproperty = 0; ply->winstance_index = 0; ply->wlength = 0; ply->wvalue_index = 0; } static void ply_element_init(p_ply_element element) { element->name[0] = '\0'; element->ninstances = 0; element->property = NULL; element->nproperties = 0; } static void ply_property_init(p_ply_property property) { property->name[0] = '\0'; property->type = -1; property->length_type = -1; property->value_type = -1; property->read_cb = (p_ply_read_cb) NULL; property->pdata = NULL; property->idata = 0; } static p_ply ply_alloc(void) { p_ply ply = (p_ply) calloc(1, sizeof(t_ply)); if (!ply) return NULL; ply_init(ply); return ply; } static void *ply_grow_array(p_ply ply, void **pointer, long *nmemb, long size) { void *temp = *pointer; long count = *nmemb + 1; if (!temp) temp = malloc(count*size); else temp = realloc(temp, count*size); if (!temp) { ply_ferror(ply, "Out of memory"); return NULL; } *pointer = temp; *nmemb = count; return (char *) temp + (count-1) * size; } static p_ply_element ply_grow_element(p_ply ply) { p_ply_element element = NULL; assert(ply); assert(ply->element || ply->nelements == 0); assert(!ply->element || ply->nelements > 0); element = (p_ply_element) ply_grow_array(ply, (void **) &ply->element, &ply->nelements, sizeof(t_ply_element)); if (!element) return NULL; ply_element_init(element); return element; } static p_ply_property ply_grow_property(p_ply ply, p_ply_element element) { p_ply_property property = NULL; assert(ply); assert(element); assert(element->property || element->nproperties == 0); assert(!element->property || element->nproperties > 0); property = (p_ply_property) ply_grow_array(ply, (void **) &element->property, &element->nproperties, sizeof(t_ply_property)); if (!property) return NULL; ply_property_init(property); return property; } static int ply_read_header_format(p_ply ply) { assert(ply && ply->fp && ply->io_mode == PLY_READ); if (strcmp(BWORD(ply), "format")) return 0; if (!ply_read_word(ply)) return 0; ply->storage_mode = ply_find_string(BWORD(ply), ply_storage_mode_list); if (ply->storage_mode == (e_ply_storage_mode) (-1)) return 0; if (ply->storage_mode == PLY_ASCII) ply->idriver = &ply_idriver_ascii; else if (ply->storage_mode == ply_arch_endian()) ply->idriver = &ply_idriver_binary; else ply->idriver = &ply_idriver_binary_reverse; if (!ply_read_word(ply)) return 0; if (strcmp(BWORD(ply), "1.0")) return 0; if (!ply_read_word(ply)) return 0; return 1; } static int ply_read_header_comment(p_ply ply) { assert(ply && ply->fp && ply->io_mode == PLY_READ); if (strcmp(BWORD(ply), "comment")) return 0; if (!ply_read_line(ply)) return 0; if (!ply_add_comment(ply, BLINE(ply))) return 0; if (!ply_read_word(ply)) return 0; return 1; } static int ply_read_header_obj_info(p_ply ply) { assert(ply && ply->fp && ply->io_mode == PLY_READ); if (strcmp(BWORD(ply), "obj_info")) return 0; if (!ply_read_line(ply)) return 0; if (!ply_add_obj_info(ply, BLINE(ply))) return 0; if (!ply_read_word(ply)) return 0; return 1; } static int ply_read_header_property(p_ply ply) { p_ply_element element = NULL; p_ply_property property = NULL; /* make sure it is a property */ if (strcmp(BWORD(ply), "property")) return 0; element = &ply->element[ply->nelements-1]; property = ply_grow_property(ply, element); if (!property) return 0; /* get property type */ if (!ply_read_word(ply)) return 0; property->type = ply_find_string(BWORD(ply), ply_type_list); if (property->type == (e_ply_type) (-1)) return 0; if (property->type == PLY_LIST) { /* if it's a list, we need the base types */ if (!ply_read_word(ply)) return 0; property->length_type = ply_find_string(BWORD(ply), ply_type_list); if (property->length_type == (e_ply_type) (-1)) return 0; if (!ply_read_word(ply)) return 0; property->value_type = ply_find_string(BWORD(ply), ply_type_list); if (property->value_type == (e_ply_type) (-1)) return 0; } /* get property name */ if (!ply_read_word(ply)) return 0; strcpy(property->name, BWORD(ply)); if (!ply_read_word(ply)) return 0; return 1; } static int ply_read_header_element(p_ply ply) { p_ply_element element = NULL; long dummy; assert(ply && ply->fp && ply->io_mode == PLY_READ); if (strcmp(BWORD(ply), "element")) return 0; /* allocate room for new element */ element = ply_grow_element(ply); if (!element) return 0; /* get element name */ if (!ply_read_word(ply)) return 0; strcpy(element->name, BWORD(ply)); /* get number of elements of this type */ if (!ply_read_word(ply)) return 0; if (sscanf(BWORD(ply), "%ld", &dummy) != 1) { ply_ferror(ply, "Expected number got '%s'", BWORD(ply)); return 0; } element->ninstances = dummy; /* get all properties for this element */ if (!ply_read_word(ply)) return 0; while (ply_read_header_property(ply) || ply_read_header_comment(ply) || ply_read_header_obj_info(ply)) /* do nothing */; return 1; } static void ply_error_cb(p_ply ply, const char *message) { (void) ply; fprintf(stderr, "RPly: %s\n", message); } static void ply_ferror(p_ply ply, const char *fmt, ...) { char buffer[1024]; va_list ap; va_start(ap, fmt); vsprintf(buffer, fmt, ap); va_end(ap); ply->error_cb(ply, buffer); } static e_ply_storage_mode ply_arch_endian(void) { unsigned long i = 1; unsigned char *s = (unsigned char *) &i; if (*s == 1) return PLY_LITTLE_ENDIAN; else return PLY_BIG_ENDIAN; } static int ply_type_check(void) { assert(sizeof(t_ply_int8) == 1); assert(sizeof(t_ply_uint8) == 1); assert(sizeof(t_ply_int16) == 2); assert(sizeof(t_ply_uint16) == 2); assert(sizeof(t_ply_int32) == 4); assert(sizeof(t_ply_uint32) == 4); assert(sizeof(float) == 4); assert(sizeof(double) == 8); if (sizeof(t_ply_int8) != 1) return 0; if (sizeof(t_ply_uint8) != 1) return 0; if (sizeof(t_ply_int16) != 2) return 0; if (sizeof(t_ply_uint16) != 2) return 0; if (sizeof(t_ply_int32) != 4) return 0; if (sizeof(t_ply_uint32) != 4) return 0; if (sizeof(float) != 4) return 0; if (sizeof(double) != 8) return 0; return 1; } /* ---------------------------------------------------------------------- * Output handlers * ---------------------------------------------------------------------- */ static int oascii_int8(p_ply ply, double value) { if (value > PLY_INT8_MAX || value < PLY_INT8_MIN) return 0; return fprintf(ply->fp, "%d", (t_ply_int8) value) > 0; } static int oascii_uint8(p_ply ply, double value) { if (value > PLY_UINT8_MAX || value < 0) return 0; return fprintf(ply->fp, "%d", (t_ply_uint8) value) > 0; } static int oascii_int16(p_ply ply, double value) { if (value > PLY_INT16_MAX || value < PLY_INT16_MIN) return 0; return fprintf(ply->fp, "%d", (t_ply_int16) value) > 0; } static int oascii_uint16(p_ply ply, double value) { if (value > PLY_UINT16_MAX || value < 0) return 0; return fprintf(ply->fp, "%d", (t_ply_uint16) value) > 0; } static int oascii_int32(p_ply ply, double value) { if (value > PLY_INT32_MAX || value < PLY_INT32_MIN) return 0; return fprintf(ply->fp, "%d", (t_ply_int32) value) > 0; } static int oascii_uint32(p_ply ply, double value) { if (value > PLY_UINT32_MAX || value < 0) return 0; return fprintf(ply->fp, "%d", (t_ply_uint32) value) > 0; } static int oascii_float32(p_ply ply, double value) { if (value < -FLT_MAX || value > FLT_MAX) return 0; return fprintf(ply->fp, "%g", (float) value) > 0; } static int oascii_float64(p_ply ply, double value) { if (value < -DBL_MAX || value > DBL_MAX) return 0; return fprintf(ply->fp, "%g", value) > 0; } static int obinary_int8(p_ply ply, double value) { t_ply_int8 int8 = (t_ply_int8) value; if (value > PLY_INT8_MAX || value < PLY_INT8_MIN) return 0; return ply->odriver->ochunk(ply, &int8, sizeof(int8)); } static int obinary_uint8(p_ply ply, double value) { t_ply_uint8 uint8 = (t_ply_uint8) value; if (value > PLY_UINT8_MAX || value < 0) return 0; return ply->odriver->ochunk(ply, &uint8, sizeof(uint8)); } static int obinary_int16(p_ply ply, double value) { t_ply_int16 int16 = (t_ply_int16) value; if (value > PLY_INT16_MAX || value < PLY_INT16_MIN) return 0; return ply->odriver->ochunk(ply, &int16, sizeof(int16)); } static int obinary_uint16(p_ply ply, double value) { t_ply_uint16 uint16 = (t_ply_uint16) value; if (value > PLY_UINT16_MAX || value < 0) return 0; return ply->odriver->ochunk(ply, &uint16, sizeof(uint16)); } static int obinary_int32(p_ply ply, double value) { t_ply_int32 int32 = (t_ply_int32) value; if (value > PLY_INT32_MAX || value < PLY_INT32_MIN) return 0; return ply->odriver->ochunk(ply, &int32, sizeof(int32)); } static int obinary_uint32(p_ply ply, double value) { t_ply_uint32 uint32 = (t_ply_uint32) value; if (value > PLY_UINT32_MAX || value < 0) return 0; return ply->odriver->ochunk(ply, &uint32, sizeof(uint32)); } static int obinary_float32(p_ply ply, double value) { float float32 = (float) value; if (value > FLT_MAX || value < -FLT_MAX) return 0; return ply->odriver->ochunk(ply, &float32, sizeof(float32)); } static int obinary_float64(p_ply ply, double value) { return ply->odriver->ochunk(ply, &value, sizeof(value)); } /* ---------------------------------------------------------------------- * Input handlers * ---------------------------------------------------------------------- */ static int iascii_int8(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtol(BWORD(ply), &end, 10); if (*end || *value > PLY_INT8_MAX || *value < PLY_INT8_MIN) return 0; return 1; } static int iascii_uint8(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtol(BWORD(ply), &end, 10); if (*end || *value > PLY_UINT8_MAX || *value < 0) return 0; return 1; } static int iascii_int16(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtol(BWORD(ply), &end, 10); if (*end || *value > PLY_INT16_MAX || *value < PLY_INT16_MIN) return 0; return 1; } static int iascii_uint16(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtol(BWORD(ply), &end, 10); if (*end || *value > PLY_UINT16_MAX || *value < 0) return 0; return 1; } static int iascii_int32(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtol(BWORD(ply), &end, 10); if (*end || *value > PLY_INT32_MAX || *value < PLY_INT32_MIN) return 0; return 1; } static int iascii_uint32(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtol(BWORD(ply), &end, 10); if (*end || *value > PLY_UINT32_MAX || *value < 0) return 0; return 1; } static int iascii_float32(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtod(BWORD(ply), &end); if (*end || *value < -FLT_MAX || *value > FLT_MAX) return 0; return 1; } static int iascii_float64(p_ply ply, double *value) { char *end; if (!ply_read_word(ply)) return 0; *value = strtod(BWORD(ply), &end); if (*end || *value < -DBL_MAX || *value > DBL_MAX) return 0; return 1; } static int ibinary_int8(p_ply ply, double *value) { t_ply_int8 int8; if (!ply->idriver->ichunk(ply, &int8, 1)) return 0; *value = int8; return 1; } static int ibinary_uint8(p_ply ply, double *value) { t_ply_uint8 uint8; if (!ply->idriver->ichunk(ply, &uint8, 1)) return 0; *value = uint8; return 1; } static int ibinary_int16(p_ply ply, double *value) { t_ply_int16 int16; if (!ply->idriver->ichunk(ply, &int16, sizeof(int16))) return 0; *value = int16; return 1; } static int ibinary_uint16(p_ply ply, double *value) { t_ply_uint16 uint16; if (!ply->idriver->ichunk(ply, &uint16, sizeof(uint16))) return 0; *value = uint16; return 1; } static int ibinary_int32(p_ply ply, double *value) { t_ply_int32 int32; if (!ply->idriver->ichunk(ply, &int32, sizeof(int32))) return 0; *value = int32; return 1; } static int ibinary_uint32(p_ply ply, double *value) { t_ply_uint32 uint32; if (!ply->idriver->ichunk(ply, &uint32, sizeof(uint32))) return 0; *value = uint32; return 1; } static int ibinary_float32(p_ply ply, double *value) { float float32; if (!ply->idriver->ichunk(ply, &float32, sizeof(float32))) return 0; *value = float32; return 1; } static int ibinary_float64(p_ply ply, double *value) { return ply->idriver->ichunk(ply, value, sizeof(double)); } /* ---------------------------------------------------------------------- * Constants * ---------------------------------------------------------------------- */ static t_ply_idriver ply_idriver_ascii = { { iascii_int8, iascii_uint8, iascii_int16, iascii_uint16, iascii_int32, iascii_uint32, iascii_float32, iascii_float64, iascii_int8, iascii_uint8, iascii_int16, iascii_uint16, iascii_int32, iascii_uint32, iascii_float32, iascii_float64 }, /* order matches e_ply_type enum */ NULL, "ascii input" }; static t_ply_idriver ply_idriver_binary = { { ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16, ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64, ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16, ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64 }, /* order matches e_ply_type enum */ ply_read_chunk, "binary input" }; static t_ply_idriver ply_idriver_binary_reverse = { { ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16, ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64, ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16, ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64 }, /* order matches e_ply_type enum */ ply_read_chunk_reverse, "reverse binary input" }; static t_ply_odriver ply_odriver_ascii = { { oascii_int8, oascii_uint8, oascii_int16, oascii_uint16, oascii_int32, oascii_uint32, oascii_float32, oascii_float64, oascii_int8, oascii_uint8, oascii_int16, oascii_uint16, oascii_int32, oascii_uint32, oascii_float32, oascii_float64 }, /* order matches e_ply_type enum */ NULL, "ascii output" }; static t_ply_odriver ply_odriver_binary = { { obinary_int8, obinary_uint8, obinary_int16, obinary_uint16, obinary_int32, obinary_uint32, obinary_float32, obinary_float64, obinary_int8, obinary_uint8, obinary_int16, obinary_uint16, obinary_int32, obinary_uint32, obinary_float32, obinary_float64 }, /* order matches e_ply_type enum */ ply_write_chunk, "binary output" }; static t_ply_odriver ply_odriver_binary_reverse = { { obinary_int8, obinary_uint8, obinary_int16, obinary_uint16, obinary_int32, obinary_uint32, obinary_float32, obinary_float64, obinary_int8, obinary_uint8, obinary_int16, obinary_uint16, obinary_int32, obinary_uint32, obinary_float32, obinary_float64 }, /* order matches e_ply_type enum */ ply_write_chunk_reverse, "reverse binary output" }; /* ---------------------------------------------------------------------- * Copyright (C) 2003-2015 Diego Nehab. All rights reserved. * * 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. * ---------------------------------------------------------------------- */ rply-1.1.4/rply.h000644 000765 000024 00000036665 12565712037 014124 0ustar00diegostaff000000 000000 #ifndef RPLY_H #define RPLY_H /* ---------------------------------------------------------------------- * RPly library, read/write PLY files * Diego Nehab, IMPA * http://www.impa.br/~diego/software/rply * * This library is distributed under the MIT License. See notice * at the end of this file. * ---------------------------------------------------------------------- */ #ifdef __cplusplus extern "C" { #endif #define RPLY_VERSION "RPly 1.1.4" #define RPLY_COPYRIGHT "Copyright (C) 2003-2015 Diego Nehab" #define RPLY_AUTHORS "Diego Nehab" /* ---------------------------------------------------------------------- * Types * ---------------------------------------------------------------------- */ /* structures are opaque */ typedef struct t_ply_ *p_ply; typedef struct t_ply_element_ *p_ply_element; typedef struct t_ply_property_ *p_ply_property; typedef struct t_ply_argument_ *p_ply_argument; /* ply format mode type */ typedef enum e_ply_storage_mode_ { PLY_BIG_ENDIAN, PLY_LITTLE_ENDIAN, PLY_ASCII, PLY_DEFAULT /* has to be the last in enum */ } e_ply_storage_mode; /* order matches ply_storage_mode_list */ /* ply data type */ typedef enum e_ply_type { PLY_INT8, PLY_UINT8, PLY_INT16, PLY_UINT16, PLY_INT32, PLY_UIN32, PLY_FLOAT32, PLY_FLOAT64, PLY_CHAR, PLY_UCHAR, PLY_SHORT, PLY_USHORT, PLY_INT, PLY_UINT, PLY_FLOAT, PLY_DOUBLE, PLY_LIST /* has to be the last in enum */ } e_ply_type; /* order matches ply_type_list */ /* ---------------------------------------------------------------------- * Error callback prototype * * message: error message * ply: handle returned by ply_open or ply_create * ---------------------------------------------------------------------- */ typedef void (*p_ply_error_cb)(p_ply ply, const char *message); /* ---------------------------------------------------------------------- * Gets user data from within an error callback * * ply: handle returned by ply_open or ply_create * idata,pdata: contextual information set in ply_open or ply_create * ---------------------------------------------------------------------- */ int ply_get_ply_user_data(p_ply ply, void **pdata, long *idata); /* ---------------------------------------------------------------------- * Opens a PLY file for reading (fails if file is not a PLY file) * * name: file name * error_cb: error callback function * idata,pdata: contextual information available to users * * Returns 1 if successful, 0 otherwise * ---------------------------------------------------------------------- */ p_ply ply_open(const char *name, p_ply_error_cb error_cb, long idata, void *pdata); /* ---------------------------------------------------------------------- * Reads and parses the header of a PLY file returned by ply_open * * ply: handle returned by ply_open * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_read_header(p_ply ply); /* ---------------------------------------------------------------------- * Property reading callback prototype * * argument: parameters for property being processed when callback is called * * Returns 1 if should continue processing file, 0 if should abort. * ---------------------------------------------------------------------- */ typedef int (*p_ply_read_cb)(p_ply_argument argument); /* ---------------------------------------------------------------------- * Sets up callbacks for property reading after header was parsed * * ply: handle returned by ply_open * element_name: element where property is * property_name: property to associate element with * read_cb: function to be called for each property value * pdata/idata: user data that will be passed to callback * * Returns 0 if no element or no property in element, returns the * number of element instances otherwise. * ---------------------------------------------------------------------- */ long ply_set_read_cb(p_ply ply, const char *element_name, const char *property_name, p_ply_read_cb read_cb, void *pdata, long idata); /* ---------------------------------------------------------------------- * Returns information about the element originating a callback * * argument: handle to argument * element: receives a the element handle (if non-null) * instance_index: receives the index of the current element instance * (if non-null) * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_get_argument_element(p_ply_argument argument, p_ply_element *element, long *instance_index); /* ---------------------------------------------------------------------- * Returns information about the property originating a callback * * argument: handle to argument * property: receives the property handle (if non-null) * length: receives the number of values in this property (if non-null) * value_index: receives the index of current property value (if non-null) * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_get_argument_property(p_ply_argument argument, p_ply_property *property, long *length, long *value_index); /* ---------------------------------------------------------------------- * Returns user data associated with callback * * pdata: receives a copy of user custom data pointer (if non-null) * idata: receives a copy of user custom data integer (if non-null) * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_get_argument_user_data(p_ply_argument argument, void **pdata, long *idata); /* ---------------------------------------------------------------------- * Returns the value associated with a callback * * argument: handle to argument * * Returns the current data item * ---------------------------------------------------------------------- */ double ply_get_argument_value(p_ply_argument argument); /* ---------------------------------------------------------------------- * Reads all elements and properties calling the callbacks defined with * calls to ply_set_read_cb * * ply: handle returned by ply_open * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_read(p_ply ply); /* ---------------------------------------------------------------------- * Iterates over all elements by returning the next element. * Call with NULL to return handle to first element. * * ply: handle returned by ply_open * last: handle of last element returned (NULL for first element) * * Returns element if successfull or NULL if no more elements * ---------------------------------------------------------------------- */ p_ply_element ply_get_next_element(p_ply ply, p_ply_element last); /* ---------------------------------------------------------------------- * Iterates over all comments by returning the next comment. * Call with NULL to return pointer to first comment. * * ply: handle returned by ply_open * last: pointer to last comment returned (NULL for first comment) * * Returns comment if successfull or NULL if no more comments * ---------------------------------------------------------------------- */ const char *ply_get_next_comment(p_ply ply, const char *last); /* ---------------------------------------------------------------------- * Iterates over all obj_infos by returning the next obj_info. * Call with NULL to return pointer to first obj_info. * * ply: handle returned by ply_open * last: pointer to last obj_info returned (NULL for first obj_info) * * Returns obj_info if successfull or NULL if no more obj_infos * ---------------------------------------------------------------------- */ const char *ply_get_next_obj_info(p_ply ply, const char *last); /* ---------------------------------------------------------------------- * Returns information about an element * * element: element of interest * name: receives a pointer to internal copy of element name (if non-null) * ninstances: receives the number of instances of this element (if non-null) * * Returns 1 if successfull or 0 otherwise * ---------------------------------------------------------------------- */ int ply_get_element_info(p_ply_element element, const char** name, long *ninstances); /* ---------------------------------------------------------------------- * Iterates over all properties by returning the next property. * Call with NULL to return handle to first property. * * element: handle of element with the properties of interest * last: handle of last property returned (NULL for first property) * * Returns element if successfull or NULL if no more properties * ---------------------------------------------------------------------- */ p_ply_property ply_get_next_property(p_ply_element element, p_ply_property last); /* ---------------------------------------------------------------------- * Returns information about a property * * property: handle to property of interest * name: receives a pointer to internal copy of property name (if non-null) * type: receives the property type (if non-null) * length_type: for list properties, receives the scalar type of * the length field (if non-null) * value_type: for list properties, receives the scalar type of the value * fields (if non-null) * * Returns 1 if successfull or 0 otherwise * ---------------------------------------------------------------------- */ int ply_get_property_info(p_ply_property property, const char** name, e_ply_type *type, e_ply_type *length_type, e_ply_type *value_type); /* ---------------------------------------------------------------------- * Creates new PLY file * * name: file name * storage_mode: file format mode * error_cb: error callback function * idata,pdata: contextual information available to users * * Returns handle to PLY file if successfull, NULL otherwise * ---------------------------------------------------------------------- */ p_ply ply_create(const char *name, e_ply_storage_mode storage_mode, p_ply_error_cb error_cb, long idata, void *pdata); /* ---------------------------------------------------------------------- * Adds a new element to the PLY file created by ply_create * * ply: handle returned by ply_create * name: name of new element * ninstances: number of element of this time in file * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_add_element(p_ply ply, const char *name, long ninstances); /* ---------------------------------------------------------------------- * Adds a new property to the last element added by ply_add_element * * ply: handle returned by ply_create * name: name of new property * type: property type * length_type: scalar type of length field of a list property * value_type: scalar type of value fields of a list property * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_add_property(p_ply ply, const char *name, e_ply_type type, e_ply_type length_type, e_ply_type value_type); /* ---------------------------------------------------------------------- * Adds a new list property to the last element added by ply_add_element * * ply: handle returned by ply_create * name: name of new property * length_type: scalar type of length field of a list property * value_type: scalar type of value fields of a list property * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_add_list_property(p_ply ply, const char *name, e_ply_type length_type, e_ply_type value_type); /* ---------------------------------------------------------------------- * Adds a new property to the last element added by ply_add_element * * ply: handle returned by ply_create * name: name of new property * type: property type * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type); /* ---------------------------------------------------------------------- * Adds a new comment item * * ply: handle returned by ply_create * comment: pointer to string with comment text * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_add_comment(p_ply ply, const char *comment); /* ---------------------------------------------------------------------- * Adds a new obj_info item * * ply: handle returned by ply_create * comment: pointer to string with obj_info data * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_add_obj_info(p_ply ply, const char *obj_info); /* ---------------------------------------------------------------------- * Writes the PLY file header after all element and properties have been * defined by calls to ply_add_element and ply_add_property * * ply: handle returned by ply_create * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_write_header(p_ply ply); /* ---------------------------------------------------------------------- * Writes one property value, in the order they should be written to the * file. For each element type, write all elements of that type in order. * For each element, write all its properties in order. For scalar * properties, just write the value. For list properties, write the length * and then each of the values. * * ply: handle returned by ply_create * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_write(p_ply ply, double value); /* ---------------------------------------------------------------------- * Closes a PLY file handle. Releases all memory used by handle * * ply: handle to be closed. * * Returns 1 if successfull, 0 otherwise * ---------------------------------------------------------------------- */ int ply_close(p_ply ply); #ifdef __cplusplus } #endif #endif /* RPLY_H */ /* ---------------------------------------------------------------------- * Copyright (C) 2003-2015 Diego Nehab. All rights reserved. * * 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. * ---------------------------------------------------------------------- */ rply-1.1.4/rplyfile.h000644 000765 000024 00000005433 12565712037 014751 0ustar00diegostaff000000 000000 #ifndef RPLY_FILE_H #define RPLY_FILE_H /* ---------------------------------------------------------------------- * RPly library, read/write PLY files * Diego Nehab, IMPA * http://www.impa.br/~diego/software/rply * * This library is distributed under the MIT License. See notice * at the end of this file. * ---------------------------------------------------------------------- */ #ifdef __cplusplus extern "C" { #endif /* ---------------------------------------------------------------------- * Opens a PLY file for reading (fails if file is not a PLY file) * * file_pointer: FILE * to file open for reading * error_cb: error callback function * idata,pdata: contextual information available to users * * Returns 1 if successful, 0 otherwise * ---------------------------------------------------------------------- */ p_ply ply_open_from_file(FILE *file_pointer, p_ply_error_cb error_cb, long idata, void *pdata); /* ---------------------------------------------------------------------- * Creates new PLY file * * file_pointer: FILE * to a file open for writing * storage_mode: file format mode * error_cb: error callback function * idata,pdata: contextual information available to users * * Returns handle to PLY file if successfull, NULL otherwise * ---------------------------------------------------------------------- */ p_ply ply_create_to_file(FILE *file_pointer, e_ply_storage_mode storage_mode, p_ply_error_cb error_cb, long idata, void *pdata); #ifdef __cplusplus } #endif #endif /* RPLY_FILE_H */ /* ---------------------------------------------------------------------- * Copyright (C) 2003-2015 Diego Nehab. All rights reserved. * * 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. * ---------------------------------------------------------------------- */ rply-1.1.4/manual/manual.html000644 000765 000024 00000111550 12565712037 016370 0ustar00diegostaff000000 000000 RPly: ANSI C library for PLY file format input and output

RPly
ANSI C Library for PLY file format input and output

Introduction

RPly is a library that lets applications read and write PLY files. The PLY file format is widely used to store geometric information, such as 3D models, but is general enough to be useful for other purposes.

There are other libraries out there, of course. I tried using them and finally decided to write my own. The result is RPly, and I hope you are as happy with it as I am.

RPly is easy to use, well documented, small, free, open-source, ANSI C, efficient, and well tested. I will keep supporting it for a while because all my tools use the library for input/output. The highlights are:

  • A callback mechanism that makes PLY file input straightforward;
  • Support for the full range of numeric formats though the user only deals with doubles;
  • Binary (big and little endian) and text modes are fully supported;
  • Input and output are buffered for efficiency;
  • Available under the MIT license for added freedom.

The format was developed at Stanford University for use with their 3D scanning projects. Greg Turk's PLY library, available from Georgia Institute of Technology, seems to be the standard reference to the PLY file format, although there are some variations out there.

Whatever documentation and examples were found, were taken into consideration to create RPly. In theory, since RPly doesn't try to interpret the meaning of the data in a PLY file, it should be able to read any PLY file. In practice, the library works with all PLY files that I could find.

Download

Version 1.1.4 of RPly is available for download in source code from this link. Examples and documentation are packed inside the tarball. Have fun!

Copyright © 2003-2015 Diego Nehab. All rights reserved.
Author: Diego Nehab

What's new?

  • Fixed bug that prevented reading of ASCII files that are not terminated by a whitespace;
  • Added ply_open_from_file and ply_create_to_file variants to ply_open and ply_create, respectively, that receive file pointers instead of file names.

RPly's idea of what a PLY file is

A PLY file contains the description of one object. This object is composed by elements, each element type being defined by a group of properties. The PLY file format specifies a syntax for the description of element types and the properties that compose them, as well as comments and meta-information.

The element type descriptions come in a header, which is followed by element instances. Element instances come grouped by their type, in the order of declaration. Each element instance is defined by the value of its properties. Properties values also appear in the order of their declaration. Here is a sample PLY file describing a triangle:

ply
format ascii 1.0
comment this is a simple file
obj_info any data, in one line of free form text
element vertex 3
property float x
property float y
property float z
element face 1
property list uchar int vertex_indices
end_header
-1 0 0
 0 1 0
 1 0 0
3 0 1 2

The header goes from the first line to the line marked by end_header. The first line contains only ply\n and is used to detect whether a file is in PLY format or not (RPly also accepts files that start with ply\r\n, in which case the end-of-line terminator is assumed to be \r\n throughout.) The second line specifies the format number (which is always 1.0) and the storage mode (ascii, binary_big_endian or binary_little_endian).

Lines that start with comment are just comments, of course. Lines that start with obj_info contain meta-information about the object. Comments and obj_infos are optional and their relative order in the header is irrelevant.

In the sample PLY file, the first element type is declared with name vertex, and on the same line we learn that there will be 3 instances of this element type. The properties following describe what a vertex element looks like. Each vertex is declared to consist of 3 scalar properties, named x, y and z. Each scalar property is declared to be of type float.

Scalar types can be any of the following: int8, uint8, int16, uint16, int32, uint32, float32, float64, char, uchar, short, ushort, int, uint, float, double. They consist of signed and unsigned integer types of sizes 8, 16 and 32 bits, as well as floating point types of 32 and 64bits.

Next, the face element type is declared, of which only 1 instance will be given. This element consists of a list property, named vertex_indices. Lists are sequences on which the first value, the length, gives the number of remaining values. List properties are described by the scalar type of their length field and the scalar type of the remaining fields. In the case of vertex_indices, the length field is of type uchar and the remaining values are of type int.

Following the header, come the elements, in the order they were declared in the header. First come the 3 elements of type vertex, each represented by the value of their properties x, y and z. Then comes the single face element, composed by a single list of type vertex_indices containing 3 values (0 1 2).

How to read a file with RPly

Most users that want to read a PLY file already know which elements and properties they are interested in. In the following example, we will implement a simple program that dumps the contents of a PLY file to the terminal, in a different, simpler format that only works for triangles.

This simple format has a header that gives the number of vertices in the first line and the number of triangles in the second line. Following the header come the vertices, and finally the triangles. Here is the sample code for the program:

#include <stdio.h>
#include "rply.h"

static int vertex_cb(p_ply_argument argument) {
    long eol;
    ply_get_argument_user_data(argument, NULL, &eol);
    printf("%g", ply_get_argument_value(argument));
    if (eol) printf("\n");
    else printf(" ");
    return 1;
}

static int face_cb(p_ply_argument argument) {
    long length, value_index;
    ply_get_argument_property(argument, NULL, &length, &value_index);
    switch (value_index) {
        case 0:
        case 1:
            printf("%g ", ply_get_argument_value(argument));
            break;
        case 2:
            printf("%g\n", ply_get_argument_value(argument));
            break;
        default:
            break;
    }
    return 1;
}

int main(void) {
    long nvertices, ntriangles;
    p_ply ply = ply_open("input.ply", NULL, 0, NULL);
    if (!ply) return 1;
    if (!ply_read_header(ply)) return 1;
    nvertices = ply_set_read_cb(ply, "vertex", "x", vertex_cb, NULL, 0);
    ply_set_read_cb(ply, "vertex", "y", vertex_cb, NULL, 0);
    ply_set_read_cb(ply, "vertex", "z", vertex_cb, NULL, 1);
    ntriangles = ply_set_read_cb(ply, "face", "vertex_indices", face_cb, NULL, 0);
    printf("%ld\n%ld\n", nvertices, ntriangles);
    if (!ply_read(ply)) return 1;
    ply_close(ply);
    return 0;
}

RPly uses callbacks to pass data to an application. Independent callbacks can be associated with each property of each element. For scalar properties, the callback is invoked once for each instance. For list properties, the callback is invoked first with the number of entries in the instance, and then once for each of the data entries. This is exactly the order in which the data items appear in the file.

To keep things simple, values are always passed as double, regardless of how they are stored in the file. From its parameters, callbacks can find out exactly which part of the file is being processed (including the actual type of the value), plus access custom information provided by the user in the form of a pointer and an integer constant.

In our example, we start with a call to ply_open to open a file for reading. Then we get RPly to parse it's header, with a call to ply_read_header. After the header is parsed, RPly knows which element types and properties are available. We then set callbacks for each of the vertex element properties and the face property (using ply_set_read_cb). Finally, we invoke the main RPly reading function, ply_read. This function reads all data in the file, passing the data to the appropriate callbacks. After all reading is done, we call ply_close to release any resources used by RPly.

There are some details, of course. Ply_set_read_cb returns the number of instances of the target property (which is the same as the number of element instances). This is how the program obtains the number of vertices and faces in the file.

RPly lets us associate one pointer and one integer to each callback. We are free to use either or both to link some context to our callbacks. Our example uses the integer placeholder to tell vertex_cb that it has to break the line after the z property (notice the last argument of ply_set_read_cb).

Vertex_cb gets the user data and the property value from it's argument and prints accordingly. The face_cb callback is a bit more complicated because lists are more complicated. Since the simple file format only supports triangles, it only prints the first 3 list values, after which it breaks the line.

The output of the program, as expected, is:

3
1
-1 0 0
0 1 0
1 0 0
0 1 2

Writing files with RPly

The next example is somewhat more involved. We will create a program that converts our simple PLY file to binary mode. Besides showing how to write a PLY file, this example also illustrates the query functions. We do not know a priori which elements and properties, comments and obj_infos will be in the input file, so we need a way to find out. Although our simple program would work on any PLY file, a better version of this program is available from the RPly distribution. For simplicity, the simple version omits error messages and command line parameter processing.

In practice, writing a file is even easier than reading one. First we create a file in binary mode, with a call to ply_create (notice the argument PLY_LITTLE_ENDIAN that gives the storage mode). Then, we define the elements using ply_add_element. After each element, we define its properties using ply_add_scalar_property or ply_add_list_property. When we are done with elements and properties, we add comments and obj_infos. We then write the header with ply_write_header and send all data items. The data items are sent one by one, with calls to ply_write, in the same order they are to appear in the file. Again, to simplify things, this function receives data as double and performs the needed conversion. Here is the code for the example:

#include <stdio.h>
#include "rply.h"

static int callback(p_ply_argument argument) {
    void *pdata;
    /* just pass the value from the input file to the output file */
    ply_get_argument_user_data(argument, &pdata, NULL);
    ply_write((p_ply) pdata, ply_get_argument_value(argument));
    return 1;
}

static int setup_callbacks(p_ply iply, p_ply oply) {
    p_ply_element element = NULL;
    /* iterate over all elements in input file */
    while ((element = ply_get_next_element(iply, element))) {
        p_ply_property property = NULL;
        long ninstances = 0;
        const char *element_name;
        ply_get_element_info(element, &element_name, &ninstances);
        /* add this element to output file */
        if (!ply_add_element(oply, element_name, ninstances)) return 0;
        /* iterate over all properties of current element */
        while ((property = ply_get_next_property(element, property))) {
            const char *property_name;
            e_ply_type type, length_type, value_type;
            ply_get_property_info(property, &property_name, &type,
                    &length_type, &value_type);
            /* setup input callback for this property */
            if (!ply_set_read_cb(iply, element_name, property_name, callback,
                    oply, 0)) return 0;
            /* add this property to output file */
            if (!ply_add_property(oply, property_name, type, length_type,
                    value_type)) return 0;
        }
    }
    return 1;
}

int main(int argc, char *argv[]) {
    const char *value;
    p_ply iply, oply;
    iply = ply_open("input.ply", NULL, 0, NULL);
    if (!iply) return 1;
    if (!ply_read_header(iply)) return 1;
    oply = ply_create("output.ply", PLY_LITTLE_ENDIAN, NULL, 0, NULL);
    if (!oply) return 1;
    if (!setup_callbacks(iply, oply)) return 1;
    /* pass comments and obj_infos from input to output */
    value = NULL;
    while ((value = ply_get_next_comment(iply, value)))
        if (!ply_add_comment(oply, value)) return 1;
    value = NULL;
    while ((value = ply_get_next_obj_info(iply, value)))
        if (!ply_add_obj_info(oply, value)) return 1;;
    /* write output header */
    if (!ply_write_header(oply)) return 1;
    /* read input file generating callbacks that pass data to output file */
    if (!ply_read(iply)) return 1;
    /* close up, we are done */
    if (!ply_close(iply)) return 1;
    if (!ply_close(oply)) return 1;
    return 0;
}

RPly uses iterators to let the user loop over a PLY file header. A function is used to get the first item of a given class (element, property etc). Passing the last returned item to the same function produces the next item, until there are no more items. Examples of iterator use can be seen in the main function, which uses them to loop over comments and obj_infos, and in the setup_callbacks function, which loops over elements and properties.

In the setup_callbacks function, for each element in the input, an equivalent element is defined in the output. For each property in each element, an equivalent property is defined in the output. Notice that the same callback is specified for all properties. It is given the output PLY handle as the context pointer. Each time it is called, it passes the received value to ply_write on the output handle. It is as simple as that.

A note on locale

ASCII PLY files are supposed to use the C locale for numeric formatting. RPly relies on library functions (such as fprintf and strtod) that are affected by the current locale. If your software modifies the locale (or if it uses another library/toolkit that does) and you use RPly under the modified locale, you may be unable to read or write properly formatted ASCII PLY files.

Modifying RPly internally to hedge against different locales would be complicated, particularly in multi-threaded applications. Therefore, RPly leaves this as your responsibility. To protect against locale problems in the simplest scenario, you should bracket RPly I/O as follows:

#include <locale.h>
/* Save application locale */
const char *old_locale = setlocale(LC_NUMERIC, NULL);
/* Change to PLY standard */
setlocale(LC_NUMERIC, "C");
/* Use the RPly library */
...

/* Restore application locale when done */
setlocale(LC_NUMERIC, old_locale);

Reference Manual

p_ply ply_open(const char *name, p_ply_error_cb error_cb, long idata, void *pdata)

Opens a PLY file for reading, checks if it is a valid PLY file and returns a handle to it.

Name is the file name, and error_cb is a function to be called when an error is found. Arguments idata and pdata are available to the error callback via the ply_get_ply_user_data function. If error_cb is NULL, the default error callback is used. It prints a message to the standard error stream.

Returns a handle to the file or NULL on error.

Note: Error_cb is of type void (*p_ply_error_cb)(p_ply ply, const char *message).

p_ply ply_open_from_file(FILE *file_pointer, p_ply_error_cb error_cb, long idata, void *pdata)

Checks if the FILE pointer points to a valid PLY file and returns a handle to it. The handle can be used wherever a handle returned by ply_open is accepted.

File_pointer is the FILE pointer open for reading, and error_cb is a function to be called when an error is found. Arguments idata and pdata are available to the error callback via the ply_get_ply_user_data function. If error_cb is NULL, the default error callback is used. It prints a message to the standard error stream.

Returns a handle to the file or NULL on error.

Note: Error_cb is of type void (*p_ply_error_cb)(p_ply ply, const char *message).

Note: This function is declared in header rplyfile.h.

int ply_get_ply_user_data(p_ply_ply ply, void *pdata, long *idata)

Retrieves user data from the ply handle.

Ply is the handle passed to the error callback. Pdata receives the user data pointer. Idata receives the user data integer. Pdata and idata can be NULL.

Returns 1 in case of success, 0 otherwise.

int ply_read_header(p_ply ply)

Reads and parses the header of a PLY file. After a call to this function, the query functions ply_get_next_element, ply_get_next_property, ply_get_next_comment, and ply_get_next_obj_info can be called. Callbacks can also be set with the ply_set_read_cb function.

Ply is a handle returned by ply_open.

Returns 1 in case of success, 0 otherwise.

long ply_set_read_cb(
   p_ply ply,
   const char *element_name,
   const char *property_name,
   p_ply_read_cb read_cb,
   void *pdata,
   long idata
)

Sets up the callback to be invoked when the value of a property is read.

Ply is a handle returned by ply_open. Element_name and property_name are the names of the element and property of interest. Read_cb is the callback function. Pdata and idata are user data to be passed to the callback function.

Returns the number of instances of the element of interest.

Note: Read_cb is of type int (*p_ply_read_cb)(p_ply_argument argument). The callback should return 1 to continue the reading process, or return 0 to abort.

int ply_get_argument_element(
   p_ply_argument argument,
   p_ply_element *element,
   long *instance_index
)

Retrieves element information from the callback argument.

Argument is the handle passed to the callback. Element receives a handle to the element originating the callback. Instance_index receives the index of the instance of the element being read. Element and instance_index can be NULL.

Returns 1 in case of success, 0 otherwise.

Note: further information can be obtained from element with a call to ply_get_element_info.

int ply_get_argument_property(
   p_ply_argument argument,
   p_ply_property *property,
   long *length,
   long *value_index
)

Retrieves property information from the callback argument.

Argument is the handle passed to the callback. Property receives a handle to the property originating the callback. Length receives the number of values in the list property (1 for scalar properties). Value_index receives the index of the current property entry (0 for scalar properties, -1 for the first value of a list property, the one that gives the number of entries). Property, length and value_index can be NULL.

Returns 1 in case of success, 0 otherwise.

Note: further information can be obtained from property with a call to ply_get_property_info.

int ply_get_argument_user_data(p_ply_argument argument, void *pdata, long *idata)

Retrieves the user data from the callback argument.

Argument is the handle passed to the callback. Pdata receives the user data pointer. Idata receives the user data integer. Pdata and idata can be NULL.

Returns 1 in case of success, 0 otherwise.

double ply_get_argument_value(p_ply_argument argument)

Retrieves the property value from the callback argument.

Argument is the handle passed to the callback.

Returns the property value.

int ply_read(p_ply ply)

Reads all data in file, calling appropriate callbacks.

Ply is a handle returned by ply_open.

Returns 1 in case of success, 0 otherwise.

p_ply_element ply_get_next_element(p_ply ply, p_ply_element last)

Iterates over all elements on the header of a PLY file.

Ply is a handle returned by ply_open. Ply_read_header must have been called on the handle otherwise no elements will be found. Last is NULL to retrieve the first element, and an element to retrieve the next element.

Returns the next element, or NULL if no more elements.

Note: further information can be obtained from an element with a call to ply_get_element_info.

p_ply_property ply_get_next_property(p_ply_element element, p_ply_property last)

Iterates over all properties of an element.

Element is an element handle. Last is NULL to retrieve the first property, and a property to retrieve the next property.

Returns the next property, or NULL if no more properties.

Note: further information can be obtained from a property with a call to ply_get_property_info.

const char *ply_get_next_comment(p_ply ply, const char *last)

Iterates over all comments on the header of a PLY file.

Ply is a handle returned by ply_open. Ply_read_header must have been called on the handle otherwise no comments will be found. Last is NULL to retrieve the first comment, and a comment to retrieve the next comment.

Returns the next comment, or NULL if no more comments.

const char *ply_get_next_obj_info(p_ply ply, const char *last)

Iterates over all obj_infos on the header of a PLY file.

Ply is a handle returned by ply_open. Ply_read_header must have been called on the handle otherwise no obj_infos will be found. Last is NULL to retrieve the first obj_info, and a obj_info to retrieve the next obj_info.

Returns the next obj_info, or NULL if no more obj_infos.

int ply_get_element_info(p_ply_element element, const char** name, long *ninstances)

Retrieves information from an element handle.

Element is the handle of the element of interest. Name receives the internal copy of the element name. Ninstances receives the number of instances of this element in the file. Both name and ninstances can be NULL.

Returns 1 in case of success, 0 otherwise.

int ply_get_property_info(
   p_ply_property property,
   const char** name,
   e_ply_type *type,
   e_ply_type *length_type,
   e_ply_type *value_type
)

Retrieves information from a property handle.

Property is the handle of the property of interest. Name receives the internal copy of the property name. Type receives the property type. Length_type receives the scalar type of the first entry in a list property (the one that gives the number of entries). Value_type receives the scalar type of the remaining list entries. Name, type, length_type, and value_type can be NULL.

Returns 1 in case of success, 0 otherwise.

Note: Length_type and value_type can receive any of the constants for scalar types defined in e_ply_type. Type can, in addition, be PLY_LIST, in which case the property is a list property and the fields length_type and value_type become meaningful.

p_ply ply_create(const char *name, e_ply_storage_mode storage_mode, p_ply_error_cb error_cb)

Creates a PLY file for writing.

Name is the file name, storage_mode is the file storage mode (PLY_ASCII, PLY_LITTLE_ENDIAN, PLY_BIG_ENDIAN, or PLY_DEFAULT to automatically detect host endianess). Error_cb is a function to be called when an error is found. Arguments idata and pdata are available to the error callback via the ply_get_ply_user_data function. If error_cb is NULL, the default error callback is used. It prints a message to the standard error stream.

Returns a handle to the file or NULL on error.

Note: Error_cb is of type void (*p_ply_error_cb)(const char *message)

p_ply ply_create_to_file(FILE *file_pointer, e_ply_storage_mode storage_mode, p_ply_error_cb error_cb)

Creates a PLY file to be written to a FILE pointer and returns a handle to it. The handle can be used wherever a handle returned by ply_create is accepted.

File_pointer a pointer to a file open for writing, storage_mode is the file storage mode (PLY_ASCII, PLY_LITTLE_ENDIAN, PLY_BIG_ENDIAN, or PLY_DEFAULT to automatically detect host endianess). Error_cb is a function to be called when an error is found. Arguments idata and pdata are available to the error callback via the ply_get_ply_user_data function. If error_cb is NULL, the default error callback is used. It prints a message to the standard error stream.

Returns a handle to the file or NULL on error.

Note: Error_cb is of type void (*p_ply_error_cb)(const char *message)

Note: This function is declared in header rplyfile.h.

int ply_add_element(p_ply ply, const char *name, long ninstances)

Adds a new element to the ply file.

Ply is a handle returned by ply_create, name is the element name and ninstances is the number of instances of this element that will be written to the file.

Returns 1 in case of success, 0 otherwise.

int ply_add_property(
   p_ply ply,
   const char *name,
   e_ply_type type,
   e_ply_type length_type,
   e_ply_type value_type
)

Adds a new property to the last element added to the ply file.

Ply is a handle returned by ply_create and name is the property name. Type is the property type. Length_type is the scalar type of the first entry in a list property (the one that gives the number of entries). Value_type is the scalar type of the remaining list entries. If type is not PLY_LIST, length_type and value_type are ignored.

Returns 1 in case of success, 0 otherwise.

Note: Length_type and value_type can be any of the constants for scalar types defined in e_ply_type. Type can, in addition, be PLY_LIST, in which case the property is a list property and the fields length_type and value_type become meaningful.

int ply_add_list_property(
   p_ply ply,
   const char *name,
   e_ply_type length_type,
   e_ply_type value_type
)

Same as ply_add_property if type is PLY_LIST.

int ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type)

Same as ply_add_property if type is not PLY_LIST.

int ply_add_comment(p_ply ply, const char *comment);

Adds a comment to a PLY file.

Ply is a handle returned by ply_create and comment is the comment text.

Returns 1 in case of success, 0 otherwise.

int ply_add_obj_info(p_ply ply, const char *obj_info);

Adds a obj_info to a PLY file.

Ply is a handle returned by ply_create and obj_info is the obj_info text.

Returns 1 in case of success, 0 otherwise.

int ply_write_header(p_ply ply);

Writes the PLY file header to disk, after all elements, properties, comments and obj_infos have been added to the handle.

Ply is a handle returned by ply_create and comment is the comment text.

Returns 1 in case of success, 0 otherwise.

int ply_write(p_ply ply, double value);

Passes a value to be stored in the PLY file. Values must be passed in the order they will appear in the file.

Ply is a handle returned by ply_create and value is the value to be stored. For simplicity, values are always passed as double and conversion is performed as needed.

Returns 1 in case of success, 0 otherwise.

int ply_close(p_ply ply);

Closes the handle and ensures that all resources have been freed and data have been written.

Ply is a handle returned by ply_create or by ply_open.

Returns 1 in case of success, 0 otherwise.

rply-1.1.4/manual/reference.css000644 000765 000024 00000001666 12565712037 016703 0ustar00diegostaff000000 000000 body { margin-left: 1em; margin-right: 1em; font-family: "Verdana", sans-serif; } tt { font-family: "Andale Mono", monospace; } h1, h2, h3, h4 { margin-left: 0em; } h3 { padding-top: 1em; } p { margin-left: 1em; } p.name { font-family: "Andale Mono", monospace; padding-top: 1em; margin-left: 0em; } a[href] { color: #7f0000; } blockquote { margin-left: 3em; } pre.example { background: #cbb; padding: 1em; margin-left: 1em; font-family: "Andale Mono", monospace; font-size: small; } hr { margin-left: 0em; background: #7f0000; border: 0px; height: 1px; } ul { list-style-type: disc; } table.index { border: 1px #7f0000; } table.index td { text-align: left; vertical-align: top; } table.index ul { padding-top: 0em; margin-top: 0em; } h1:first-letter, h2:first-letter, h2:first-letter, h3:first-letter { color: #7f0000; } div.header, div.footer { margin-left: 0em; } rply-1.1.4/manual/rply.png000644 000765 000024 00000014130 12565712037 015715 0ustar00diegostaff000000 000000 PNG  IHDRd{ M pHYsiTStIME  LxIDATx}w\W3[(bID5JD4M)(bÒ5FAb(MDA0ł5ym UI,[~Xg~/wχFg9g=gR'cǾ;e :|@'777m=CCq㴵>$[w?IP(6-YP VP*x"BBvt\֌_RRK~02ex! <˗.ђm20`H m+WҒ+uo4x*.{=ܽkMHX*iIgd,+cwiItt iYtX׏ĸo'u'i_$WHBGGei:.$,sU e 9w]![· K<)mi0LJ7@CCB^ݻ'Kq9:֓0 cǩ[ /wn({{===zh5{M3ݻ9!dAjnnuuuH$YYYHM̆);0z<͜9r_ *36''1mÔ{_=8Z"#5K*r&㲲'OLVx<ۜ^^G/} 2g|D™DZ1g6AsaJ?޿j>XddstXja7T_^@ψss&,33Wu`jy;T]|WTWuuuIĀMNʉ;ʿZƲ+VKx+:zܹ|x?';{Ǐq&&RlfYHOOIm-g1`:T&Z's_":'1=-3I1SJz;}_b";{ӣ0DLYfo 5K/~YfgggX({x/g\ozOخX%{X(<'7&k֬LV]UU /l|>TTE ޜWQ;Ǐ>-]$bLn[Xr`=I9'͒Jʿ1D{ lƹm*̀`KI"r ?hzc?*'{Ϝheo/(ry*!ϵ>~,aJ֬i?J׭[gΟOEnm 08qT$**ٙ}$qQQ-\._@űgp0ΝWuY;][!0!d 0绎׏٭[7ɓ+)-_8ttگM( od6 wvȩ1>}Eŕ +M`ݘ1( P_͛gYVP _zҷ`08=-a%rvu& ô͏H5_PSSyr &yyV~eiu'0AJ />|y WV>mbnCX0u* X߲EG,Ҍ͘a<V;:=|ȍ[^4N^-,jF/!0vv)ɴ׶uxˍNuAƩS)jUVV1襎}kYbˍk8AX篷Km"'VVsq~*'8㗟m+]T\,%l6::&%&d:0uu3u"*(4ujRRRgr3Ue寮F/`UPCK! `UY+W1i6EE~&5Ww`luF/0d}bB«NW\7(`ԄbT*cǒ꒓ -pG~iLfj|V`9[JKYU5| ww-Bwoξ}Ҍ@sٮ_f^~1ÙٳπVFFݽkllLE"\1!k锶``GG?0< @-q0۷Cegt:@FFT&?x \J$/=3!@tRTЏD:@D"z{ƌ۷G+-j{CFFF.ix&ZGgyb([7nOAA|z=0:OKdYYU?l{/u(>P( ?/4- N;9F(j z=TSsRl,PNjsxSSS[U.IG]bCCK^P[Xv@^nn.^L`!ś7oYGsp w&<55Ύ^2e͛7^ZZCBbgyjj-؈zy$LYY@f__J @Khh3Jş6Z3Pؘ$L yef_$]ifhSSO;:/˚#.p&`JHI``@[WB.ӂ)5Z5)OM2Aj:_6hiSjvW;~Yuwouh{| B/  )߶m^hk:ӯҹ+ӣlm%L'*vvs鑓'?^W{̙3IlŶm?YT*?ǏsS | E;:Vggsf`K4lsOs&ڿ6Yӌ kWxUw$[H$sTn߾z踅L6] HO9Hf͊70!zw:!zr׮: RqGŷQ4J<b0@$|y=4M@اrMR@5}!Pܻ3"M X%BwFF3"6'2yjOŒ{}2q"3n5j=c<=H [gIILK4K}J7[UUEQŘ)GKRQQW/RҠUhI!Ӧ6(ѣ9/PVVmcsI >X"в(@/P  XCcʕv*تSؑ#x233Wd{zc;&B.**V[{rLIm--˲)II)| *,(5ӥKtDϯ]pCfnXPP7t舚%swmzW;.]BB8,zMLJ{O7msJOΝ{::c-Q!ԩ/5Rk0E]Y#":4 ##==4i+N͟u$њ?[A^bRi_-, ޞ| z􌎌(Z:_kD&` ,bΜVhΞLyĉZ |H0…'NR$ S|M N^2ͬY| `LuZY0:sKw/c΍8~/.9@3gV̙Fį38,ԍ }p`T]ݹO?8~ŽnbΜ7a8…y>g|VzB߹P`t}yNxx^]}56X.C_} &曙3=n2t66\'m[u+%v୆@ܢEe?<֕+Ba&&& L~^n{{Ya[); l@pb={hC=+.33ju92c^q8mhhѤI4MC'5}ۂ O !Dem=̌$%%Ewkk] `r2/`XK/Z67:X A)ĢaQ,{*!T$!]Ԩ|SSJe|vÜY=~\[[Kui|?{6vw/*,G>è8}{f)|gfKˠR֏x K@{@40ARѲH$5 Ieٻwܺ5ʸfm޽>˖U7IBv h- ;xpw*mԔ59ZZaG /U:B6: ._ԟSC#1MLJTke @b\~a`B[s`ٸ0Jxqѣ|zrg0!}};^#//]GֆT ?N .}yxH7҃T _ߝ[tF?4QuIJ;7ozT^Z848v<*E3;^#KQ| щ^_lٱ_B3S;^ 0ʒ%GLJ}ӦΡ ŕ> ֌G Btk˖F/.9A|}grC~y8,T'6u&E͜ý3PzqcKG_=yyaOH*K:Ԥ^,ԨTRoFH^^)eem JUi5֦0\G$>HIn.YbM*tL~ĝ;+*n4wbzAII΅ ʼncKD54D#]+$YmwذA]R_^mz#$z%eĶ ,;rDN" InR`"z,m,Չ[ T[e=9_. `/qCĒJ7o6ISLR"i1;wDc_ƔպȐuVhvNVr}퉉])۵XvK11*Y39v>];k5g1n ZšA@r%T"aXrVU~իutt8|7mBzjO?/Vk+,vL6/45i @b||1|}XjIENDB`rply-1.1.4/etc/convert.c000644 000765 000024 00000012060 12565712037 015343 0ustar00diegostaff000000 000000 #include #include #include #include #include "rply.h" /* internal function prototypes */ static void error(const char *fmt, ...); static void help(void); static void parse_arguments(int argc, char **argv, e_ply_storage_mode *storage_mode, const char **iname, const char **oname); static void setup_callbacks(p_ply iply, p_ply oply); /* given a format mode, an input file name and an output file name, * convert input file to output in given format mode */ int main(int argc, char **argv) { const char *value = NULL; e_ply_storage_mode storage_mode = PLY_LITTLE_ENDIAN; const char *iname = NULL, *oname = NULL; p_ply iply = NULL, oply = NULL; /* parse command line arguments */ parse_arguments(argc, argv, &storage_mode, &iname, &oname); /* open input file and make sure we parsed its header */ iply = ply_open(iname, NULL, 0, NULL); if (!iply) error("Unable to open file '%s'", iname); if (!ply_read_header(iply)) error("Failed reading '%s' header", iname); /* create output file */ oply = ply_create(oname, storage_mode, NULL, 0, NULL); if (!oply) error("Unable to create file '%s'", oname); /* create elements and properties in output file and * setup callbacks for them in input file */ setup_callbacks(iply, oply); /* pass comments and obj_infos from input to output */ value = NULL; while ((value = ply_get_next_comment(iply, value))) if (!ply_add_comment(oply, value)) error("Failed adding comments"); value = NULL; while ((value = ply_get_next_obj_info(iply, value))) if (!ply_add_obj_info(oply, value)) error("Failed adding comments"); /* write output header */ if (!ply_write_header(oply)) error("Failed writing '%s' header", oname); /* read input file generating callbacks that pass data to output file */ if (!ply_read(iply)) error("Conversion failed"); /* close up, we are done */ if (!ply_close(iply)) error("Error closing file '%s'", iname); if (!ply_close(oply)) error("Error closing file '%s'", oname); return 0; } /* prints an error message and exits */ static void error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); exit(1); } /* prints the help message and exits */ static void help(void) { error("Usage:\n" " convert