// Copyright (C) 1996 Keith Whitwell. // This file may only be copied under the terms of the GNU Library General // Public License - see the file COPYING in the lib3d distribution. #include #include #include ModelBuilder::ModelBuilder() : vertices(100), polygons(100), vertexNormals(100), vertexNormalScratch(100), polygonNormals(100), materials(8), calculatePolygonNormalMode( false ), calculateVertexNormalMode( false ), colourfulMode( false ), texture(0) { searchFlag[Vertices] = 1; searchFlag[PolygonNormals] = 1; searchFlag[VertexNormals] = 1; } ModelBuilder::~ModelBuilder() { } void ModelBuilder::startModel() { vertices.truncate(0); polygons.truncate(0); vertexNormals.truncate(0); polygonNormals.truncate(0); materials.truncate(0); material = 0; polygonNormal = 0; vertexNormal = 0; } Model * ModelBuilder::endModel() { if (materials.getNr() == 0) { Vector3 col(.8,.8,.8); addMaterial(col, .2,1,0,0,0); } if (calculateVertexNormalMode) { uint i = vertices.getNr(); while (i > calculateVertexNormalBase) { i--; NormalTmp &t = vertexNormalScratch[i]; if (t.nr) { t.polyNormalSum.normalize(); uint n = setVertexNormal( t.polyNormalSum ); vertices[i].normal = n; } } } BoundingBoxExact *box = new BoundingBoxExact(vertices.getStorage(), vertices.getNr()); if (texture) { cylinderWrap(box); } Model *ob = new Model(polygons.getNr(), polygons.export(), vertices.getNr(), vertices.export(), vertexNormals.getNr(), vertexNormals.export(), polygonNormals.getNr(), polygonNormals.export(), materials.getNr(), materials.export(), texture, box); return ob; } // Too boring to have bugs. // void ModelBuilder::stupidWrap( const BoundingBoxExact *box ) { float ymin = box->model[0].v[Y]; float ymax = box->model[0].v[Y]; float zmin = box->model[0].v[Z]; float zmax = box->model[0].v[Z]; for (int j = 1 ; j < 8 ; j++) { ymax = max(ymax, box->model[j].v[Y]); ymin = min(ymin, box->model[j].v[Y]); zmax = max(zmax, box->model[j].v[Z]); zmin = min(zmin, box->model[j].v[Z]); } float yscale = texture->getDimension() / (ymax-ymin); float zscale = texture->getDimension() / (zmax-zmin); uint i = vertices.getNr(); while (i--) { vertices[i].u = (vertices[i].model.v[Y]-ymin) * yscale; vertices[i].v = (vertices[i].model.v[Z]-zmin) * zscale; } } inline static bool isWrapped(float a, float b, float threshold) { float f = fabs(a-b); // printf("f: %.2f threshold: %.2f\n", f, threshold); return f > threshold; } inline static float wrap( float a, float threshold ) { if (a < threshold) { return a + threshold + threshold; } else { return threshold + threshold - a; } } // A little buggy. // void ModelBuilder::cylinderWrap( const BoundingBoxExact *box ) { float ymin = box->model[0].v[Y]; float ymax = box->model[0].v[Y]; float xmin = box->model[0].v[Y]; float xmax = box->model[0].v[Y]; float zmin = box->model[0].v[Z]; float zmax = box->model[0].v[Z]; for (int j = 1 ; j < 8 ; j++) { zmax = max(zmax, box->model[j].v[Z]); zmin = min(zmin, box->model[j].v[Z]); ymax = max(ymax, box->model[j].v[Y]); ymin = min(ymin, box->model[j].v[Y]); xmax = max(xmax, box->model[j].v[X]); xmin = min(xmin, box->model[j].v[X]); } float xmid = (xmin + xmax) / 2; float ymid = (ymin + ymax) / 2; int dim = texture->getDimension(); float uscale = dim / (2 * M_PI); float vscale = dim / (zmax-zmin); uint i = vertices.getNr(); while (i--) { vertices[i].u = ((atan2(vertices[i].model.v[Y] - ymid, vertices[i].model.v[X] - xmid) + M_PI) * uscale); vertices[i].v = (vertices[i].model.v[Z]-zmin) * vscale; //printf("u: %.2f v: %.2f\n", vertices[i].u, vertices[i].v); } float threshold = float(dim/2); i = polygons.getNr(); while (i--) { bool w1 = isWrapped(vertices[polygons[i].vertex0].u, vertices[polygons[i].vertex1].u, threshold); bool w2 = isWrapped(vertices[polygons[i].vertex1].u, vertices[polygons[i].vertex2].u, threshold); bool w3 = isWrapped(vertices[polygons[i].vertex0].u, vertices[polygons[i].vertex2].u, threshold); if (w1|w2|w3) { if (!w1) { int v = polygons[i].vertex2; vertexNormal = vertices[v].normal; polygons[i].vertex2 = addVertex(vertices[v].model, wrap(vertices[v].u, threshold), vertices[v].v); } if (!w2) { int v = polygons[i].vertex0; vertexNormal = vertices[v].normal; polygons[i].vertex0 = addVertex(vertices[v].model, wrap(vertices[v].u, threshold), vertices[v].v); } if (!w3) { int v = polygons[i].vertex1; vertexNormal = vertices[v].normal; polygons[i].vertex1 = addVertex(vertices[v].model, wrap(vertices[v].u, threshold), vertices[v].v); } } } } // A lot buggy // void ModelBuilder::sphereWrap( const BoundingBoxExact *box ) { float ymin = box->model[0].v[Y]; float ymax = box->model[0].v[Y]; float xmin = box->model[0].v[Y]; float xmax = box->model[0].v[Y]; float zmin = box->model[0].v[Z]; float zmax = box->model[0].v[Z]; for (int j = 1 ; j < 8 ; j++) { zmax = max(zmax, box->model[j].v[Z]); zmin = min(zmin, box->model[j].v[Z]); ymax = max(ymax, box->model[j].v[Y]); ymin = min(ymin, box->model[j].v[Y]); xmax = max(xmax, box->model[j].v[X]); xmin = min(xmin, box->model[j].v[X]); } float xmid = (xmin + xmax) / 2; float ymid = (ymin + ymax) / 2; float zmid = (zmin + zmax) / 2; float xscale = 1.0/(xmax - xmin); float yscale = 1.0/(ymax - ymin); float zscale = 1.0/(zmax - zmin); int dim = texture->getDimension(); float scale = dim / (2 * M_PI); uint i = vertices.getNr(); while (i--) { float x = (vertices[i].model.v[X] - xmid) * xscale; float y = (vertices[i].model.v[Y] - ymid) * yscale; float z = (vertices[i].model.v[Z] - zmid) * zscale; vertices[i].u = ((atan2(y, x) + M_PI) * scale); vertices[i].v = ((atan2(y, z) + M_PI) * scale); //vertices[i].v = (vertices[i].model.v[Z]-zmin) * zscale * dim; //printf("u: %.2f v: %.2f\n", vertices[i].u, vertices[i].v); } // return; float threshold = float(dim/2); i = polygons.getNr(); while (i--) { bool w1, w2, w3; w1 = isWrapped(vertices[polygons[i].vertex0].u, vertices[polygons[i].vertex1].u, threshold); w2 = isWrapped(vertices[polygons[i].vertex1].u, vertices[polygons[i].vertex2].u, threshold); w3 = isWrapped(vertices[polygons[i].vertex0].u, vertices[polygons[i].vertex2].u, threshold); if (w1|w2|w3) { if (!w1) { vertexNormal = vertices[polygons[i].vertex2].normal; polygons[i].vertex2 = addVertex(vertices[polygons[i].vertex2].model, wrap(vertices[polygons[i].vertex2].u, threshold), vertices[polygons[i].vertex2].v); } if (!w2) { vertexNormal = vertices[polygons[i].vertex0].normal; polygons[i].vertex0 = addVertex(vertices[polygons[i].vertex0].model, wrap(vertices[polygons[i].vertex0].u, threshold), vertices[polygons[i].vertex0].v); } if (!w3) { vertexNormal = vertices[polygons[i].vertex1].normal; polygons[i].vertex1 = addVertex(vertices[polygons[i].vertex1].model, wrap(vertices[polygons[i].vertex1].u, threshold), vertices[polygons[i].vertex1].v); } } w1 = isWrapped(vertices[polygons[i].vertex0].v, vertices[polygons[i].vertex1].v, threshold); w2 = isWrapped(vertices[polygons[i].vertex1].v, vertices[polygons[i].vertex2].v, threshold); w3 = isWrapped(vertices[polygons[i].vertex0].v, vertices[polygons[i].vertex2].v, threshold); if (w1|w2|w3) { if (!w1) { vertexNormal = vertices[polygons[i].vertex2].normal; polygons[i].vertex2 = addVertex(vertices[polygons[i].vertex2].model, vertices[polygons[i].vertex2].u, wrap(vertices[polygons[i].vertex2].v, threshold)); } if (!w2) { vertexNormal = vertices[polygons[i].vertex0].normal; polygons[i].vertex0 = addVertex(vertices[polygons[i].vertex0].model, vertices[polygons[i].vertex0].u, wrap(vertices[polygons[i].vertex0].v, threshold)); } if (!w3) { vertexNormal = vertices[polygons[i].vertex1].normal; polygons[i].vertex1 = addVertex(vertices[polygons[i].vertex1].model, vertices[polygons[i].vertex1].u, wrap(vertices[polygons[i].vertex1].v, threshold)); } } } } uint ModelBuilder::setVertexNormal( Vector3& norm ) { vertexNormal = searchVertexNormal( norm ); if (vertexNormal != uint(~0)) return vertexNormal; Normal &n = vertexNormals.nextFree( vertexNormal ); n.model.v[X] = norm.v[X]; n.model.v[Y] = norm.v[Y]; n.model.v[Z] = norm.v[Z]; return vertexNormal; } uint ModelBuilder::setPolygonNormal( Vector3& norm ) { polygonNormal = searchPolygonNormal( norm ); if (polygonNormal != uint(~0)) { return polygonNormal; } Normal &n = polygonNormals.nextFree( polygonNormal ); n.model.v[X] = norm.v[X]; n.model.v[Y] = norm.v[Y]; n.model.v[Z] = norm.v[Z]; return polygonNormal; } void ModelBuilder::calculatePolygonNormals() { calculatePolygonNormalMode = true; } void ModelBuilder::calculateVertexNormals() { calculateVertexNormalMode = true; calculateVertexNormalBase = vertices.getNr(); } void ModelBuilder::setColourfulMode( float Ka, float Kd ) { // .5,.9 looks quite good dithered if (!colourfulMode) { Vector3 col(.8,.3,.3); addMaterial(col,Ka,Kd,0,0,0); col.assign(.3,.8,.3); addMaterial(col,Ka,Kd,0,0,0); col.assign(.3,.3,.8); addMaterial(col,Ka,Kd,0,0,0); col.assign(.3,.8,.8); addMaterial(col,Ka,Kd,0,0,0); col.assign(.8,.3,.8); addMaterial(col,Ka,Kd,0,0,0); col.assign(.8,.8,.3); addMaterial(col,Ka,Kd,0,0,0); col.assign(.8,.8,.8); addMaterial(col,Ka,Kd,0,0,0); } colourfulMode = true; } uint ModelBuilder::addVertex( const Vector3& position ) { uint idx = searchVertex( position ); if (idx != uint(~0) ) { Vertex &v = vertices[idx]; if (vertexNormal == v.normal) return idx; } Vertex &v = vertices.nextFree( idx ); if (calculateVertexNormalMode) { vertexNormalScratch.nextFree(); } v.model.v[X] = position.v[X]; v.model.v[Y] = position.v[Y]; v.model.v[Z] = position.v[Z]; v.normal = vertexNormal; v.u = 0; v.v = 0; return idx; } uint ModelBuilder::addVertex( float x, float y, float z ) { Vector3 position(x,y,z); return addVertex(position); } uint ModelBuilder::addVertex( float x, float y, float z, float u, float v ) { Vector3 position(x,y,z); return addVertex( position, u, v ); } uint ModelBuilder::addVertex( const Vector3& position, float u, float v ) { // cout << "adding :" << position << ", u: " << u << " v: " << v << endl; uint idx = searchVertex( position ); if (idx != uint(~0)) { if (vertexNormal == vertices[idx].normal && vertices[idx].u == u && vertices[idx].v == v) { return idx; } } Vertex &vert = vertices.nextFree( idx ); if (calculateVertexNormalMode) { vertexNormalScratch.nextFree(); } vert.model.v[X] = position.v[X]; vert.model.v[Y] = position.v[Y]; vert.model.v[Z] = position.v[Z]; vert.normal = vertexNormal; vert.u = u; vert.v = v; return idx; } void ModelBuilder::setModelTexture( Texture *t ) { texture = t; } // must be a concave polygon !! // return value is pretty useless right now. uint ModelBuilder::addPolygon( uint nr, const uint *v ) { uint i; if (nr < 2) return ~0; if (nr == 2) { return addTriangle(v[0],v[1],v[0]); } for ( i = 2; i < nr; i++) { if (addTriangle(v[0],v[i-1],v[i]) == (uint)(~0)) return ~0; } return 0; } uint ModelBuilder::addPolygon( uint nr, const Vector3 *vert ) { if (nr < 2) return ~0; uint *v = new uint[nr]; for ( uint i = 0; i < nr; i++) v[i] = addVertex(vert[i]); uint idx = addPolygon(nr,v); delete[] v; return idx; } uint ModelBuilder::addTriangle( uint v1, uint v2, uint v3 ) { uint idx; Polygon &p = polygons.nextFree( idx ); p.vertex0 = v1; p.vertex1 = v2; p.vertex2 = v3; if (colourfulMode) { p.material = idx % 7; } else { p.material = material; } if (calculatePolygonNormalMode) { Vector3 norm; Vector3 ba; Vector3 bc; ba.sub( vertices[p.vertex0].model, vertices[p.vertex1].model ); bc.sub( vertices[p.vertex2].model, vertices[p.vertex1].model ); norm.cross(ba, bc); norm.normalize(); setPolygonNormal(norm); } p.normal = polygonNormal; if (calculateVertexNormalMode) { Vector3 &n = polygonNormals[p.normal].model; int v0 = p.vertex0 - calculateVertexNormalBase; int v1 = p.vertex1 - calculateVertexNormalBase; int v2 = p.vertex2 - calculateVertexNormalBase; vertexNormalScratch[v0].polyNormalSum.add( n ); vertexNormalScratch[v0].nr++; vertexNormalScratch[v1].polyNormalSum.add( n ); vertexNormalScratch[v1].nr++; vertexNormalScratch[v2].polyNormalSum.add( n ); vertexNormalScratch[v2].nr++; } return idx; } uint ModelBuilder::addTriangle( const Vector3 vert[3] ) { uint v[3]; v[0] = addVertex(vert[0]); v[1] = addVertex(vert[1]); v[2] = addVertex(vert[2]); return addTriangle(v[0],v[1],v[2]); } uint ModelBuilder::searchVertex( const Vector3 &v ) const { if (searchFlag[Vertices]) { // Use a brute force approach for now. for (int i = vertices.getNr() ; i-- ; ) { Vector3 dist; dist.sub(vertices[i].model, v); if (dist.magnitude() < .0001) return i; /* const Vector3 &t = vertices[i].model; if ( v.v[X] == t.v[X] && v.v[Y] == t.v[Y] && v.v[Z] == t.v[Z] ) return i; */ } } return ~0; } uint ModelBuilder::searchVertexNormal( const Vector3 &v ) const { if (searchFlag[VertexNormals]) { for (int i = vertexNormals.getNr() ; i-- ; ) { const Vector3 &t = vertexNormals[i].model; float cos_angle = dot(t, v); if ( cos_angle > .9999 && cos_angle < 1.0001 ) // .8 degrees // if ( cos_angle > .99999 && cos_angle < 1.00001 ) // .25 degrees // if ( cos_angle > .9999999 && cos_angle < 1.0000001 ) return i; } } return ~0; } uint ModelBuilder::searchPolygonNormal( const Vector3 &v ) const { if (searchFlag[PolygonNormals]) { for (int i = polygonNormals.getNr() ; i-- ; ) { const Vector3 &t = polygonNormals[i].model; float cos_angle = dot(t, v); // if ( cos_angle > .999 && cos_angle < 1.001 ) // 2.5 degrees if ( cos_angle > .9999 && cos_angle < 1.0001 ) // .8 degrees // if ( cos_angle > .99999 && cos_angle < 1.00001 ) // .25 degrees // if ( cos_angle > .999999 && cos_angle < 1.000001 ) // .08 degrees // if ( cos_angle == 1.0 ) // exact return i; } } return ~0; } bool ModelBuilder::readNFF( istream &in ) { char buf[1024]; calculatePolygonNormals(); calculateVertexNormals(); setPolygonMaterial( 0 ); while (in && in.getline(buf, sizeof(buf))) { switch (buf[0]) { case 'p': uint vnum[3]; for (int i = 0 ; i < 3 && in ; i++) { Vector3 v, n; in >> v.v[X] >> v.v[Y] >> v.v[Z] >> n.v[X] >> n.v[Y] >> n.v[Z]; n.scale(-1); // setVertexNormal(n); vnum[i] = addVertex(v); } addTriangle(vnum[0], vnum[1], vnum[2]); default: break; } } return true; } // Very incomplete - will work with only the simplest .obj files. // Even then, normals are randomly flipped. // Does not optimize vertex list. bool ModelBuilder::readObj( istream &in ) { calculatePolygonNormals(); setPolygonMaterial( 0 ); setSearchBehaviour( Vertices, false ); int i = vertices.getNr() - 1; char c; Vector3 v; uint vnum[3]; while (in && in.get(c)) { switch (c) { case 'v': in >> v.v[X] >> v.v[Y] >> v.v[Z]; addVertex(v); break; case 'f': in >> vnum[0] >> vnum[2] >> vnum[1]; vnum[0] += i; vnum[1] += i; vnum[2] += i; addTriangle(vnum[0], vnum[1], vnum[2]); break; default: break; } in.ignore(1000, '\n'); } return true; } // Reader for .geom files as distributed with TAGL. // Does not optimize vertex list. bool ModelBuilder::readGeom( istream &in ) { calculatePolygonNormals(); calculateVertexNormals(); setPolygonMaterial( 0 ); setSearchBehaviour( Vertices, false ); int i = vertices.getNr() - 1; int nrVert; int nrFace; in >> nrVert >> nrFace; in.ignore(1000, '\n'); Vector3 v; uint vnum[3]; while (in && nrVert--) { in >> v.v[X] >> v.v[Y] >> v.v[Z]; addVertex(v); } while (in && nrFace--) { in >> nrVert >> vnum[0] >> vnum[2] >> vnum[1]; if (nrVert != 3) { cout << "Warning: only triangular faces are supported" << endl; } vnum[0] += i; vnum[1] += i; vnum[2] += i; addTriangle(vnum[0], vnum[1], vnum[2]); in.ignore(1000, '\n'); } return true; } uint ModelBuilder::addMaterial(const Vector3 &colour, float Ka, float Kd, float c1, float c2, float c3) { uint idx; Material &m = materials.nextFree(idx); m.setParameters(colour, Ka, Kd, c1, c2, c3); return idx; } uint ModelBuilder::addMaterial(const Material &mat) { uint idx; Material &m = materials.nextFree(idx); m = mat; return idx; } void ModelBuilder::setSearchTolerance( uint buffer, float error ) { searchError[buffer] = error; } void ModelBuilder::setSearchBehaviour( uint buffer, bool flag ) { searchFlag[buffer] = flag; } uint ModelBuilder::setVertexNormal( uint i ) { return vertexNormal = i; } uint ModelBuilder::setPolygonNormal( uint i ) { return polygonNormal = i; } void ModelBuilder::setPolygonMaterial( uint i ) { material = i; }