csci5607/assignment-2b/src/trimesh.hpp
2023-04-16 00:22:02 -05:00

348 lines
9.1 KiB
C++
Executable file

// Copyright 2016 University of Minnesota
//
// TRIMESH Uses the BSD 2-Clause License
// (http://www.opensource.org/licenses/BSD-2-Clause) 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 "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
// UNIVERSITY OF MINNESOTA, DULUTH 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.
#ifndef TRIMESH_HPP
#define TRIMESH_HPP 1
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
//
// Vector Class
// Not complete, only has functions needed for this sample.
//
template <size_t D, class T> class Vec {
public:
// Constructors
Vec() {
data[0] = T(0);
data[1] = T(0);
data[2] = T(0);
}
Vec(T x_, T y_, T z_) {
data[0] = x_;
data[1] = y_;
data[2] = z_;
}
// Data
T data[3];
// Functions
T operator[](int i) const { return data[i]; }
T &operator[](int i) { return data[i]; }
Vec<D, T> &operator+=(const Vec<D, T> &x) {
for (size_t i = 0; i < D; ++i) {
data[i] += x[i];
}
return *this;
}
double len2() const { return this->dot(*this); } // squared length
double len() const { return sqrt(len2()); } // length
T dot(const Vec<D, T> &v) const {
T r(0);
for (size_t i = 0; i < D; ++i) {
r += v[i] * data[i];
}
return r;
}
Vec<3, T> cross(const Vec<3, T> &v) const {
assert(D == 3); // only defined for 3 dims
return Vec<3, T>(data[1] * v[2] - data[2] * v[1],
data[2] * v[0] - data[0] * v[2],
data[0] * v[1] - data[1] * v[0]);
}
void normalize() {
double l = len();
if (l <= 0.0) {
return;
}
for (size_t i = 0; i < D; ++i) {
data[i] = data[i] / l;
}
}
};
template <size_t D, class T>
static inline const Vec<D, T> operator-(const Vec<D, T> &v1,
const Vec<D, T> &v2) {
Vec<D, T> r;
for (size_t i = 0; i < D; ++i) {
r[i] = v1[i] - v2[i];
}
return r;
}
template <size_t D, class T>
static inline const Vec<D, T> operator*(const Vec<D, T> &v, const T &x) {
Vec<D, T> r;
for (size_t i = 0; i < D; ++i) {
r[i] = v[i] * x;
}
return r;
}
typedef Vec<3, float> Vec3f;
typedef Vec<3, int> Vec3i;
//
// Triangle Mesh Class
//
class TriMesh {
public:
std::vector<Vec3f> vertices;
std::vector<Vec3f> normals;
std::vector<Vec3f> colors;
std::vector<Vec3i> faces;
// Compute normals if not loaded from obj
// or if recompute is set to true.
void need_normals(bool recompute = false);
// Sets a default vertex color if
// they haven't been set.
void need_colors(Vec3f default_color = Vec3f(0.4, 0.4, 0.4));
// Loads an OBJ file
bool load_obj(std::string file);
// Prints details about the mesh
void print_details();
};
//
// Implementation
//
void TriMesh::print_details() {
std::cout << "Vertices: " << vertices.size() << std::endl;
std::cout << "Normals: " << normals.size() << std::endl;
std::cout << "Colors: " << colors.size() << std::endl;
std::cout << "Faces: " << faces.size() << std::endl;
}
void TriMesh::need_normals(bool recompute) {
if (vertices.size() == normals.size() && !recompute) {
return;
}
if (normals.size() != vertices.size()) {
normals.resize(vertices.size());
}
std::cout << "Computing TriMesh normals" << std::endl;
const int nv = normals.size();
for (int i = 0; i < nv; ++i) {
normals[i][0] = 0.f;
normals[i][1] = 0.f;
normals[i][2] = 0.f;
}
int nf = faces.size();
for (int f = 0; f < nf; ++f) {
Vec3i face = faces[f];
const Vec3f &p0 = vertices[face[0]];
const Vec3f &p1 = vertices[face[1]];
const Vec3f &p2 = vertices[face[2]];
Vec3f a = p0 - p1, b = p1 - p2, c = p2 - p0;
float l2a = a.len2(), l2b = b.len2(), l2c = c.len2();
if (!l2a || !l2b || !l2c) {
continue;
} // check for zeros or nans
Vec3f facenormal = a.cross(b);
normals[faces[f][0]] += facenormal * (1.0f / (l2a * l2c));
normals[faces[f][1]] += facenormal * (1.0f / (l2b * l2a));
normals[faces[f][2]] += facenormal * (1.0f / (l2c * l2b));
}
for (int i = 0; i < nv; i++) {
normals[i].normalize();
}
} // end need normals
void TriMesh::need_colors(Vec3f default_color) {
if (vertices.size() == colors.size()) {
return;
} else {
colors.resize(vertices.size(), default_color);
}
} // end need colors
// Function to split a string into multiple strings, seperated by delimeter
static void split_str(char delim, const std::string &str,
std::vector<std::string> *result) {
std::stringstream ss(str);
std::string s;
while (std::getline(ss, s, delim)) {
result->push_back(s);
}
}
bool TriMesh::load_obj(std::string file) {
std::cout << "\nLoading " << file << std::endl;
// README:
//
// The problem with standard obj files and opengl is that
// there isn't a good way to make triangles with different indices
// for vertices/normals. At least, not any way that I'm aware of.
// So for now, we'll do the inefficient (but robust) way:
// redundant vertices/normals.
//
std::vector<Vec3f> temp_normals;
std::vector<Vec3f> temp_verts;
std::vector<Vec3f> temp_colors;
//
// First loop, make buffers
//
std::ifstream infile(file.c_str());
if (infile.is_open()) {
std::string line;
while (std::getline(infile, line)) {
std::stringstream ss(line);
std::string tok;
ss >> tok;
// Vertex
if (tok == "v") {
// First three location
float x, y, z;
ss >> x >> y >> z;
temp_verts.push_back(Vec3f(x, y, z));
// Next three colors
float cx, cy, cz;
if (ss >> cx >> cy >> cz) {
temp_colors.push_back(Vec3f(cx, cy, cz));
} else {
temp_colors.push_back(Vec3f(0.3f, 0.3f, 0.3f));
}
}
// Normal
if (tok == "vn") {
float x, y, z;
ss >> x >> y >> z;
temp_normals.push_back(Vec3f(x, y, z));
}
} // end loop lines
} // end load obj
else {
std::cerr << "\n**TriMesh::load_obj Error: Could not open file " << file
<< std::endl;
return false;
}
//
// Second loop, make faces
//
std::ifstream infile2(file.c_str());
if (infile2.is_open()) {
std::string line;
while (std::getline(infile2, line)) {
std::stringstream ss(line);
std::string tok;
ss >> tok;
// Face
if (tok == "f") {
Vec3i face;
// Get the three vertices
for (size_t i = 0; i < 3; ++i) {
std::string f_str;
ss >> f_str;
std::vector<std::string> f_vals;
split_str('/', f_str, &f_vals);
assert(f_vals.size() > 0);
face[i] = vertices.size();
int v_idx = std::stoi(f_vals[0]) - 1;
vertices.push_back(temp_verts[v_idx]);
colors.push_back(temp_colors[v_idx]);
// Check for normal
if (f_vals.size() > 2) {
int n_idx = std::stoi(f_vals[2]) - 1;
normals.push_back(temp_normals[n_idx]);
}
}
faces.push_back(face);
// If it's a quad, make another triangle
std::string last_vert = "";
if (ss >> last_vert) {
Vec3i face2;
face2[0] = face[0];
face2[1] = face[2];
std::vector<std::string> f_vals;
split_str('/', last_vert, &f_vals);
assert(f_vals.size() > 0);
int v_idx = std::stoi(f_vals[0]) - 1;
vertices.push_back(temp_verts[v_idx]);
colors.push_back(temp_colors[v_idx]);
face2[2] = vertices.size();
// Check for normal
if (f_vals.size() > 2) {
int n_idx = std::stoi(f_vals[2]) - 1;
normals.push_back(temp_normals[n_idx]);
}
faces.push_back(face2);
}
} // end parse face
} // end loop lines
} // end load obj
// Make sure we have normals
if (!normals.size()) {
std::cout << "**Warning: normals not loaded so we'll compute them instead."
<< std::endl;
need_normals();
}
return true;
} // end load obj
#endif