/* * Copyright (c) 2006-2019 Julien Nadeau Carriere * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Loader for Stanford PLY file format. */ #include #include #include #include #include #include #include #include #define PLY_MAX_HEADER 256 #define PLY_MAX_ELEMENT_NAME 32 #define PLY_MAX_PROP_NAME 32 #define PLY_MAX_LIST_ITEMS 128 enum ply_prop_type { PLY_INT8, PLY_INT16, PLY_INT32, PLY_UINT8, PLY_UINT16, PLY_UINT32, PLY_FLOAT32, PLY_FLOAT64, PLY_LAST_TYPE }; static const char *plyTypeNames[] = { "int8", "int16", "int32", "uint8", "uint16", "uint32", "float32", "float64" }; static const char *plyTypeNamesLegacy[] = { "char", "short", "int", "uchar", "ushort", "uint", "float", "double" }; enum ply_std_prop { PLY_VERTEX_INDICES, PLY_X, PLY_Y, PLY_Z, PLY_NX, PLY_NY, PLY_NZ, PLY_U, PLY_V, PLY_RED, PLY_GREEN, PLY_BLUE, PLY_INTENSITY, PLY_OPACITY, PLY_AMBIENT_COEFF, PLY_AMBIENT_RED, PLY_AMBIENT_GREEN, PLY_AMBIENT_BLUE, PLY_DIFFUSE_COEFF, PLY_DIFFUSE_RED, PLY_DIFFUSE_GREEN, PLY_DIFFUSE_BLUE, PLY_SPECULAR_COEFF, PLY_SPECULAR_RED, PLY_SPECULAR_GREEN, PLY_SPECULAR_BLUE, PLY_SPECULAR_POWER, PLY_STD_PROP_LAST }; static const char *plyStdPropNames[] = { "vertex_indices", "x", "y", "z", "nx", "ny", "nz", "u", "v", "red", "green", "blue", "intensity", "opacity", "ambient_coeff", "ambient_red", "ambient_green", "ambient_blue", "diffuse_coeff", "diffuse_red", "diffuse_green", "diffuse_blue", "specular_coeff", "specular_red", "specular_green", "specular_blue", "specular_power" }; enum ply_std_element { PLY_VERTEX, PLY_FACE, PLY_MATERIAL, PLY_STD_ELEMENT_LAST }; static const char *plyStdElementNames[] = { "vertex", "face", "material" }; typedef struct ply_prop { char name[PLY_MAX_PROP_NAME]; enum ply_std_prop std; /* Standard property */ enum ply_prop_type type; /* Type of item */ int list; /* Is a variable-sized list */ enum ply_prop_type list_type; /* Type of list count number */ TAILQ_ENTRY(ply_prop) props; } PLY_Prop; typedef struct ply_element { char name[PLY_MAX_ELEMENT_NAME]; enum ply_std_element std; /* Standard property */ Uint count; /* Number of instances */ TAILQ_HEAD_(ply_prop) props; /* Properties per instance */ TAILQ_ENTRY(ply_element) elements; } PLY_Element; typedef struct ply_info { enum ply_format { PLY_ASCII, PLY_BIN_BE, PLY_BIN_LE } format; Uint32 _pad; TAILQ_HEAD_(ply_element) elements; } PLY_Info; static void InitPLY(PLY_Info *_Nonnull ply) { ply->format = PLY_ASCII; TAILQ_INIT(&ply->elements); } static void FreePLY(PLY_Info *_Nonnull ply) { PLY_Element *el, *el2; PLY_Prop *prop, *nprop; for (el = TAILQ_FIRST(&ply->elements); el != TAILQ_END(&ply->elements); el = el2) { el2 = TAILQ_NEXT(el, elements); for (prop = TAILQ_FIRST(&el->props); prop != TAILQ_END(&el->props); prop = nprop) { nprop = TAILQ_NEXT(prop, props); Free(prop); } Free(el); } } static int IsValidElementName(const char *_Nonnull s) { const char *c; for (c = &s[0]; *c != '\0'; c++) { if (!isprint((int)*c)) { AG_SetError("Illegal PLY element name `%s'", s); return (0); } } return (1); } static __inline__ enum ply_prop_type GetType(const char *_Nonnull name) { int i; for (i = 0; i < PLY_LAST_TYPE; i++) { if (AG_Strcasecmp(name, plyTypeNames[i]) == 0 || AG_Strcasecmp(name, plyTypeNamesLegacy[i]) == 0) { return (i); } } AG_SetError("Bad prop type `%s'", name); return (PLY_LAST_TYPE); } static __inline__ int GetUintASCII(const char *_Nonnull s, Uint *_Nonnull rv) { Uint v; char *c; v = (Uint)strtoul(s, &c, 10); if (s[0] == '\0' || *c != '\0') { AG_SetError("Malformed int `%s'", s); return (-1); } *rv = v; return (0); } static __inline__ int GetRealASCII(const char *_Nonnull s, M_Real *_Nonnull rv) { double v; char *c; v = (M_Real)strtod(s, &c); if (s[0] == '\0' || *c != '\0') { AG_SetError("Malformed number `%s'", s); return (-1); } *rv = v; return (0); } static __inline__ int GetUintBinary(enum ply_format fmt, enum ply_prop_type type, FILE *_Nonnull f, Uint *_Nullable rv) { union { Uint8 u8; Uint32 u16; Uint32 u32; } data; switch (type) { case PLY_INT8: case PLY_UINT8: if (fread(&data.u8, sizeof(Uint8), 1, f) < 1) { AG_SetError("Read error"); return (-1); } if (rv != NULL) { *rv = (Uint)data.u8; } break; case PLY_INT16: case PLY_UINT16: if (fread(&data.u16, sizeof(Uint16), 1, f) < 1) { AG_SetError("Read error"); return (-1); } if (rv != NULL) { *rv = (fmt == PLY_BIN_BE) ? (Uint)AG_SwapBE16(data.u16) : (Uint)AG_SwapLE16(data.u16); } break; case PLY_INT32: case PLY_UINT32: if (fread(&data.u32, sizeof(Uint32), 1, f) < 1) { AG_SetError("Read error"); return (-1); } if (rv != NULL) { *rv = (fmt == PLY_BIN_BE) ? (Uint)AG_SwapBE32(data.u32) : (Uint)AG_SwapLE32(data.u32); } break; default: AG_SetError("Invalid int type: %d", type); return (-1); } return (0); } static __inline__ int GetRealBinary(enum ply_format fmt, enum ply_prop_type type, FILE *_Nonnull f, M_Real *_Nonnull rv) { union { Uint8 u8; Sint8 s8; Uint16 u16; Sint16 s16; Uint32 u32; Sint32 s32; float flt; double dbl; } data; switch (type) { case PLY_UINT8: if (fread(&data.u8, sizeof(Uint8), 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (M_Real)data.u8; break; case PLY_INT8: if (fread(&data.s8, sizeof(Sint8), 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (M_Real)data.s8; break; case PLY_UINT16: if (fread(&data.u16, sizeof(Uint16), 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (fmt == PLY_BIN_BE) ? (M_Real)AG_SwapBE16(data.u16) : (M_Real)AG_SwapLE16(data.u16); break; case PLY_INT16: if (fread(&data.s16, sizeof(Sint16), 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (fmt == PLY_BIN_BE) ? (M_Real)AG_SwapBE16(data.s16) : (M_Real)AG_SwapLE16(data.s16); break; case PLY_UINT32: if (fread(&data.u32, sizeof(Uint32), 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (fmt == PLY_BIN_BE) ? (M_Real)AG_SwapBE32(data.u32) : (M_Real)AG_SwapLE32(data.u32); break; case PLY_INT32: if (fread(&data.s32, sizeof(Sint32), 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (fmt == PLY_BIN_BE) ? (M_Real)AG_SwapBE32(data.s32) : (M_Real)AG_SwapLE32(data.s32); break; case PLY_FLOAT32: if (fread(&data.flt, 4, 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (M_Real)data.flt; break; case PLY_FLOAT64: if (fread(&data.dbl, 8, 1, f) < 1) { AG_SetError("Read error"); return (-1); } *rv = (M_Real)data.dbl; break; default: AG_SetError("Invalid real type: %d", type); return (-1); } return (0); } static __inline__ void SetVertexProps(SG_Vertex *_Nonnull v, PLY_Prop *_Nonnull prop, M_Real fv, Uint flags) { struct ply_vertex_prop { enum ply_std_prop std; /* PLY property */ int inVec; /* Is a vector? */ void *p; /* Address */ int conv8; /* Convert to 8-bit */ Uint flags; } vProps[] = { { PLY_X, 1, &v->v.x, 0, 0 }, { PLY_Y, 1, &v->v.y, 0, 0 }, { PLY_Z, 1, &v->v.z, 0, 0 }, { PLY_NX, 1, &v->n.x, 0, SG_PLY_LOAD_VTX_NORMALS }, { PLY_NY, 1, &v->n.y, 0, SG_PLY_LOAD_VTX_NORMALS }, { PLY_NZ, 1, &v->n.z, 0, SG_PLY_LOAD_VTX_NORMALS }, { PLY_U, 0, &v->st.x, 0, SG_PLY_LOAD_TEXCOORDS }, { PLY_V, 0, &v->st.y, 0, SG_PLY_LOAD_TEXCOORDS }, { PLY_RED, 1, &v->c.r, 1, SG_PLY_LOAD_VTX_COLORS }, { PLY_GREEN, 1, &v->c.g, 1, SG_PLY_LOAD_VTX_COLORS }, { PLY_BLUE, 1, &v->c.b, 1, SG_PLY_LOAD_VTX_COLORS }, }; const int nprops = sizeof(vProps)/sizeof(vProps[0]); int i; for (i = 0; i < nprops; i++) { struct ply_vertex_prop *vp = &vProps[i]; if ((vp->flags != 0 && (vp->flags & flags) == 0) || vp->std != prop->std) { continue; } if (vp->inVec) { #if defined(SINGLE_PRECISION) || defined(HAVE_SSE) *(float *)vp->p = vp->conv8 ? ((float)fv)/255.0f : fv; #elif defined(DOUBLE_PRECISION) *(double *)vp->p = vp->conv8 ? ((double)fv)/255.0 : fv; #elif defined(QUAD_PRECISION) *(long double *)vp->p = vp->conv8 ? ((long double)fv)/255.0L : fv; #endif } else { *(M_Real *)vp->p = vp->conv8 ? fv/255.0 : fv; } } } /* * Insert a new facet. We don't check facets for validity or duplicates, * and we only support triangles and quads. */ static __inline__ int InsertFacet(SG_Object *_Nonnull so, Uint *_Nonnull v, Uint nind, PLY_Element *_Nonnull elem, Uint *_Nonnull map, Uint mapSize, Uint flags) { switch (nind) { case 3: if (v[0] >= mapSize || v[1] >= mapSize || v[2] >= mapSize) { AG_SetError("Bad refs in triangle [%u,%u,%u]", v[0], v[1], v[2]); return (-1); } SG_FacetFromTri3(so, map[v[0]], map[v[1]], map[v[2]]); break; case 4: if (v[0] >= mapSize || v[1] >= mapSize || v[2] >= mapSize || v[3] >= mapSize) { AG_SetError("Bad refs in quad [%u,%u,%u,%u]", v[0], v[1], v[2], v[3]); return (-1); } SG_FacetFromQuad4(so, map[v[0]], map[v[1]], map[v[2]], map[v[3]]); break; default: AG_SetError("%d-sided face!", nind); return (-1); } return (0); } /* * Insert a new vertex. If requested, we check for duplicates and remap * them, but that is extremely slow since no space partitioning is done. */ static __inline__ int InsertVertex(SG_Object *_Nonnull so, const SG_Vertex *_Nonnull v, Uint i, Uint *_Nonnull vtxMap, Uint flags) { Uint j; if (flags & SG_PLY_DUP_VERTICES) { for (j = 1; j < so->nVtx; j++) { SG_Vertex *ve = &so->vtx[j]; if (ve->v.x != v->v.x || ve->v.y != v->v.y || ve->v.z != v->v.z) { continue; } ve->n = v->n; ve->c = v->c; ve->st = v->st; vtxMap[i] = j; /* Map existing */ break; } } else { j = so->nVtx; } if (j == so->nVtx) { SG_Vertex *vNew; if ((vNew = realloc(so->vtx, (so->nVtx+1)*sizeof(SG_Vertex))) == NULL) { AG_SetError("Out of memory for vertices"); return (-1); } so->vtx = vNew; vNew = &so->vtx[so->nVtx]; vNew->v = v->v; vNew->n = v->n; vNew->c = v->c; vNew->st = v->st; vNew->flags = 0; vtxMap[i] = so->nVtx++; /* Map new */ } return (0); } static int LoadASCII(SG_Object *_Nonnull so, PLY_Info *_Nonnull ply, FILE *_Nonnull f, Uint flags) { char line[4096], *s, *c; Uint *vtxMap = NULL, mapSize = 0; PLY_Element *el; PLY_Prop *prop; Uint i; TAILQ_FOREACH(el, &ply->elements, elements) { switch (el->std) { case PLY_VERTEX: if (vtxMap != NULL) { AG_SetError("Multiple vertex elements"); Free(vtxMap); goto fail; } mapSize = el->count; if ((vtxMap = malloc(mapSize*sizeof(Uint))) == NULL) { AG_SetError("Out of memory for vtxmap"); goto fail; } break; case PLY_FACE: /* * Optimize based on assumption that object has * an Euler characteristic of 2. */ (void)SG_EdgeRehash(so, (Uint)el->count + so->nVtx - 2); (void)SG_FacetRehash(so, so->nEdgeTbl); break; default: break; } for (i = 0; i < el->count; i++) { SG_Vertex vTmp; vTmp.n = M_VecZero3(); vTmp.st = M_VecZero2(); vTmp.c.r = 0.5; vTmp.c.g = 0.5; vTmp.c.b = 0.5; if ((s = fgets(line, sizeof(line), f)) == NULL || (c = strchr(line, '\n')) == NULL) { AG_SetError("Premature end of PLY file"); goto fail; } *c = '\0'; TAILQ_FOREACH(prop, &el->props, props) { char *num = AG_Strsep(&s, " "); M_Real fv; Uint face[4], count = 0, j; if (prop->list) { if (GetUintASCII(num, &count) == -1) { goto fail; } if (el->std == PLY_FACE && prop->std == PLY_VERTEX_INDICES) { if (count > 4) { AG_SetError( "%d-sided faces not" " supported", count); goto fail; } for (j = 0; j < count; j++) if (GetUintASCII( AG_Strsep(&s, " "), &face[j]) == -1) goto fail; } else { /* Skip */ for (j = 0; j < count; j++) AG_Strsep(&s, " "); } } else { if (GetRealASCII(num, &fv) == -1) { goto fail; } if (el->std == PLY_VERTEX) SetVertexProps(&vTmp, prop, fv, flags); } if (el->std == PLY_FACE) { if (vtxMap == NULL) { AG_SetError("Face element " "without vertices"); goto fail; } if (InsertFacet(so, face, count, el, vtxMap, mapSize, flags) == -1) goto fail; } } if (el->std == PLY_VERTEX) InsertVertex(so, &vTmp, i, vtxMap, flags); } } Free(vtxMap); return (0); fail: Free(vtxMap); return (-1); } static int LoadBinary(SG_Object *_Nonnull so, PLY_Info *_Nonnull ply, FILE *_Nonnull f, Uint flags) { Uint *vtxMap = NULL, mapSize = 0; PLY_Element *el; PLY_Prop *prop; Uint i, j; TAILQ_FOREACH(el, &ply->elements, elements) { switch (el->std) { case PLY_VERTEX: if (vtxMap != NULL) { AG_SetError("Multiple vertex elements"); Free(vtxMap); goto fail; } mapSize = el->count; if ((vtxMap = malloc(mapSize*sizeof(Uint))) == NULL) { AG_SetError("Out of memory for dupmap"); goto fail; } break; case PLY_FACE: /* * Optimize based on assumption that object has * an Euler characteristic of 2. */ SG_EdgeRehash(so, (Uint)el->count + so->nVtx - 2); break; default: break; } for (i = 0; i < el->count; i++) { SG_Vertex vTmp; vTmp.n = M_VecZero3(); vTmp.st = M_VecZero2(); vTmp.c.r = 0.5; vTmp.c.g = 0.5; vTmp.c.b = 0.5; TAILQ_FOREACH(prop, &el->props, props) { Uint face[4], count = 0; M_Real fv; if (prop->list) { if (GetUintBinary(ply->format, prop->list_type, f, &count) == -1) { goto fail; } if (el->std == PLY_FACE && prop->std == PLY_VERTEX_INDICES) { if (count > 4) { AG_SetError( "%d-sided faces not" " supported", count); goto fail; } for (j = 0; j < count; j++) if (GetUintBinary( ply->format, prop->type, f, &face[j]) == -1) goto fail; } else { /* Skip */ for (j = 0; j < count; j++) (void)GetUintBinary( ply->format, prop->type, f, NULL); } } else { if (GetRealBinary(ply->format, prop->type, f, &fv) == -1) { goto fail; } if (el->std == PLY_VERTEX) SetVertexProps(&vTmp, prop, fv, flags); } if (el->std == PLY_FACE && prop->std == PLY_VERTEX_INDICES) { if (vtxMap == NULL) { AG_SetError("Face element " "without vertices"); goto fail; } if (InsertFacet(so, face, count, el, vtxMap, mapSize, flags) == -1) goto fail; } } if (el->std == PLY_VERTEX) InsertVertex(so, &vTmp, i, vtxMap, flags); } } Free(vtxMap); return (0); fail: Free(vtxMap); return (-1); } int SG_ObjectLoadPLY(void *obj, const char *path, Uint flags) { SG_Object *so = obj; PLY_Info ply; PLY_Element *cur_el = NULL; char line[4096], *s; char sig[4]; FILE *f; int i, nline = 0; char *c; if ((f = fopen(path, "r")) == NULL) { AG_SetError("%s: %s", path, strerror(errno)); return (-1); } if (fread(sig, sizeof(sig), 1, f) < 1 || strncmp(sig, "ply\n", 4) != 0) { AG_SetError("Not a Stanford PLY file"); fclose(f); return (-1); } InitPLY(&ply); /* Load the PLY header information */ for (nline = 0; (nline < PLY_MAX_HEADER) && (s = fgets(line, sizeof(line), f)) != NULL; nline++) { char *key, *v1, *v2, *v3, *v4; if ((c = strchr(s, '\n')) != NULL) { *c = '\0'; } else { AG_SetError("Line too long"); goto fail; } if (strcmp(line, "end_header") == 0) { break; } key = AG_Strsep(&s, " "); if (strcmp(key, "format") == 0) { if ((v1 = AG_Strsep(&s, " ")) == NULL) { AG_SetError("Bad format line"); goto fail; } if (strcmp(v1, "ascii") == 0) { ply.format = PLY_ASCII; } else if (strcmp(v1, "binary_little_endian") == 0) { ply.format = PLY_BIN_LE; } else if (strcmp(v1, "binary_big_endian") == 0) { ply.format = PLY_BIN_BE; } else { AG_SetError("Unrecognized format `%s'", v1); goto fail; } } else if (strcmp(key, "element") == 0) { PLY_Element *el; Uint count; if ((v1 = AG_Strsep(&s, " ")) == NULL || (v2 = AG_Strsep(&s, " ")) == NULL) { AG_SetError("Bad element line"); goto fail; } if (!IsValidElementName(v1)) { goto fail; } count = strtoul(v2, &c, 10); if (v2[0] == '\0' || *c != '\0') { AG_SetError("Bad element count `%s'", v2); goto fail; } el = Malloc(sizeof(PLY_Element)); Strlcpy(el->name, v1, sizeof(el->name)); el->count = count; TAILQ_INIT(&el->props); TAILQ_INSERT_TAIL(&ply.elements, el, elements); cur_el = el; for (i = 0; i < PLY_STD_ELEMENT_LAST; i++) { if (strcmp(el->name, plyStdElementNames[i]) == 0) break; } el->std = (enum ply_std_element)i; } else if (strcmp(key, "property") == 0) { PLY_Prop *prop; if (cur_el == NULL) { AG_SetError("Properties must follow elements"); goto fail; } if ((v1 = AG_Strsep(&s, " ")) == NULL || (v2 = AG_Strsep(&s, " ")) == NULL) { AG_SetError("Bad property line"); goto fail; } prop = Malloc(sizeof(PLY_Prop)); if (strcmp(v1, "list") == 0) { if ((v3 = AG_Strsep(&s, " ")) == NULL || (v4 = AG_Strsep(&s, " ")) == NULL) { AG_SetError("Bad property list line"); Free(prop); goto fail; } if ((prop->list_type = GetType(v2)) == PLY_LAST_TYPE || (prop->type = GetType(v3)) == PLY_LAST_TYPE || !IsValidElementName(v4)) { Free(prop); goto fail; } Strlcpy(prop->name, v4, sizeof(prop->name)); prop->list = 1; } else { if ((prop->type = GetType(v1)) == PLY_LAST_TYPE || !IsValidElementName(v2)) { Free(prop); goto fail; } Strlcpy(prop->name, v2, sizeof(prop->name)); prop->list = 0; } for (i = 0; i < PLY_STD_PROP_LAST; i++) { if (strcmp(prop->name, plyStdPropNames[i]) == 0) break; } prop->std = (enum ply_std_prop)i; TAILQ_INSERT_TAIL(&cur_el->props, prop, props); } } AG_ObjectLock(so); switch (ply.format) { case PLY_ASCII: if (LoadASCII(so, &ply, f, flags) == -1) { AG_ObjectUnlock(so); goto fail; } break; case PLY_BIN_BE: case PLY_BIN_LE: if (LoadBinary(so, &ply, f, flags) == -1) { AG_ObjectUnlock(so); goto fail; } break; } AG_ObjectUnlock(so); fclose(f); FreePLY(&ply); return (0); fail: fclose(f); FreePLY(&ply); return (-1); }