OpenSceneGraph/src/osgPlugins/ac3d/ac3d.cpp

776 lines
24 KiB
C++
Raw Normal View History

// 30 Oct 2002
// AC3D loader for models generated by the AC3D modeller (www.ac3d.org)
// part of this source code were supplied by the AC3D project (Andy Colebourne)
// eg the basic parsing of an AC3D file.
// Conversion from AC3D scenegraph to OSG by GW Michel.
#include <stdio.h>
#include <malloc.h>
#include <math.h>
#include <osg/CullFace>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry> //Set>
#include <osg/Light>
#include <osg/LightSource>
#include <osg/Material>
#include <osg/Texture2D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/Notify>
#include <osg/Texture2D>
#include <osgDB/FileNameUtils>
#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>
#include <osgUtil/Tesselator>
#include <osgUtil/SmoothingVisitor>
#include "osgac3d.h"
class ReaderWriterAC : public osgDB::ReaderWriter
{
public:
virtual const char* className() { return "AC3D Database Reader"; }
virtual bool acceptsExtension(const std::string& extension)
{
return osgDB::equalCaseInsensitive(extension,"ac");
}
virtual ReadResult readNode(const std::string& fileName,const osgDB::ReaderWriter::Options*)
{
osg::Group *grp; // holder for all loaded objects
grp=ac_load_ac3d(fileName.c_str());
return grp;
};
private:
};
static int line = 0;
static char buff[255];
static osg::Material *palette[255];
static int num_palette = 0;
static int startmatindex = 0;
osg::Material*ac_palette_get_material(int id)
{
return(palette[id]);
}
Boolean read_line(FILE *f)
{
fgets(buff, 255, f); line++;
return(TRUE);
}
int tokc = 0;
char *tokv[30];
Prototype int get_tokens(char *s, int *argc, char *argv[])
/** bung '\0' chars at the end of tokens and set up the array (tokv) and count (tokc)
like argv argc **/
{
char *p = s;
char *st;
char c;
//int n;
int tc;
tc = 0;
while ((c = *p))
{
if ((c != ' ') && (c != '\t') && (c != '\n') && ( c != 13))
{
if (c == '"')
{
c = *p++;
st = p;
while ((c = *p) && ((c != '"')&&(c != '\n')&& ( c != 13)) )
{
if (c == '\\')
strcpy(p, p+1);
p++;
}
*p=0;
argv[tc++] = st;
}
else
{
st = p;
while ((c = *p) && ((c != ' ') && (c != '\t') && (c != '\n') && ( c != 13)) )
p++;
*p=0;
argv[tc++] = st;
}
}
p++;
}
*argc = tc;
return(tc);
}
void initobject(ACObject *ob)
{
ob->loc[0] = ob->loc[1] = ob->loc[2] = 0.0;
ob->name = ob->url = NULL;
ob->data = NULL;
ob->num_vert = 0;
ob->num_surf = 0;
ob->texture = NULL;
ob->texture_repeat_x = ob->texture_repeat_y = 1.0;
ob->texture_offset_x = ob->texture_offset_y = 0.0;
ob->kids = NULL;
ob->num_kids = 0;
ob->matrix[0] = 1;
ob->matrix[1] = 0;
ob->matrix[2] = 0;
ob->matrix[3] = 0;
ob->matrix[4] = 1;
ob->matrix[5] = 0;
ob->matrix[6] = 0;
ob->matrix[7] = 0;
ob->matrix[8] = 1;
}
void init_surface(ACSurface *s)
{
s->num_vertref = 0;
s->flags = 0;
s->mat = 0;
// s->normal.x = 0.0; s->normal.z = 0.0; s->normal.z = 0.0;
}
void osgtri_calc_normal(osg::Vec3 &v1, osg::Vec3 &v2, osg::Vec3 &v3, osg::Vec3 &n)
{
osg::Vec3 side1=v2-v1;
osg::Vec3 side2=v3-v2;
n=side1^side2;
n.normalize();
}
ACSurface *read_surface(FILE *f, ACSurface *s,osg::UShortArray *nusidx,
osg::Vec2Array *tcs)
{
char t[20];
init_surface(s);
while (!feof(f))
{
read_line(f);
sscanf(buff, "%s", t);
if (streq(t, "SURF"))
{
int flgs;
if (get_tokens(buff, &tokc, tokv) != 2)
{
printf("SURF should be followed by one flags argument\n");
}
else
{
flgs = strtol(tokv[1], NULL, 0);
s->flags = flgs;
}
}
else
if (streq(t, "mat"))
{
int mindx;
sscanf(buff, "%s %d", t, &mindx);
s->mat = mindx+startmatindex;
}
else
if (streq(t, "refs"))
{
int num, n;
int ind;
osg::Vec2 tx;
sscanf(buff, "%s %d", t, &num);
s->num_vertref = num;
for (n = 0; n < num; n++)
{
fscanf(f, "%d %f %f\n", &ind, &tx[0], &tx[1]); line++;
nusidx->push_back(ind);
if (tcs) tcs->push_back(tx);
}
return(s);
}
else
printf("ignoring %s\n", t);
}
return(NULL);
}
/*void ac_object_calc_vertex_normals(ACObject *ob)
{
int s, v, vr;
/ ** for each vertex in this object ** /
for (v = 0; v < ob->num_vert; v++)
{
ACNormal n = {0, 0, 0};
int found = 0;
/ ** go through each surface ** /
for (s = 0; s < ob->num_surf; s++)
{
ACSurface *surf = &ob->surfaces[s];
/ ** check if this vertex is used in this surface ** /
/ ** if it is, use it to create an average normal ** /
for (vr = 0; vr < surf->num_vertref; vr++)
if (surf->vertref[vr] == v)
{
n.x+=surf->normal.x;
n.y+=surf->normal.y;
n.z+=surf->normal.z;
found++;
}
}
if (found > 0)
{
n.x /= found;
n.y /= found;
n.z /= found;
}
ob->vertices[v].normal = n;
}
} */
int string_to_objecttype(char *s)
{
if (streq("world", s))
return(OBJECT_WORLD);
if (streq("poly", s))
return(OBJECT_NORMAL);
if (streq("group", s))
return(OBJECT_GROUP);
if (streq("light", s))
return(OBJECT_LIGHT);
return(OBJECT_NORMAL);
}
void
protate(osg::Vec3 p, float m[9])
{ // p-> m*p, m is 3.3 matrix
osg::Vec3 t=p;
p[0]=m[0]*t[0]+m[1]*t[1]+m[2]*t[3];
p[1]=m[3]*t[0]+m[4]*t[1]+m[5]*t[3];
p[2]=m[6]*t[0]+m[7]*t[1]+m[8]*t[3];
}
osg::Group *ac_load_object(FILE *f,const ACObject *parent)
{
// most of this logic came from Andy Colebourne (developer of the AC3D editor) so it had better be right!
char t[20];
osg::Group *gp=NULL;
osg::Geode *geode=NULL;
osg::Vec3Array *normals = NULL; // new osg::Vec3Array; // NULL;
ACObject ob; // local storage for stuff taken from AC's loader
osg::Vec3Array *vertpool = new osg::Vec3Array;
if (parent) ob.loc=parent->loc; // copy loc
while (!feof(f))
{
read_line(f);
sscanf(buff, "%s", t);
if (streq(t, "MATERIAL"))
{
if (get_tokens(buff, &tokc, tokv) != 22)
{
printf("expected 21 params after \"MATERIAL\" - line %d\n", line);
}
else
{
osg::Material *numat=new osg::Material();
osg::Vec4 cdiff((float)atof(tokv[3]), (float)atof(tokv[4]),
(float)atof(tokv[5]), 1.0-(float)atof(tokv[21]));
numat->setDiffuse(osg::Material::FRONT_AND_BACK,cdiff);
osg::Vec4 camb((float)atof(tokv[7]),(float)atof(tokv[8]),
(float)atof(tokv[9]),1.0-(float)atof(tokv[21]));
numat->setAmbient(osg::Material::FRONT_AND_BACK,camb);
osg::Vec4 cspec((float)atof(tokv[15]), (float)atof(tokv[16]),
(float)atof(tokv[17]),1.0-(float)atof(tokv[21]));
numat->setSpecular(osg::Material::FRONT_AND_BACK,cspec);
osg::Vec4 cemm((float)atof(tokv[11]),(float)atof(tokv[12]),(float)atof(tokv[13]),1.0-(float)atof(tokv[21]));
numat->setSpecular(osg::Material::FRONT_AND_BACK,cemm);
//numat->setTransparency(osg::Material::FRONT_AND_BACK, 1.0-(float)atof(tokv[21]));
palette[num_palette++] = numat;
}
}
else
if (streq(t, "OBJECT"))
{
char type[20];
char str[20];
osg::Vec3 loc=ob.loc;
if (!gp) gp = new osg::Group();
initobject(&ob); // rezero data for object
ob.loc=loc;
sscanf(buff, "%s %s", str, type);
ob.type = string_to_objecttype(type);
}
else
if (streq(t, "data"))
{
if (get_tokens(buff, &tokc, tokv) != 2)
printf("expected 'data <number>' at line %d\n", line);
else
{
char *str;
int len;
len = atoi(tokv[1]);
if (len > 0)
{
str = (char *)myalloc(len+1);
fread(str, len, 1, f);
str[len] = 0;
fscanf(f, "\n"); line++;
ob.data = STRING(str);
myfree(str);
}
}
}
else
if (streq(t, "name"))
{
int numtok = get_tokens(buff, &tokc, tokv);
if (numtok != 2)
{
printf("expected quoted name at line %d (got %d tokens)\n", line, numtok);
}
else
if (gp) gp->setName(tokv[1]);
}
else
if (streq(t, "texture"))
{
if (get_tokens(buff, &tokc, tokv) != 2)
printf("expected quoted texture name at line %d\n", line);
else
{
osg::Image *ctx= osgDB::readImageFile(tokv[1]);
if (ctx) { // image coukd be read
ob.texture = new osg::Texture2D;// ac_load_texture(tokv[1]);
ob.texture->setImage(ctx);
if (ob.texture_repeat_x > 0.1) {
ob.texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
} else {
ob.texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP);
}
if (ob.texture_repeat_y > 0.1) {
ob.texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
} else {
ob.texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP);
}
}
}
}
else
if (streq(t, "texrep"))
{
if (get_tokens(buff, &tokc, tokv) != 3)
printf("expected 'texrep <float> <float>' at line %d\n", line);
else
{
ob.texture_repeat_x = atof(tokv[1]);
ob.texture_repeat_y = atof(tokv[2]);
}
}
else
if (streq(t, "texoff"))
{
if (get_tokens(buff, &tokc, tokv) != 3)
printf("expected 'texoff <float> <float>' at line %d\n", line);
else
{
ob.texture_offset_x = atof(tokv[1]);
ob.texture_offset_y = atof(tokv[2]);
}
}
else
if (streq(t, "rot"))
{
float r[9];
char str2[5];
int n;
sscanf(buff, "%s %f %f %f %f %f %f %f %f %f", str2,
&r[0], &r[1], &r[2], &r[3], &r[4], &r[5], &r[6], &r[7], &r[8] );
for (n = 0; n < 9; n++)
ob.matrix[n] = r[n];
}
else
if (streq(t, "loc"))
{
char str[5];
osg::Vec3 loc;
sscanf(buff, "%s %f %f %f", str, &loc[0], &loc[1], &loc[2]);
ob.loc+=loc;
}
else
if (streq(t, "url"))
{
if (get_tokens(buff, &tokc, tokv) != 2)
printf("expected one arg to url at line %d (got %d)\n", line, tokv);
else
ob.url = STRING(tokv[1]);
}
else
if (streq(t, "numvert"))
{
int num, n;
char str[10];
if (ob.type == OBJECT_GROUP || ob.type == OBJECT_WORLD) {
} else if (ob.type == OBJECT_NORMAL) {
if (geode) {
osgUtil::Tesselator tesselator;
for(unsigned int i=0;i<geode->getNumDrawables();++i)
{
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
if (geom) tesselator.retesselatePolygons(*geom);
}
}
geode = new osg::Geode();
gp->addChild(geode);
geode->setName(gp->getName());
normals = new osg::Vec3Array;
}
sscanf(buff, "%s %d", str, &num);
if (num > 0)
{
ob.num_vert = num;
for (n = 0; n < num; n++)
{
osg::Vec3 p;
fscanf(f, "%f %f %f\n", &p[0], &p[1], &p[2]); line++;
protate(p, ob.matrix);
vertpool->push_back(p+ob.loc);
}
}
}
else
if (streq(t, "numsurf"))
{
int num, n;
char str[10];
// this is not obvious (what is?). Each set of surfaces can have different material on each surface.
// so I set up a set of 'bins' for
// the primitives (geometry list, geomlist)
// the coords array of each geometry (Vec3Array list, vertslist)
// the tx coords array for each geometry (Vec2Array list, texslist)
// then I add a new geometry to the current Geode for each new material as it is found.
std::vector<int> ia; // list of materials required- generate one geode per material
typedef std::vector<osg::Geometry *> geomlist;
geomlist glist;
typedef std::vector<osg::Vec3Array *> vertslist;
vertslist vlists; // list of vertices for each glist element
typedef std::vector<osg::Vec2Array *> texslist;
texslist txlists; // list of texture coords for each glist element
sscanf(buff, "%s %d", str, &num);
if (num > 0)
{
int needSmooth=0; // flat shaded
ob.num_surf = num;
for (n = 0; n < num; n++)
{
osg::Geometry *geom=NULL; // the surface will be addded to this geometry
osg::Vec3Array *vgeom=NULL; // vertices corresponding to geom taken from vertexpool
osg::Vec2Array *tgeom=NULL; // texture coords corresponding to geom taken from vertexpool
ACSurface asurf;
osg::UShortArray *nusidx = new osg::UShortArray; // indices into the vertices
osg::Vec2Array *tcs=new osg::Vec2Array; // texture coordinates for this object
ACSurface *news = read_surface(f, &asurf, nusidx, tcs);
if (news == NULL)
{
printf("error whilst reading surface at line: %d\n", line);
return(NULL);
} else {
int i=0;
for (std::vector<int>::iterator itr= ia.begin(); itr<ia.end(); itr++, i++) {
if ((*itr)==asurf.mat) {
geom=glist[i];
vgeom=vlists[i];
tgeom=txlists[i]; // what is current texture array
}
}
if (!geom) { // then we need a new geometry
osg::Vec3Array *verts = new osg::Vec3Array;
osg::Vec2Array *tcrds=new osg::Vec2Array; // texture coordinates for this object
vgeom=verts;
tgeom=tcrds; // what is current texture array
vlists.push_back(verts);
txlists.push_back(tcrds);
geom=new osg::Geometry();
geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
geom->setNormalArray(normals);
geom->setVertexArray(verts);
if (ob.texture) {
geom->setTexCoordArray(0,tgeom); // share same set of TexCoords
}
osg::Material*mat=ac_palette_get_material(asurf.mat);
osg::StateSet *dstate = new osg::StateSet;
dstate->setMode( GL_LIGHTING, osg::StateAttribute::ON );
dstate->setAttribute(mat);
const osg::Vec4 cdiff =mat->getDiffuse(osg::Material::FRONT_AND_BACK);
if (cdiff[3]<0.99) {
dstate->setMode(GL_BLEND,osg::StateAttribute::ON);
dstate->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
} else {
dstate->setMode(GL_BLEND,osg::StateAttribute::OFF);
}
if (ob.texture) dstate->setTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF);
if (ob.texture) {
osg::TexEnv* texenv = new osg::TexEnv;
texenv->setMode(osg::TexEnv::MODULATE);
dstate->setTextureAttribute(0, texenv );
dstate->setTextureAttributeAndModes(0,ob.texture,osg::StateAttribute::ON);
}
if (asurf.flags & SURFACE_TWOSIDED) dstate->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
else dstate->setMode( GL_CULL_FACE, osg::StateAttribute::ON );
geom->setStateSet( dstate );
glist.push_back(geom);
geode->addDrawable(geom);
ia.push_back(asurf.mat);
}
osg::Vec3Array* normals = geom->getNormalArray();
/** calc surface normal **/
if (asurf.num_vertref >= 3) {
osg::Vec3 norm;
osg::ushort i1=(*nusidx)[0];
osg::ushort i2=(*nusidx)[1];
osg::ushort i3=(*nusidx)[2];
osgtri_calc_normal((*vertpool)[i1],
(*vertpool)[i2],
(*vertpool)[i3], norm);
normals->push_back(norm);
}
int nstart=(*vgeom).size();
for (i=0; i<asurf.num_vertref; i++) {
int i1=(*nusidx)[i];
(*vgeom).push_back((*vertpool)[i1]);
(*tgeom).push_back((*tcs)[i]);
}
GLenum poltype=osg::PrimitiveSet::POLYGON;
if (asurf.flags & SURFACE_TYPE_CLOSEDLINE) poltype=osg::PrimitiveSet::LINE_LOOP;
if (asurf.flags & SURFACE_TYPE_LINE) poltype=osg::PrimitiveSet::LINE_STRIP;
geom->addPrimitiveSet(new osg::DrawArrays(poltype,nstart,asurf.num_vertref));
if (asurf.flags & 0x10) needSmooth++;
}
}
for (geomlist::iterator itr= glist.begin(); itr<glist.end(); itr++) {
osgUtil::Tesselator tesselator;
if (*itr) tesselator.retesselatePolygons(**itr);
if (needSmooth) {
osgUtil::SmoothingVisitor smoother;
smoother.smooth(**itr);
}
}
}
}
else
if (streq(t, "kids")) /** 'kids' is the last token in an object **/
{
int num, n;
sscanf(buff, "%s %d", t, &num);
if (num != 0)
{
// ob.kids = (ACObject **)myalloc(num * sizeof(ACObject *) );
ob.num_kids = num;
for (n = 0; n < num; n++)
{
osg::Group *k = ac_load_object(f,&ob); //, ob);
if (k == NULL)
{
printf("error reading expected child object %d of %d at line: %d\n", n+1, num, line);
return(gp);
}
else
//ob.kids[n] = k;
gp->addChild(k);
}
}
if (geode) {
osgUtil::Tesselator tesselator;
for(unsigned int i=0;i<geode->getNumDrawables();++i)
{
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
if (geom) tesselator.retesselatePolygons(*geom);
}
}
return(gp);
}
}
if (geode) {
osgUtil::Tesselator tesselator;
for(unsigned int i=0;i<geode->getNumDrawables();++i)
{
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
if (geom) tesselator.retesselatePolygons(*geom);
}
}
return(gp);
}
/*void ac_calc_vertex_normals(ACObject *ob)
{
int n;
ac_object_calc_vertex_normals(ob);
if (ob->num_kids)
for (n = 0; n < ob->num_kids; n++)
ac_calc_vertex_normals(ob->kids[n]);
}*/
osg::Group *ac_load_ac3d(const char *fname)
{
FILE *f = fopen(fname, "r");
osg::Group *ret = NULL;
if (f == NULL)
{
printf("can't open %s\n", fname);
return(NULL);
}
read_line(f);
if (strncmp(buff, "AC3D", 4))
{
printf("ac_load_ac '%s' is not a valid AC3D file.", fname);
fclose(f);
return(0);
}
startmatindex = num_palette;
ret = ac_load_object(f,NULL); //, NULL);
fclose(f);
// ac_calc_vertex_normals(ret);
// here I need to calculate nromals for this object
return(ret);
}
void ac_dump(ACObject *ob)
{ // not yet finished option ot output AC3D file from OSG scene.
int n;
printf("OBJECT name %s\nloc %f %f %f\nnum_vert %d\nnum_surf %d\n",
ob->name, ob->loc[0], ob->loc[1], ob->loc[2], ob->num_vert, ob->num_surf);
// for (n=0; n < ob->num_vert; n++)
// printf("\tv %f %f %f\n", ob->vertices[n].x, ob->vertices[n].y, ob->vertices[n].z);
for (n=0; n < ob->num_surf; n++)
{
//// ACSurface *s = &ob->surfaces[n];
// printf("surface %d, %d refs, mat %d\n", n, s->num_vertref, s->mat);
}
/* this structure not used in OSG
if (ob->num_kids)
for (n = 0; n < ob->num_kids; n++)
ac_dump(ob->kids[n]);
*/
}
// now register with osg::Registry to instantiate the above
// reader/writer.
osgDB::RegisterReaderWriterProxy<ReaderWriterAC> g_readerWriter_AC_Proxy;