2023-04-10 03:01:36 +00:00
|
|
|
// Copyright 2016 University of Minnesota
|
2023-04-16 05:22:02 +00:00
|
|
|
//
|
|
|
|
// 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
|
2023-04-10 03:01:36 +00:00
|
|
|
// conditions and the following disclaimer.
|
2023-04-16 05:22:02 +00:00
|
|
|
// 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.
|
2023-04-10 03:01:36 +00:00
|
|
|
|
|
|
|
#ifndef TRIMESH_HPP
|
|
|
|
#define TRIMESH_HPP 1
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <cmath>
|
2023-04-16 05:22:02 +00:00
|
|
|
#include <fstream>
|
2023-04-10 03:01:36 +00:00
|
|
|
#include <iostream>
|
2023-04-16 05:22:02 +00:00
|
|
|
#include <sstream>
|
|
|
|
#include <vector>
|
2023-04-10 03:01:36 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Vector Class
|
|
|
|
// Not complete, only has functions needed for this sample.
|
|
|
|
//
|
|
|
|
template <size_t D, class T> class Vec {
|
|
|
|
public:
|
2023-04-16 05:22:02 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
2023-04-10 03:01:36 +00:00
|
|
|
};
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
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;
|
2023-04-10 03:01:36 +00:00
|
|
|
}
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
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;
|
2023-04-10 03:01:36 +00:00
|
|
|
}
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
typedef Vec<3, float> Vec3f;
|
|
|
|
typedef Vec<3, int> Vec3i;
|
2023-04-10 03:01:36 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Triangle Mesh Class
|
|
|
|
//
|
|
|
|
class TriMesh {
|
|
|
|
public:
|
2023-04-16 05:22:02 +00:00
|
|
|
std::vector<Vec3f> vertices;
|
|
|
|
std::vector<Vec3f> normals;
|
|
|
|
std::vector<Vec3f> colors;
|
|
|
|
std::vector<Vec3i> faces;
|
2023-04-10 03:01:36 +00:00
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
// Compute normals if not loaded from obj
|
|
|
|
// or if recompute is set to true.
|
|
|
|
void need_normals(bool recompute = false);
|
2023-04-10 03:01:36 +00:00
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
// Sets a default vertex color if
|
|
|
|
// they haven't been set.
|
|
|
|
void need_colors(Vec3f default_color = Vec3f(0.4, 0.4, 0.4));
|
2023-04-10 03:01:36 +00:00
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
// Loads an OBJ file
|
|
|
|
bool load_obj(std::string file);
|
2023-04-10 03:01:36 +00:00
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
// Prints details about the mesh
|
|
|
|
void print_details();
|
2023-04-10 03:01:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Implementation
|
|
|
|
//
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
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;
|
2023-04-10 03:01:36 +00:00
|
|
|
}
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
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();
|
|
|
|
}
|
2023-04-10 03:01:36 +00:00
|
|
|
} // end need normals
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
void TriMesh::need_colors(Vec3f default_color) {
|
|
|
|
if (vertices.size() == colors.size()) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
colors.resize(vertices.size(), default_color);
|
|
|
|
}
|
2023-04-10 03:01:36 +00:00
|
|
|
} // end need colors
|
|
|
|
|
|
|
|
// Function to split a string into multiple strings, seperated by delimeter
|
2023-04-16 05:22:02 +00:00
|
|
|
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);
|
|
|
|
}
|
2023-04-10 03:01:36 +00:00
|
|
|
}
|
|
|
|
|
2023-04-16 05:22:02 +00:00
|
|
|
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;
|
2023-04-10 03:01:36 +00:00
|
|
|
|
|
|
|
} // end load obj
|
|
|
|
|
|
|
|
#endif
|