I'm not entirely sure what you're doing in your
CalVertexNormals function. The error is that you access
vert_face[y][...] but you never actually resize
vert_face[y], you only resize
vert_face.
Here's what I came up with based on your code. Note that if I'd have been writing this I wouldn't have done it this way. I would probably have read the vertex, face, normal etc. data into arrays and then assembled polygons from them rather than trying to do the two at one. It's a little cludgy since it was hacked together with little design though. It's a little STL heavy, so have a reference handy (but be glad I couldn't get
bind2nd and
mem_fun to work in the accumulate call)!
A point class to replace your point3 struct. Note that I templated it since faces are a set of three integers instead of floats. If I'd have been writing this from scratch I would have gone for different names, since a normal and a face are not a point, but I wanted to keep it as close as possible to your original code.
#if !defined(POINT_HEADER) #define POINT_HEADER #include <cmath> #include <ctype> #include <iostream>template <typename TYPE>class Point3{ public: Point3() : x(), y(), z() { } TYPE x; TYPE y; TYPE z;};typedef Point3<float> Point3f;typedef Point3<int> Point3i;template <typename TYPE>std::istream& operator>>(std::istream& reader, Point3<TYPE>& point){ while (std::isspace(reader.peek())) { reader.ignore(1); } reader.ignore(1); reader >> point.x; reader.ignore(1); reader >> point.y; reader.ignore(1); reader >> point.z; reader.ignore(1); return reader;}template <typename TYPE>std::ostream& operator<<(std::ostream& writer, const Point3<TYPE>& point){ writer << '[' << point.x << ", " << point.y << ", " << point.z << ']'; return writer;}template <typename TYPE>const Point3<TYPE>& operator+=(Point3<TYPE>& firstPoint, const Point3<TYPE>& secondPoint){ firstPoint.x += secondPoint.x; firstPoint.y += secondPoint.y; firstPoint.z += secondPoint.z; return firstPoint;}template <typename TYPE>class Normalise{ public: void operator()(TYPE& point) { float length = std::sqrt((point.x * point.x) + (point.y * point.y) + (point.z * point.z)); if (length < FLT_EPSILON) { length = 1; } point.x /= length; point.y /= length; point.z /= length; }};#endif
Header file for a Polygon class. This is pretty poorly designed since it's just a set of getters/setters. This again is because I was trying to make it as close to your code as possible.
#if !defined(POLYGON_HEADER) #define POLYGON_HEADER #include <iosfwd> #include "Point.h"class Polygon{ public: Polygon(); Point3f getFaceNormal() const; int getSmoothingGroup() const; void print(std::ostream& writer) const; void setMaterial(int materialId); void setNormal(Point3f normal); void setSmoothingGroup(int smoothingGroup); void setVertices(Point3f vertex1, Point3f vertex2, Point3f vertex3); void setVertexNormals(Point3f vertex1, Point3f vertex2, Point3f vertex3); private: Point3f vertices_[3]; Point3f faceNormal_; Point3f vertexNormals_[3]; int materialId_; int smoothingGroup_;};std::ostream& operator<<(std::ostream& writer, const Polygon& polygon);#endif
Implementation for the polygon. Note how I don't have a single comment in the source. This is not due to lack of time or lazyness - it doesn't need comments because the variable and function names, in their context, tell you all you need to know.
#if !defined(POLYGON_IMPLEMENTATION) #define POLYGON_IMPLEMENTATION #include <iostream> #include "Polygon.h"Polygon::Polygon() : materialId_(-1), smoothingGroup_(-1){}Point3f Polygon::getFaceNormal() const{ return faceNormal_;}int Polygon::getSmoothingGroup() const{ return smoothingGroup_;}void Polygon::print(std::ostream& writer) const{ writer << "Vertices:\n" << '\t' << vertices_[0] << "\n\t" << vertices_[1] << "\n\t" << vertices_[2] << '\n'; writer << "Normal: " << faceNormal_ << '\n'; writer << "Vertex Normals:\n" << '\t' << vertexNormals_[0] << "\n\t" << vertexNormals_[1] << "\n\t" << vertexNormals_[2] << '\n'; writer << "Material ID: " << materialId_ << '\n'; writer << "Smoothing Group: " << smoothingGroup_ << '\n';}void Polygon::setMaterial(int materialId){ materialId_ = materialId;}void Polygon::setNormal(Point3f normal){ faceNormal_ = normal;}void Polygon::setSmoothingGroup(int smoothingGroup){ smoothingGroup_ = smoothingGroup;}void Polygon::setVertices(Point3f vertex1, Point3f vertex2, Point3f vertex3){ vertices_[0] = vertex1; vertices_[1] = vertex2; vertices_[2] = vertex3;}void Polygon::setVertexNormals(Point3f normal1, Point3f normal2, Point3f normal3){ vertexNormals_[0] = normal1; vertexNormals_[1] = normal2; vertexNormals_[2] = normal3;}std::ostream& operator<<(std::ostream& writer, const Polygon& polygon){ polygon.print(writer); return writer;}#endif
Header file for the mesh.
#if !defined(MESH_HEADER) #define MESH_HEADER #include <iosfwd> #include <vector> #include "Point.h" #include "Polygon.h"class Mesh{ public: void calculateVertexNormals(); void print(std::ostream& writer) const; void read(std::istream& reader); private: void readFaces(std::istream& reader); void readMaterials(std::istream& reader); void readNormals(std::istream& reader); void readSmoothingGroups(std::istream& reader); void readVertices(std::istream& reader); std::vector<Point3f> vertices; std::vector<Point3i> faces; std::vector<Polygon> polygons;};std::istream& operator>>(std::istream& reader, Mesh& mesh);std::ostream& operator<<(std::ostream& writer, const Mesh& mesh);#endif
Implementation for the mesh. Have an STL reference handy!
#if !defined(MESH_IMPLEMENTATION) #define MESH_IMPLEMENTATION #include <algorithm> #include <iostream> #include <iterator> #include <numeric> #include "Mesh.h"class FindMaximumSmoothingGroup{ public: int operator()(int maximumSmoothingGroup, Polygon& polygon) { return std::max(maximumSmoothingGroup, polygon.getSmoothingGroup()); }};void Mesh::calculateVertexNormals(){ // find out how many smoothing groups there are int maximumSmoothingGroup = std::accumulate(polygons.begin(), polygons.end(), 0, FindMaximumSmoothingGroup()); // for each smoothing group for (int smoothingGroupNumber = 0; smoothingGroupNumber < maximumSmoothingGroup + 1; ++smoothingGroupNumber) { // set up a set of vertex normals for this smoothing group std::vector<Point3f> vertexNormals(vertices.size()); // for each polygon for (unsigned int polygonNumber = 0; polygonNumber < polygons.size(); ++polygonNumber) { // if this polygon is in the current smoothing group if (polygons[polygonNumber].getSmoothingGroup() == smoothingGroupNumber) { // set the polygon vertices polygons[polygonNumber].setVertices(vertices[faces[polygonNumber].x], vertices[faces[polygonNumber].y], vertices[faces[polygonNumber].z]); // sum the polgon normal into the vertex normals for this smoothing group vertexNormals[faces[polygonNumber].x] += polygons[polygonNumber].getFaceNormal(); vertexNormals[faces[polygonNumber].y] += polygons[polygonNumber].getFaceNormal(); vertexNormals[faces[polygonNumber].z] += polygons[polygonNumber].getFaceNormal(); } } // normalise the vertex normals for this smoothing group std::for_each(vertexNormals.begin(), vertexNormals.end(), Normalise<Point3f>()); // for each polygon for (unsigned int polygonNumber = 0; polygonNumber < polygons.size(); ++polygonNumber) { // if this polygon is in the current smoothing group then copy the calculated vertex normals into it if (polygons[polygonNumber].getSmoothingGroup() == smoothingGroupNumber) { polygons[polygonNumber].setVertexNormals(vertexNormals[faces[polygonNumber].x], vertexNormals[faces[polygonNumber].y], vertexNormals[faces[polygonNumber].z]); } } }}void Mesh::print(std::ostream& writer) const{ writer << "vertices:\n"; std::copy(vertices.begin(), vertices.end(), std::ostream_iterator<Point3f>(writer, "\n")); writer << "\nfaces:\n"; std::copy(faces.begin(), faces.end(), std::ostream_iterator<Point3i>(writer, "\n")); writer << "\npolygons:\n"; std::copy(polygons.begin(), polygons.end(), std::ostream_iterator<Polygon>(writer, "\n"));}void Mesh::read(std::istream& reader){ int numberOfVertices; reader >> numberOfVertices; vertices.resize(numberOfVertices); int numberOfFaces; reader >> numberOfFaces; faces.resize(numberOfFaces); polygons.resize(numberOfFaces); bool verticesRead = false; bool facesRead = false; while (!reader.eof()) { while (std::isspace(reader.peek())) { reader.ignore(1); } char dataType = reader.get(); if (dataType == 'v') { readVertices(reader); verticesRead = true; } else if (dataType == 'f') { readFaces(reader); facesRead = true; } else if (dataType == 'n') { if (!verticesRead || !facesRead) { throw std::string("Need vertex & face information before faces"); } readNormals(reader); } else if (dataType == 'm') { if (!verticesRead || !facesRead) { throw std::string("Need vertex & face information before materials"); } readMaterials(reader); } else if (dataType == 's') { if (!verticesRead || !facesRead) { throw std::string("Need vertex & face information before smoothing groups"); } readSmoothingGroups(reader); } }}void Mesh::readVertices(std::istream& reader){ for (unsigned int vertexNumber = 0; vertexNumber < vertices.size(); ++vertexNumber) { Point3f vertex; reader >> vertex; vertices[vertexNumber] = vertex; }}void Mesh::readFaces(std::istream& reader){ for (unsigned int faceNumber = 0; faceNumber < faces.size(); ++faceNumber) { Point3i face; reader >> face; face.x -= 1; face.y -= 1; face.z -= 1; faces[faceNumber] = face; }}void Mesh::readNormals(std::istream& reader){ for (unsigned int polygonNumber = 0; polygonNumber < faces.size(); ++polygonNumber) { Point3f normal; reader >> normal; polygons[polygonNumber].setNormal(normal); }}void Mesh::readMaterials(std::istream& reader){ for (unsigned int polygonNumber = 0; polygonNumber < faces.size(); ++polygonNumber) { int materialId; while (std::isspace(reader.peek())) { reader.ignore(1); } reader >> materialId; polygons[polygonNumber].setMaterial(materialId); }}void Mesh::readSmoothingGroups(std::istream& reader){ for (unsigned int polygonNumber = 0; polygonNumber < faces.size(); ++polygonNumber) { int smoothingGroup; while (std::isspace(reader.peek())) { reader.ignore(1); } // ignore the #( and ) around the value reader.ignore(2); reader >> smoothingGroup; reader.ignore(1); polygons[polygonNumber].setSmoothingGroup(smoothingGroup); }}std::istream& operator>>(std::istream& reader, Mesh& mesh){ mesh.read(reader); return reader;}std::ostream& operator<<(std::ostream& writer, const Mesh& mesh){ mesh.print(writer); return writer;}#endif
Main application file. Note that by having classes with appropriate behaviour the high level view of the application is completely trivial.
#include <fstream>#include <iostream>#include <string>#include "Mesh.h"Mesh importLdm(std::string filename){ std::ifstream reader(filename.c_str()); if (!reader) { throw std::string("File not found"); } // natural reusable syntax for reading meshes. Mesh mesh; reader >> mesh; std::cout << mesh; mesh.calculateVertexNormals(); std::cout << mesh; return mesh;}int main(){ // no buffer overflow bugs std::string filename; std::cout << "input a filename with extension...\n"; std::getline(std::cin, filename); try { Mesh mesh = importLdm(filename); } catch (std::string& exception) { std::cout << "Error: " << exception << '\n'; }}
Hope this helps. One final comment I would make: The two most important things in C++ are whitespace and function/variable names. Source code is a communication medium - use it as such.
Enigma