/*
* Copyright (c) 2006-2007 Hypertriton, Inc
*
* 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;
TAILQ_HEAD_(ply_element) elements;
} PLY_Info;
static void
InitPLY(PLY_Info *ply)
{
ply->format = PLY_ASCII;
TAILQ_INIT(&ply->elements);
}
static void
FreePLY(PLY_Info *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 *s)
{
const char *c;
for (c = &s[0]; *c != '\0'; c++) {
if (!isprint(*c)) {
AG_SetError("Illegal PLY element name `%s'", s);
return (0);
}
}
return (1);
}
static __inline__ enum ply_prop_type
GetType(const char *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 *s, Uint *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 *s, M_Real *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 *f,
Uint *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 *f,
M_Real *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 *v, PLY_Prop *prop, M_Real fv, Uint flags)
{
struct ply_vertex_prop {
enum ply_std_prop std;
void *p;
int inVec;
int conv8;
Uint flags;
} vProps[] = {
{ PLY_X, &v->v.x, 1, 0, 0 },
{ PLY_Y, &v->v.y, 1, 0, 0 },
{ PLY_Z, &v->v.z, 1, 0, 0 },
{ PLY_NX, &v->n.x, 1, 0, SG_PLY_LOAD_VTX_NORMALS },
{ PLY_NY, &v->n.y, 1, 0, SG_PLY_LOAD_VTX_NORMALS },
{ PLY_NZ, &v->n.z, 1, 0, SG_PLY_LOAD_VTX_NORMALS },
{ PLY_U, &v->st.x, 0, 0, SG_PLY_LOAD_TEXCOORDS },
{ PLY_V, &v->st.y, 0, 0, SG_PLY_LOAD_TEXCOORDS },
{ PLY_RED, &v->c.r, 1, 1, SG_PLY_LOAD_VTX_COLORS },
{ PLY_GREEN, &v->c.g, 1, 1, SG_PLY_LOAD_VTX_COLORS },
{ PLY_BLUE, &v->c.b, 1, 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 *so, Uint *v, Uint nind, PLY_Element *elem, Uint *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 *so, const SG_Vertex *v, Uint i, Uint *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 *so, PLY_Info *ply, FILE *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 *so, PLY_Info *ply, FILE *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);
}