/* * * Copyright (C) 2000 Silicon Graphics, Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Further, this software is distributed without any warranty that it is * free of the rightful claim of any third person regarding infringement * or the like. Any license provided herein, whether implied or * otherwise, applies only to this software file. Patent licenses, if * any, provided herein do not apply to combinations of this program with * other software, or any other product whatsoever. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, * Mountain View, CA 94043, or: * * http://www.sgi.com * * For further information regarding this notice, see: * * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ * */ // // Simple class that encapsulates some information about a quad-mesh. // Used by the qmorf program and by the little programs that generate // quad-mesh objects. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QuadThing.h" QuadThing::QuadThing(const QuadThing *from) { createSceneGraph(); interp(from, from, 0.0); } QuadThing::QuadThing(int nx, int ny, const SbVec3f *verts) { createSceneGraph(); coords->point.setValues(0, nx*ny, verts); qmesh->verticesPerRow.setValue(nx); qmesh->verticesPerColumn.setValue(ny); figureNormals(); } static SoNode * searchLastType(SoPath *p, SoType t) { SoSearchAction sa; sa.setSearchingAll(TRUE); sa.setType(t); sa.setInterest(SoSearchAction::LAST); sa.apply(p); SoPath *outPath = sa.getPath(); SoNode *result = NULL; if (outPath != NULL && (outPath->getLength() > 0) ) result = outPath->getTail(); return result; } // // Read in a scene graph and extract the information out of it needed // to create a QuadThing. This also uniformly scales the object to // reside in a -1 to 1 bounding box. // QuadThing::QuadThing(const char *filename) { // // Need to extract: // -- first QuadMesh; need its x,y size. // -- coordinates // -- materials // -- material binding // -- textures // -- texture coordinates // -- texture binding // // Note: these are only set to NULL in this constructor because // this is the only constructor that may fail to create them (all // the other constructors create them in the createSceneGraph // routine) // sceneGraph = NULL; coords = NULL; norms = NULL; qmesh = NULL; materials = NULL; matbind = NULL; texture = NULL; texcoords = NULL; texbind = NULL; SoInput in; if (!in.openFile(filename)) { fprintf(stderr, "Couldn't open %s\n", filename); exit(1); } SoSeparator *graph = SoDB::readAll(&in); in.closeFile(); if (graph == NULL) return; graph->ref(); // // Search for first mesh // SoSearchAction sa; sa.setInterest(SoSearchAction::FIRST); sa.setType(SoQuadMesh::getClassTypeId()); sa.apply(graph); SoPath *pathToMesh = sa.getPath(); if (pathToMesh == NULL) { fprintf(stderr, "Error reading qmesh from %s\n", filename); return; } pathToMesh->ref(); SoQuadMesh *q = (SoQuadMesh *)pathToMesh->getTail(); // // If there is a VertexProperty node, make a new separator containing // nodes with that information, and put it in the scene graph. // if (q->vertexProperty.getValue() != NULL) { SoVertexProperty *vp = (SoVertexProperty*)q->vertexProperty.getValue(); SoSeparator *quadsep = new SoSeparator; int numVerts = vp->vertex.getNum(); if (numVerts > 0) { SoCoordinate3 *newcoord = new SoCoordinate3; newcoord->point.setValues(0,numVerts,vp->vertex.getValues(0)); quadsep->addChild(newcoord); } int numNormals = vp->normal.getNum(); if (numNormals > 0) { SoNormal *newnormal = new SoNormal; newnormal->vector.setValues(0,numNormals,vp->normal.getValues(0)); quadsep->addChild(newnormal); } int numTexCoords = vp->texCoord.getNum(); if (numTexCoords > 0) { SoTextureCoordinate2 *newtexcoord = new SoTextureCoordinate2; newtexcoord->point.setValues(0,numTexCoords,vp->texCoord.getValues(0)); quadsep->addChild(newtexcoord); } int numColors = vp->orderedRGBA.getNum(); if (numColors > 0) { float transparency; SbColor color; SoMaterial *newmat = new SoMaterial; for (int i=0; iorderedRGBA[i],transparency); newmat->diffuseColor.set1Value(i,color); newmat->transparency.set1Value(i,transparency); } quadsep->addChild(newmat); } q->vertexProperty.setValue(NULL); quadsep->addChild(q); ((SoGroup*)(pathToMesh->getNodeFromTail(1)))->replaceChild(q, quadsep); pathToMesh->unref(); sa.apply(graph); pathToMesh = sa.getPath(); if (pathToMesh == NULL) { fprintf(stderr, "Error reading qmesh from %s\n", filename); return; } pathToMesh->ref(); q = (SoQuadMesh *)pathToMesh->getTail(); } createSceneGraph(); sceneGraph->replaceChild(qmesh, q); qmesh = q; qmesh->verticesPerRow.setValue(q->verticesPerRow.getValue()); qmesh->verticesPerColumn.setValue(q->verticesPerColumn.getValue()); // // Search for coordinates. We look for the last coordinates on // the path to the mesh, and assume that the mesh will use those // coordinates. This isn't quite correct; the ignore flag could // be set on the coordinate's point field. We should check for // that and search further up the path, but for simplicity I just // assume that the ignore flag isn't set (certainly true for the // data sets I'm likely to throw at this!). // SoCoordinate3 *c = (SoCoordinate3 *) searchLastType(pathToMesh, SoCoordinate3::getClassTypeId()); if (c == NULL) { fprintf(stderr, "Could not find coordinates in %s!\n", filename); exit(1); } sceneGraph->replaceChild(coords, c); coords = c; // // And other stuff... Again, note that the results may not be // correct if the ignore flag is set on any of this stuff. Hi ho. // materials = (SoMaterial *) searchLastType(pathToMesh, SoMaterial::getClassTypeId()); if (materials != NULL) { materials->ref(); } matbind = (SoMaterialBinding *) searchLastType(pathToMesh, SoMaterialBinding::getClassTypeId()); if (matbind != NULL) { matbind->ref(); } texture = (SoTexture2 *) searchLastType(pathToMesh, SoTexture2::getClassTypeId()); if (texture != NULL) { texture->ref(); } texcoords = (SoTextureCoordinate2 *) searchLastType(pathToMesh, SoTextureCoordinate2::getClassTypeId()); if (texcoords != NULL) { texcoords->ref(); } texbind = (SoTextureCoordinateBinding *) searchLastType(pathToMesh, SoTextureCoordinateBinding::getClassTypeId()); if (texbind != NULL) { texbind->ref(); } pathToMesh->unref(); graph->unref(); // No longer needed figureNormals(); } QuadThing::~QuadThing() { // // Delete any of the stuff we might have found // if (sceneGraph != NULL) sceneGraph->unref(); if (materials != NULL) materials->unref(); if (matbind != NULL) matbind->unref(); if (texture != NULL) texture->unref(); if (texcoords != NULL) texcoords->unref(); if (texbind != NULL) texbind->unref(); } // // Create the scene graph. Used by the constructors. // void QuadThing::createSceneGraph() { sceneGraph = new SoSeparator; sceneGraph->ref(); coords = new SoCoordinate3; sceneGraph->addChild(coords); norms = new SoNormal; sceneGraph->addChild(norms); SoNormalBinding *nb = new SoNormalBinding; nb->value = SoNormalBinding::PER_VERTEX_INDEXED; sceneGraph->addChild(nb); qmesh = new SoQuadMesh; sceneGraph->addChild(qmesh); } #define MAX(a,b) (a > b ? a : b) // // Modify the vertices so the bounding boxes of all of the objects are // the same // void QuadThing::scaleBBox() { SoGetBoundingBoxAction bba(SbVec2s(1,1)); bba.apply(sceneGraph); SbBox3f bbox = bba.getBoundingBox(); SbVec3f center = bbox.getCenter(); float x, y, z, scale; bbox.getSize(x, y, z); scale = 2.0 / MAX( x, MAX(y,z) ); SbVec3f *vertices = coords->point.startEditing(); // Now modify vertices... int nverts = (int) (qmesh->verticesPerColumn.getValue() * qmesh->verticesPerRow.getValue()); for (int i = 0; i < nverts; i++) { vertices[i] = (vertices[i]-center)*scale; } coords->point.finishEditing(); } // // Newell's formula for the normal of a polygon-- this version is // hardwired for quads. n will be close to zero if the quad is // degenerate. This does NOT normalize the result. // void QuadThing::newell4(SbVec3f &n, int v1, int v2, int v3, int v4) { const SbVec3f *v = coords->point.getValues(0); n[0] = (v[v1][1] - v[v2][1])*(v[v1][2] + v[v2][2]); n[1] = (v[v1][2] - v[v2][2])*(v[v1][0] + v[v2][0]); n[2] = (v[v1][0] - v[v2][0])*(v[v1][1] + v[v2][1]); n[0] += (v[v2][1] - v[v3][1])*(v[v2][2] + v[v3][2]); n[1] += (v[v2][2] - v[v3][2])*(v[v2][0] + v[v3][0]); n[2] += (v[v2][0] - v[v3][0])*(v[v2][1] + v[v3][1]); n[0] += (v[v3][1] - v[v4][1])*(v[v3][2] + v[v4][2]); n[1] += (v[v3][2] - v[v4][2])*(v[v3][0] + v[v4][0]); n[2] += (v[v3][0] - v[v4][0])*(v[v3][1] + v[v4][1]); n[0] += (v[v4][1] - v[v1][1])*(v[v4][2] + v[v1][2]); n[1] += (v[v4][2] - v[v1][2])*(v[v4][0] + v[v1][0]); n[2] += (v[v4][0] - v[v1][0])*(v[v4][1] + v[v1][1]); } // // Average the normals of the faces around the vertex at the // given row, column to figure out its normal. This actually weights // the polygons' contributions by their area. // void QuadThing::averageNormals(SbVec3f *normals, int r, int c) { int numx = (int)qmesh->verticesPerRow.getValue(); int i = r*numx+c; SbVec3f n; newell4(n, i-numx-1, i-1, i, i-numx); normals[i] = n; newell4(n, i-numx, i, i+1, i-numx+1); normals[i] += n; newell4(n, i, i+numx, i+numx+1, i+1); normals[i] += n; newell4(n, i-1, i+numx-1, i+numx, i); normals[i] += n; float d = normals[i].length(); if (d > 0.00001) normals[i] /= d; else normals[i].setValue(0.0, 0.0, 1.0); } // // Figure out the normals for this thing. This uses Newell's formula // on the 4 vertices surrounding each vertex. This will be fast and // fairly robust (edges are handled by using the vertex itself, so // corners get calculated correctly). If the polygon is degenerate, // an arbitrary normal is assigned to the vertex. // void QuadThing::figureNormals() { int row, column; int numx = (int)qmesh->verticesPerRow.getValue(); int numy = (int)qmesh->verticesPerColumn.getValue(); int nn = norms->vector.getNum(); if (nn < numx*numy) { norms->vector.insertSpace(0, numx*numy-nn); } SbVec3f *normals = norms->vector.startEditing(); // // Do interior vertices. We take the normal of the plane through // the neighboring vertices for speed. // for (row = 1; row < numy-1; row++) { for (column = 1; column < numx-1; column++) { int i = row*numx+column; newell4(normals[i], i-numx, i-1, i+numx, i+1); // // Normalize; if using the neighboring points failed for // any reason, average the normals of the surrounding // faces: // float d = normals[i].length(); if (d > 0.00001) normals[i] /= d; else averageNormals(normals, row, column); } } // // For the border quads, average the normals of surrounding faces. // First, bottom and top: // SbVec3f lastBot(0.0, 0.0, 0.0); SbVec3f lastTop(0.0, 0.0, 0.0); int i; for (i = 0; i < numx; i++) { int top = (numy-1)*numx+i; normals[i] = lastBot; normals[top] = lastTop; if (i+1 < numx) { newell4(lastBot, i, i+numx, i+numx+1, i+1); newell4(lastTop, top-numx, top, top+1, top-numx+1); normals[i] += lastBot; normals[top] += lastTop; } float d = normals[i].length(); if (d > 0.00001) normals[i] /= d; else normals[i].setValue(0.0, 0.0, 1.0); d = normals[top].length(); if (d > 0.00001) normals[top] /= d; else normals[top].setValue(0.0, 0.0, 1.0); } // // Next, left and right sides. There are no special cases // because the corners were done in the previous loop. // SbVec3f lastLeft; SbVec3f lastRight; newell4(lastLeft, 0, numx, numx+1, 1); newell4(lastRight, numx-2, 2*numx-2, 2*numx-1, numx-1); for (i = 1; i < numy-1; i++) { int left = i*numx; int right = left+numx-1; normals[left] = lastLeft; normals[right] = lastRight; newell4(lastLeft, left, left+numx, left+numx+1, left+1); newell4(lastRight, right-1, right+numx-1, right+numx, right); normals[left] += lastLeft; normals[right] += lastRight; float d = normals[left].length(); if (d > 0.00001) normals[left] /= d; else normals[left].setValue(0.0, 0.0, 1.0); d = normals[right].length(); if (d > 0.00001) normals[right] /= d; else normals[right].setValue(0.0, 0.0, 1.0); } norms->vector.finishEditing(); } // // Interpolate between two things. This does simple linear interpolation. // void QuadThing::interp(const QuadThing *from, const QuadThing *to, float time) { int fnx = (int)from->qmesh->verticesPerRow.getValue(); int fny = (int)from->qmesh->verticesPerColumn.getValue(); int tnx = (int)to->qmesh->verticesPerRow.getValue(); int tny = (int)to->qmesh->verticesPerColumn.getValue(); int nx = fnx > tnx ? fnx : tnx; int ny = fny > tny ? fny : tny; int nv = coords->point.getNum(); if (nv < nx*ny) { coords->point.insertSpace(0, (int)(nx*ny-nv)); } qmesh->verticesPerRow.setValue(nx); qmesh->verticesPerColumn.setValue(ny); SbVec3f *vertices = coords->point.startEditing(); const SbVec3f *f_vertices = from->coords->point.getValues(0); const SbVec3f *t_vertices = to->coords->point.getValues(0); // Use linear interpolation for now for (int row = 0; row < ny; row++) { int from_row = (row * fny) / ny; int to_row = (row * tny) / ny; for (int col = 0; col < nx; col++) { int i = row*nx+col; int from_i = from_row*fnx+col; int to_i = to_row*tnx+col; vertices[i] = f_vertices[from_i] * (1.0 - time) + t_vertices[to_i] * time; } } coords->point.finishEditing(); figureNormals(); } // // Some access routines. // SoSeparator * QuadThing::getSceneGraph() { return sceneGraph; } SoMaterial * QuadThing::getMaterial() { return materials; } SoMaterialBinding * QuadThing::getMatBinding() { return matbind; } SoTexture2 * QuadThing::getTexture2() { return texture; } SoTextureCoordinate2 * QuadThing::getTexCoord() { return texcoords; } SoTextureCoordinateBinding * QuadThing::getTexBinding() { return texbind; }