1194 lines
40 KiB
C++
1194 lines
40 KiB
C++
// OBJ_Loader.h - A Single Header OBJ Model Loader
|
|
// Source: https://github.com/Bly7/OBJ-Loader
|
|
|
|
// MIT License
|
|
//
|
|
// Copyright (c) 2016 Robert Smith
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
#ifndef RAYTRYCPP_OBJ_LOADER_H
|
|
#define RAYTRYCPP_OBJ_LOADER_H
|
|
|
|
// Iostream - STD I/O Library
|
|
#include <iostream>
|
|
|
|
// Vector - STD Vector/Array Library
|
|
#include <vector>
|
|
|
|
// String - STD String Library
|
|
#include <string>
|
|
|
|
// fStream - STD File I/O Library
|
|
#include <fstream>
|
|
|
|
// Math.h - STD math Library
|
|
#include <math.h>
|
|
|
|
// Print progress to console while loading (large models)
|
|
//#define OBJL_CONSOLE_OUTPUT
|
|
|
|
// Namespace: OBJL
|
|
//
|
|
// Description: The namespace that holds eveyrthing that
|
|
// is needed and used for the OBJ Model Loader
|
|
namespace objl
|
|
{
|
|
// Structure: Vector2
|
|
//
|
|
// Description: A 2D Vector that Holds Positional Data
|
|
struct Vector2
|
|
{
|
|
// Default Constructor
|
|
Vector2()
|
|
{
|
|
X = 0.0f;
|
|
Y = 0.0f;
|
|
}
|
|
// Variable Set Constructor
|
|
Vector2(float X_, float Y_)
|
|
{
|
|
X = X_;
|
|
Y = Y_;
|
|
}
|
|
// Bool Equals Operator Overload
|
|
bool operator==(const Vector2& other) const
|
|
{
|
|
return (this->X == other.X && this->Y == other.Y);
|
|
}
|
|
// Bool Not Equals Operator Overload
|
|
bool operator!=(const Vector2& other) const
|
|
{
|
|
return !(this->X == other.X && this->Y == other.Y);
|
|
}
|
|
// Addition Operator Overload
|
|
Vector2 operator+(const Vector2& right) const
|
|
{
|
|
return Vector2(this->X + right.X, this->Y + right.Y);
|
|
}
|
|
// Subtraction Operator Overload
|
|
Vector2 operator-(const Vector2& right) const
|
|
{
|
|
return Vector2(this->X - right.X, this->Y - right.Y);
|
|
}
|
|
// Float Multiplication Operator Overload
|
|
Vector2 operator*(const float& other) const
|
|
{
|
|
return Vector2(this->X *other, this->Y * other);
|
|
}
|
|
|
|
// Positional Variables
|
|
float X;
|
|
float Y;
|
|
};
|
|
|
|
// Structure: Vector3
|
|
//
|
|
// Description: A 3D Vector that Holds Positional Data
|
|
struct Vector3
|
|
{
|
|
// Default Constructor
|
|
Vector3()
|
|
{
|
|
X = 0.0f;
|
|
Y = 0.0f;
|
|
Z = 0.0f;
|
|
}
|
|
// Variable Set Constructor
|
|
Vector3(float X_, float Y_, float Z_)
|
|
{
|
|
X = X_;
|
|
Y = Y_;
|
|
Z = Z_;
|
|
}
|
|
// Bool Equals Operator Overload
|
|
bool operator==(const Vector3& other) const
|
|
{
|
|
return (this->X == other.X && this->Y == other.Y && this->Z == other.Z);
|
|
}
|
|
// Bool Not Equals Operator Overload
|
|
bool operator!=(const Vector3& other) const
|
|
{
|
|
return !(this->X == other.X && this->Y == other.Y && this->Z == other.Z);
|
|
}
|
|
// Addition Operator Overload
|
|
Vector3 operator+(const Vector3& right) const
|
|
{
|
|
return Vector3(this->X + right.X, this->Y + right.Y, this->Z + right.Z);
|
|
}
|
|
// Subtraction Operator Overload
|
|
Vector3 operator-(const Vector3& right) const
|
|
{
|
|
return Vector3(this->X - right.X, this->Y - right.Y, this->Z - right.Z);
|
|
}
|
|
// Float Multiplication Operator Overload
|
|
Vector3 operator*(const float& other) const
|
|
{
|
|
return Vector3(this->X * other, this->Y * other, this->Z * other);
|
|
}
|
|
// Float Division Operator Overload
|
|
Vector3 operator/(const float& other) const
|
|
{
|
|
return Vector3(this->X / other, this->Y / other, this->Z / other);
|
|
}
|
|
|
|
// Positional Variables
|
|
float X;
|
|
float Y;
|
|
float Z;
|
|
};
|
|
|
|
// Structure: Vertex
|
|
//
|
|
// Description: Model Vertex object that holds
|
|
// a Position, Normal, and Texture Coordinate
|
|
struct Vertex
|
|
{
|
|
// Position Vector
|
|
Vector3 Position;
|
|
|
|
// Normal Vector
|
|
Vector3 Normal;
|
|
|
|
// Texture Coordinate Vector
|
|
Vector2 TextureCoordinate;
|
|
};
|
|
|
|
struct Material
|
|
{
|
|
Material()
|
|
{
|
|
name;
|
|
Ns = 0.0f;
|
|
Ni = 0.0f;
|
|
d = 0.0f;
|
|
illum = 0;
|
|
}
|
|
|
|
// Material Name
|
|
std::string name;
|
|
// Ambient Color
|
|
Vector3 Ka;
|
|
// Diffuse Color
|
|
Vector3 Kd;
|
|
// Specular Color
|
|
Vector3 Ks;
|
|
// Specular Exponent
|
|
float Ns;
|
|
// Optical Density
|
|
float Ni;
|
|
// Dissolve
|
|
float d;
|
|
// Illumination
|
|
int illum;
|
|
// Ambient Texture Map
|
|
std::string map_Ka;
|
|
// Diffuse Texture Map
|
|
std::string map_Kd;
|
|
// Specular Texture Map
|
|
std::string map_Ks;
|
|
// Specular Hightlight Map
|
|
std::string map_Ns;
|
|
// Alpha Texture Map
|
|
std::string map_d;
|
|
// Bump Map
|
|
std::string map_bump;
|
|
};
|
|
|
|
// Structure: Mesh
|
|
//
|
|
// Description: A Simple Mesh Object that holds
|
|
// a name, a vertex list, and an index list
|
|
struct Mesh
|
|
{
|
|
// Default Constructor
|
|
Mesh()
|
|
{
|
|
|
|
}
|
|
// Variable Set Constructor
|
|
Mesh(std::vector<Vertex>& _Vertices, std::vector<unsigned int>& _Indices)
|
|
{
|
|
Vertices = _Vertices;
|
|
Indices = _Indices;
|
|
}
|
|
// Mesh Name
|
|
std::string MeshName;
|
|
// Vertex List
|
|
std::vector<Vertex> Vertices;
|
|
// Index List
|
|
std::vector<unsigned int> Indices;
|
|
|
|
// Material
|
|
Material MeshMaterial;
|
|
};
|
|
|
|
// Namespace: Math
|
|
//
|
|
// Description: The namespace that holds all of the math
|
|
// functions need for OBJL
|
|
namespace math
|
|
{
|
|
// Vector3 Cross Product
|
|
Vector3 CrossV3(const Vector3 a, const Vector3 b)
|
|
{
|
|
return Vector3(a.Y * b.Z - a.Z * b.Y,
|
|
a.Z * b.X - a.X * b.Z,
|
|
a.X * b.Y - a.Y * b.X);
|
|
}
|
|
|
|
// Vector3 Magnitude Calculation
|
|
float MagnitudeV3(const Vector3 in)
|
|
{
|
|
return (sqrtf(powf(in.X, 2) + powf(in.Y, 2) + powf(in.Z, 2)));
|
|
}
|
|
|
|
// Vector3 DotProduct
|
|
float DotV3(const Vector3 a, const Vector3 b)
|
|
{
|
|
return (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z);
|
|
}
|
|
|
|
// Angle between 2 Vector3 Objects
|
|
float AngleBetweenV3(const Vector3 a, const Vector3 b)
|
|
{
|
|
float angle = DotV3(a, b);
|
|
angle /= (MagnitudeV3(a) * MagnitudeV3(b));
|
|
return angle = acosf(angle);
|
|
}
|
|
|
|
// Projection Calculation of a onto b
|
|
Vector3 ProjV3(const Vector3 a, const Vector3 b)
|
|
{
|
|
Vector3 bn = b / MagnitudeV3(b);
|
|
return bn * DotV3(a, bn);
|
|
}
|
|
}
|
|
|
|
// Namespace: Algorithm
|
|
//
|
|
// Description: The namespace that holds all of the
|
|
// Algorithms needed for OBJL
|
|
namespace algorithm
|
|
{
|
|
// Vector3 Multiplication Opertor Overload
|
|
Vector3 operator*(const float& left, const Vector3& right)
|
|
{
|
|
return Vector3(right.X * left, right.Y * left, right.Z * left);
|
|
}
|
|
|
|
// A test to see if P1 is on the same side as P2 of a line segment ab
|
|
bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b)
|
|
{
|
|
Vector3 cp1 = math::CrossV3(b - a, p1 - a);
|
|
Vector3 cp2 = math::CrossV3(b - a, p2 - a);
|
|
|
|
if (math::DotV3(cp1, cp2) >= 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Generate a cross produect normal for a triangle
|
|
Vector3 GenTriNormal(Vector3 t1, Vector3 t2, Vector3 t3)
|
|
{
|
|
Vector3 u = t2 - t1;
|
|
Vector3 v = t3 - t1;
|
|
|
|
Vector3 normal = math::CrossV3(u,v);
|
|
|
|
return normal;
|
|
}
|
|
|
|
// Check to see if a Vector3 Point is within a 3 Vector3 Triangle
|
|
bool inTriangle(Vector3 point, Vector3 tri1, Vector3 tri2, Vector3 tri3)
|
|
{
|
|
// Test to see if it is within an infinite prism that the triangle outlines.
|
|
bool within_tri_prisim = SameSide(point, tri1, tri2, tri3) && SameSide(point, tri2, tri1, tri3)
|
|
&& SameSide(point, tri3, tri1, tri2);
|
|
|
|
// If it isn't it will never be on the triangle
|
|
if (!within_tri_prisim)
|
|
return false;
|
|
|
|
// Calulate Triangle's Normal
|
|
Vector3 n = GenTriNormal(tri1, tri2, tri3);
|
|
|
|
// Project the point onto this normal
|
|
Vector3 proj = math::ProjV3(point, n);
|
|
|
|
// If the distance from the triangle to the point is 0
|
|
// it lies on the triangle
|
|
if (math::MagnitudeV3(proj) == 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Split a String into a string array at a given token
|
|
inline void split(const std::string &in,
|
|
std::vector<std::string> &out,
|
|
std::string token)
|
|
{
|
|
out.clear();
|
|
|
|
std::string temp;
|
|
|
|
for (int i = 0; i < int(in.size()); i++)
|
|
{
|
|
std::string test = in.substr(i, token.size());
|
|
|
|
if (test == token)
|
|
{
|
|
if (!temp.empty())
|
|
{
|
|
out.push_back(temp);
|
|
temp.clear();
|
|
i += (int)token.size() - 1;
|
|
}
|
|
else
|
|
{
|
|
out.push_back("");
|
|
}
|
|
}
|
|
else if (i + token.size() >= in.size())
|
|
{
|
|
temp += in.substr(i, token.size());
|
|
out.push_back(temp);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
temp += in[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get tail of string after first token and possibly following spaces
|
|
inline std::string tail(const std::string &in)
|
|
{
|
|
size_t token_start = in.find_first_not_of(" \t");
|
|
size_t space_start = in.find_first_of(" \t", token_start);
|
|
size_t tail_start = in.find_first_not_of(" \t", space_start);
|
|
size_t tail_end = in.find_last_not_of(" \t");
|
|
if (tail_start != std::string::npos && tail_end != std::string::npos)
|
|
{
|
|
return in.substr(tail_start, tail_end - tail_start + 1);
|
|
}
|
|
else if (tail_start != std::string::npos)
|
|
{
|
|
return in.substr(tail_start);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Get first token of string
|
|
inline std::string firstToken(const std::string &in)
|
|
{
|
|
if (!in.empty())
|
|
{
|
|
size_t token_start = in.find_first_not_of(" \t");
|
|
size_t token_end = in.find_first_of(" \t", token_start);
|
|
if (token_start != std::string::npos && token_end != std::string::npos)
|
|
{
|
|
return in.substr(token_start, token_end - token_start);
|
|
}
|
|
else if (token_start != std::string::npos)
|
|
{
|
|
return in.substr(token_start);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Get element at given index position
|
|
template <class T>
|
|
inline const T & getElement(const std::vector<T> &elements, std::string &index)
|
|
{
|
|
int idx = std::stoi(index);
|
|
if (idx < 0)
|
|
idx = int(elements.size()) + idx;
|
|
else
|
|
idx--;
|
|
return elements[idx];
|
|
}
|
|
}
|
|
|
|
// Class: Loader
|
|
//
|
|
// Description: The OBJ Model Loader
|
|
class Loader
|
|
{
|
|
public:
|
|
// Default Constructor
|
|
Loader()
|
|
{
|
|
|
|
}
|
|
~Loader()
|
|
{
|
|
LoadedMeshes.clear();
|
|
}
|
|
|
|
// Load a file into the loader
|
|
//
|
|
// If file is loaded return true
|
|
//
|
|
// If the file is unable to be found
|
|
// or unable to be loaded return false
|
|
bool LoadFile(std::string Path)
|
|
{
|
|
// If the file is not an .obj file return false
|
|
if (Path.substr(Path.size() - 4, 4) != ".obj")
|
|
return false;
|
|
|
|
|
|
std::ifstream file(Path);
|
|
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
LoadedMeshes.clear();
|
|
LoadedVertices.clear();
|
|
LoadedIndices.clear();
|
|
|
|
std::vector<Vector3> Positions;
|
|
std::vector<Vector2> TCoords;
|
|
std::vector<Vector3> Normals;
|
|
|
|
std::vector<Vertex> Vertices;
|
|
std::vector<unsigned int> Indices;
|
|
|
|
std::vector<std::string> MeshMatNames;
|
|
|
|
bool listening = false;
|
|
std::string meshname;
|
|
|
|
Mesh tempMesh;
|
|
|
|
#ifdef OBJL_CONSOLE_OUTPUT
|
|
const unsigned int outputEveryNth = 1000;
|
|
unsigned int outputIndicator = outputEveryNth;
|
|
#endif
|
|
|
|
std::string curline;
|
|
while (std::getline(file, curline))
|
|
{
|
|
#ifdef OBJL_CONSOLE_OUTPUT
|
|
if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1)
|
|
{
|
|
if (!meshname.empty())
|
|
{
|
|
std::cout
|
|
<< "\r- " << meshname
|
|
<< "\t| vertices > " << Positions.size()
|
|
<< "\t| texcoords > " << TCoords.size()
|
|
<< "\t| normals > " << Normals.size()
|
|
<< "\t| triangles > " << (Vertices.size() / 3)
|
|
<< (!MeshMatNames.empty() ? "\t| material: " + MeshMatNames.back() : "");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Generate a Mesh Object or Prepare for an object to be created
|
|
if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g" || curline[0] == 'g')
|
|
{
|
|
if (!listening)
|
|
{
|
|
listening = true;
|
|
|
|
if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g")
|
|
{
|
|
meshname = algorithm::tail(curline);
|
|
}
|
|
else
|
|
{
|
|
meshname = "unnamed";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Generate the mesh to put into the array
|
|
|
|
if (!Indices.empty() && !Vertices.empty())
|
|
{
|
|
// Create Mesh
|
|
tempMesh = Mesh(Vertices, Indices);
|
|
tempMesh.MeshName = meshname;
|
|
|
|
// Insert Mesh
|
|
LoadedMeshes.push_back(tempMesh);
|
|
|
|
// Cleanup
|
|
Vertices.clear();
|
|
Indices.clear();
|
|
meshname.clear();
|
|
|
|
meshname = algorithm::tail(curline);
|
|
}
|
|
else
|
|
{
|
|
if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g")
|
|
{
|
|
meshname = algorithm::tail(curline);
|
|
}
|
|
else
|
|
{
|
|
meshname = "unnamed";
|
|
}
|
|
}
|
|
}
|
|
#ifdef OBJL_CONSOLE_OUTPUT
|
|
std::cout << std::endl;
|
|
outputIndicator = 0;
|
|
#endif
|
|
}
|
|
// Generate a Vertex Position
|
|
if (algorithm::firstToken(curline) == "v")
|
|
{
|
|
std::vector<std::string> spos;
|
|
Vector3 vpos;
|
|
algorithm::split(algorithm::tail(curline), spos, " ");
|
|
|
|
vpos.X = std::stof(spos[0]);
|
|
vpos.Y = std::stof(spos[1]);
|
|
vpos.Z = std::stof(spos[2]);
|
|
|
|
Positions.push_back(vpos);
|
|
}
|
|
// Generate a Vertex Texture Coordinate
|
|
if (algorithm::firstToken(curline) == "vt")
|
|
{
|
|
std::vector<std::string> stex;
|
|
Vector2 vtex;
|
|
algorithm::split(algorithm::tail(curline), stex, " ");
|
|
|
|
vtex.X = std::stof(stex[0]);
|
|
vtex.Y = std::stof(stex[1]);
|
|
|
|
TCoords.push_back(vtex);
|
|
}
|
|
// Generate a Vertex Normal;
|
|
if (algorithm::firstToken(curline) == "vn")
|
|
{
|
|
std::vector<std::string> snor;
|
|
Vector3 vnor;
|
|
algorithm::split(algorithm::tail(curline), snor, " ");
|
|
|
|
vnor.X = std::stof(snor[0]);
|
|
vnor.Y = std::stof(snor[1]);
|
|
vnor.Z = std::stof(snor[2]);
|
|
|
|
Normals.push_back(vnor);
|
|
}
|
|
// Generate a Face (vertices & indices)
|
|
if (algorithm::firstToken(curline) == "f")
|
|
{
|
|
// Generate the vertices
|
|
std::vector<Vertex> vVerts;
|
|
GenVerticesFromRawOBJ(vVerts, Positions, TCoords, Normals, curline);
|
|
|
|
// Add Vertices
|
|
for (int i = 0; i < int(vVerts.size()); i++)
|
|
{
|
|
Vertices.push_back(vVerts[i]);
|
|
|
|
LoadedVertices.push_back(vVerts[i]);
|
|
}
|
|
|
|
std::vector<unsigned int> iIndices;
|
|
|
|
VertexTriangluation(iIndices, vVerts);
|
|
|
|
// Add Indices
|
|
for (int i = 0; i < int(iIndices.size()); i++)
|
|
{
|
|
unsigned int indnum = (unsigned int)((Vertices.size()) - vVerts.size()) + iIndices[i];
|
|
Indices.push_back(indnum);
|
|
|
|
indnum = (unsigned int)((LoadedVertices.size()) - vVerts.size()) + iIndices[i];
|
|
LoadedIndices.push_back(indnum);
|
|
|
|
}
|
|
}
|
|
// Get Mesh Material Name
|
|
if (algorithm::firstToken(curline) == "usemtl")
|
|
{
|
|
MeshMatNames.push_back(algorithm::tail(curline));
|
|
|
|
// Create new Mesh, if Material changes within a group
|
|
if (!Indices.empty() && !Vertices.empty())
|
|
{
|
|
// Create Mesh
|
|
tempMesh = Mesh(Vertices, Indices);
|
|
tempMesh.MeshName = meshname;
|
|
int i = 2;
|
|
while(1) {
|
|
tempMesh.MeshName = meshname + "_" + std::to_string(i);
|
|
|
|
for (auto &m : LoadedMeshes)
|
|
if (m.MeshName == tempMesh.MeshName)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
// Insert Mesh
|
|
LoadedMeshes.push_back(tempMesh);
|
|
|
|
// Cleanup
|
|
Vertices.clear();
|
|
Indices.clear();
|
|
}
|
|
|
|
#ifdef OBJL_CONSOLE_OUTPUT
|
|
outputIndicator = 0;
|
|
#endif
|
|
}
|
|
// Load Materials
|
|
if (algorithm::firstToken(curline) == "mtllib")
|
|
{
|
|
// Generate LoadedMaterial
|
|
|
|
// Generate a path to the material file
|
|
std::vector<std::string> temp;
|
|
algorithm::split(Path, temp, "/");
|
|
|
|
std::string pathtomat = "";
|
|
|
|
if (temp.size() != 1)
|
|
{
|
|
for (int i = 0; i < temp.size() - 1; i++)
|
|
{
|
|
pathtomat += temp[i] + "/";
|
|
}
|
|
}
|
|
|
|
|
|
pathtomat += algorithm::tail(curline);
|
|
|
|
#ifdef OBJL_CONSOLE_OUTPUT
|
|
std::cout << std::endl << "- find materials in: " << pathtomat << std::endl;
|
|
#endif
|
|
|
|
// Load Materials
|
|
LoadMaterials(pathtomat);
|
|
}
|
|
}
|
|
|
|
#ifdef OBJL_CONSOLE_OUTPUT
|
|
std::cout << std::endl;
|
|
#endif
|
|
|
|
// Deal with last mesh
|
|
|
|
if (!Indices.empty() && !Vertices.empty())
|
|
{
|
|
// Create Mesh
|
|
tempMesh = Mesh(Vertices, Indices);
|
|
tempMesh.MeshName = meshname;
|
|
|
|
// Insert Mesh
|
|
LoadedMeshes.push_back(tempMesh);
|
|
}
|
|
|
|
file.close();
|
|
|
|
// Set Materials for each Mesh
|
|
for (int i = 0; i < MeshMatNames.size(); i++)
|
|
{
|
|
std::string matname = MeshMatNames[i];
|
|
|
|
// Find corresponding material name in loaded materials
|
|
// when found copy material variables into mesh material
|
|
for (int j = 0; j < LoadedMaterials.size(); j++)
|
|
{
|
|
if (LoadedMaterials[j].name == matname)
|
|
{
|
|
LoadedMeshes[i].MeshMaterial = LoadedMaterials[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LoadedMeshes.empty() && LoadedVertices.empty() && LoadedIndices.empty())
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Loaded Mesh Objects
|
|
std::vector<Mesh> LoadedMeshes;
|
|
// Loaded Vertex Objects
|
|
std::vector<Vertex> LoadedVertices;
|
|
// Loaded Index Positions
|
|
std::vector<unsigned int> LoadedIndices;
|
|
// Loaded Material Objects
|
|
std::vector<Material> LoadedMaterials;
|
|
|
|
private:
|
|
// Generate vertices from a list of positions,
|
|
// tcoords, normals and a face line
|
|
void GenVerticesFromRawOBJ(std::vector<Vertex>& oVerts,
|
|
const std::vector<Vector3>& iPositions,
|
|
const std::vector<Vector2>& iTCoords,
|
|
const std::vector<Vector3>& iNormals,
|
|
std::string icurline)
|
|
{
|
|
std::vector<std::string> sface, svert;
|
|
Vertex vVert;
|
|
algorithm::split(algorithm::tail(icurline), sface, " ");
|
|
|
|
bool noNormal = false;
|
|
|
|
// For every given vertex do this
|
|
for (int i = 0; i < int(sface.size()); i++)
|
|
{
|
|
// See What type the vertex is.
|
|
int vtype;
|
|
|
|
algorithm::split(sface[i], svert, "/");
|
|
|
|
// Check for just position - v1
|
|
if (svert.size() == 1)
|
|
{
|
|
// Only position
|
|
vtype = 1;
|
|
}
|
|
|
|
// Check for position & texture - v1/vt1
|
|
if (svert.size() == 2)
|
|
{
|
|
// Position & Texture
|
|
vtype = 2;
|
|
}
|
|
|
|
// Check for Position, Texture and Normal - v1/vt1/vn1
|
|
// or if Position and Normal - v1//vn1
|
|
if (svert.size() == 3)
|
|
{
|
|
if (svert[1] != "")
|
|
{
|
|
// Position, Texture, and Normal
|
|
vtype = 4;
|
|
}
|
|
else
|
|
{
|
|
// Position & Normal
|
|
vtype = 3;
|
|
}
|
|
}
|
|
|
|
// Calculate and store the vertex
|
|
switch (vtype)
|
|
{
|
|
case 1: // P
|
|
{
|
|
vVert.Position = algorithm::getElement(iPositions, svert[0]);
|
|
vVert.TextureCoordinate = Vector2(0, 0);
|
|
noNormal = true;
|
|
oVerts.push_back(vVert);
|
|
break;
|
|
}
|
|
case 2: // P/T
|
|
{
|
|
vVert.Position = algorithm::getElement(iPositions, svert[0]);
|
|
vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]);
|
|
noNormal = true;
|
|
oVerts.push_back(vVert);
|
|
break;
|
|
}
|
|
case 3: // P//N
|
|
{
|
|
vVert.Position = algorithm::getElement(iPositions, svert[0]);
|
|
vVert.TextureCoordinate = Vector2(0, 0);
|
|
vVert.Normal = algorithm::getElement(iNormals, svert[2]);
|
|
oVerts.push_back(vVert);
|
|
break;
|
|
}
|
|
case 4: // P/T/N
|
|
{
|
|
vVert.Position = algorithm::getElement(iPositions, svert[0]);
|
|
vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]);
|
|
vVert.Normal = algorithm::getElement(iNormals, svert[2]);
|
|
oVerts.push_back(vVert);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// take care of missing normals
|
|
// these may not be truly acurate but it is the
|
|
// best they get for not compiling a mesh with normals
|
|
if (noNormal)
|
|
{
|
|
Vector3 A = oVerts[0].Position - oVerts[1].Position;
|
|
Vector3 B = oVerts[2].Position - oVerts[1].Position;
|
|
|
|
Vector3 normal = math::CrossV3(A, B);
|
|
|
|
for (int i = 0; i < int(oVerts.size()); i++)
|
|
{
|
|
oVerts[i].Normal = normal;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Triangulate a list of vertices into a face by printing
|
|
// inducies corresponding with triangles within it
|
|
void VertexTriangluation(std::vector<unsigned int>& oIndices,
|
|
const std::vector<Vertex>& iVerts)
|
|
{
|
|
// If there are 2 or less verts,
|
|
// no triangle can be created,
|
|
// so exit
|
|
if (iVerts.size() < 3)
|
|
{
|
|
return;
|
|
}
|
|
// If it is a triangle no need to calculate it
|
|
if (iVerts.size() == 3)
|
|
{
|
|
oIndices.push_back(0);
|
|
oIndices.push_back(1);
|
|
oIndices.push_back(2);
|
|
return;
|
|
}
|
|
|
|
// Create a list of vertices
|
|
std::vector<Vertex> tVerts = iVerts;
|
|
|
|
while (true)
|
|
{
|
|
// For every vertex
|
|
for (int i = 0; i < int(tVerts.size()); i++)
|
|
{
|
|
// pPrev = the previous vertex in the list
|
|
Vertex pPrev;
|
|
if (i == 0)
|
|
{
|
|
pPrev = tVerts[tVerts.size() - 1];
|
|
}
|
|
else
|
|
{
|
|
pPrev = tVerts[i - 1];
|
|
}
|
|
|
|
// pCur = the current vertex;
|
|
Vertex pCur = tVerts[i];
|
|
|
|
// pNext = the next vertex in the list
|
|
Vertex pNext;
|
|
if (i == tVerts.size() - 1)
|
|
{
|
|
pNext = tVerts[0];
|
|
}
|
|
else
|
|
{
|
|
pNext = tVerts[i + 1];
|
|
}
|
|
|
|
// Check to see if there are only 3 verts left
|
|
// if so this is the last triangle
|
|
if (tVerts.size() == 3)
|
|
{
|
|
// Create a triangle from pCur, pPrev, pNext
|
|
for (int j = 0; j < int(tVerts.size()); j++)
|
|
{
|
|
if (iVerts[j].Position == pCur.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pPrev.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pNext.Position)
|
|
oIndices.push_back(j);
|
|
}
|
|
|
|
tVerts.clear();
|
|
break;
|
|
}
|
|
if (tVerts.size() == 4)
|
|
{
|
|
// Create a triangle from pCur, pPrev, pNext
|
|
for (int j = 0; j < int(iVerts.size()); j++)
|
|
{
|
|
if (iVerts[j].Position == pCur.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pPrev.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pNext.Position)
|
|
oIndices.push_back(j);
|
|
}
|
|
|
|
Vector3 tempVec;
|
|
for (int j = 0; j < int(tVerts.size()); j++)
|
|
{
|
|
if (tVerts[j].Position != pCur.Position
|
|
&& tVerts[j].Position != pPrev.Position
|
|
&& tVerts[j].Position != pNext.Position)
|
|
{
|
|
tempVec = tVerts[j].Position;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Create a triangle from pCur, pPrev, pNext
|
|
for (int j = 0; j < int(iVerts.size()); j++)
|
|
{
|
|
if (iVerts[j].Position == pPrev.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pNext.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == tempVec)
|
|
oIndices.push_back(j);
|
|
}
|
|
|
|
tVerts.clear();
|
|
break;
|
|
}
|
|
|
|
// If Vertex is not an interior vertex
|
|
float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (180 / 3.14159265359);
|
|
if (angle <= 0 && angle >= 180)
|
|
continue;
|
|
|
|
// If any vertices are within this triangle
|
|
bool inTri = false;
|
|
for (int j = 0; j < int(iVerts.size()); j++)
|
|
{
|
|
if (algorithm::inTriangle(iVerts[j].Position, pPrev.Position, pCur.Position, pNext.Position)
|
|
&& iVerts[j].Position != pPrev.Position
|
|
&& iVerts[j].Position != pCur.Position
|
|
&& iVerts[j].Position != pNext.Position)
|
|
{
|
|
inTri = true;
|
|
break;
|
|
}
|
|
}
|
|
if (inTri)
|
|
continue;
|
|
|
|
// Create a triangle from pCur, pPrev, pNext
|
|
for (int j = 0; j < int(iVerts.size()); j++)
|
|
{
|
|
if (iVerts[j].Position == pCur.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pPrev.Position)
|
|
oIndices.push_back(j);
|
|
if (iVerts[j].Position == pNext.Position)
|
|
oIndices.push_back(j);
|
|
}
|
|
|
|
// Delete pCur from the list
|
|
for (int j = 0; j < int(tVerts.size()); j++)
|
|
{
|
|
if (tVerts[j].Position == pCur.Position)
|
|
{
|
|
tVerts.erase(tVerts.begin() + j);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// reset i to the start
|
|
// -1 since loop will add 1 to it
|
|
i = -1;
|
|
}
|
|
|
|
// if no triangles were created
|
|
if (oIndices.size() == 0)
|
|
break;
|
|
|
|
// if no more vertices
|
|
if (tVerts.size() == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Load Materials from .mtl file
|
|
bool LoadMaterials(std::string path)
|
|
{
|
|
// If the file is not a material file return false
|
|
if (path.substr(path.size() - 4, path.size()) != ".mtl")
|
|
return false;
|
|
|
|
std::ifstream file(path);
|
|
|
|
// If the file is not found return false
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
Material tempMaterial;
|
|
|
|
bool listening = false;
|
|
|
|
// Go through each line looking for material variables
|
|
std::string curline;
|
|
while (std::getline(file, curline))
|
|
{
|
|
// new material and material name
|
|
if (algorithm::firstToken(curline) == "newmtl")
|
|
{
|
|
if (!listening)
|
|
{
|
|
listening = true;
|
|
|
|
if (curline.size() > 7)
|
|
{
|
|
tempMaterial.name = algorithm::tail(curline);
|
|
}
|
|
else
|
|
{
|
|
tempMaterial.name = "none";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Generate the material
|
|
|
|
// Push Back loaded Material
|
|
LoadedMaterials.push_back(tempMaterial);
|
|
|
|
// Clear Loaded Material
|
|
tempMaterial = Material();
|
|
|
|
if (curline.size() > 7)
|
|
{
|
|
tempMaterial.name = algorithm::tail(curline);
|
|
}
|
|
else
|
|
{
|
|
tempMaterial.name = "none";
|
|
}
|
|
}
|
|
}
|
|
// Ambient Color
|
|
if (algorithm::firstToken(curline) == "Ka")
|
|
{
|
|
std::vector<std::string> temp;
|
|
algorithm::split(algorithm::tail(curline), temp, " ");
|
|
|
|
if (temp.size() != 3)
|
|
continue;
|
|
|
|
tempMaterial.Ka.X = std::stof(temp[0]);
|
|
tempMaterial.Ka.Y = std::stof(temp[1]);
|
|
tempMaterial.Ka.Z = std::stof(temp[2]);
|
|
}
|
|
// Diffuse Color
|
|
if (algorithm::firstToken(curline) == "Kd")
|
|
{
|
|
std::vector<std::string> temp;
|
|
algorithm::split(algorithm::tail(curline), temp, " ");
|
|
|
|
if (temp.size() != 3)
|
|
continue;
|
|
|
|
tempMaterial.Kd.X = std::stof(temp[0]);
|
|
tempMaterial.Kd.Y = std::stof(temp[1]);
|
|
tempMaterial.Kd.Z = std::stof(temp[2]);
|
|
}
|
|
// Specular Color
|
|
if (algorithm::firstToken(curline) == "Ks")
|
|
{
|
|
std::vector<std::string> temp;
|
|
algorithm::split(algorithm::tail(curline), temp, " ");
|
|
|
|
if (temp.size() != 3)
|
|
continue;
|
|
|
|
tempMaterial.Ks.X = std::stof(temp[0]);
|
|
tempMaterial.Ks.Y = std::stof(temp[1]);
|
|
tempMaterial.Ks.Z = std::stof(temp[2]);
|
|
}
|
|
// Specular Exponent
|
|
if (algorithm::firstToken(curline) == "Ns")
|
|
{
|
|
tempMaterial.Ns = std::stof(algorithm::tail(curline));
|
|
}
|
|
// Optical Density
|
|
if (algorithm::firstToken(curline) == "Ni")
|
|
{
|
|
tempMaterial.Ni = std::stof(algorithm::tail(curline));
|
|
}
|
|
// Dissolve
|
|
if (algorithm::firstToken(curline) == "d")
|
|
{
|
|
tempMaterial.d = std::stof(algorithm::tail(curline));
|
|
}
|
|
// Illumination
|
|
if (algorithm::firstToken(curline) == "illum")
|
|
{
|
|
tempMaterial.illum = std::stoi(algorithm::tail(curline));
|
|
}
|
|
// Ambient Texture Map
|
|
if (algorithm::firstToken(curline) == "map_Ka")
|
|
{
|
|
tempMaterial.map_Ka = algorithm::tail(curline);
|
|
}
|
|
// Diffuse Texture Map
|
|
if (algorithm::firstToken(curline) == "map_Kd")
|
|
{
|
|
tempMaterial.map_Kd = algorithm::tail(curline);
|
|
}
|
|
// Specular Texture Map
|
|
if (algorithm::firstToken(curline) == "map_Ks")
|
|
{
|
|
tempMaterial.map_Ks = algorithm::tail(curline);
|
|
}
|
|
// Specular Hightlight Map
|
|
if (algorithm::firstToken(curline) == "map_Ns")
|
|
{
|
|
tempMaterial.map_Ns = algorithm::tail(curline);
|
|
}
|
|
// Alpha Texture Map
|
|
if (algorithm::firstToken(curline) == "map_d")
|
|
{
|
|
tempMaterial.map_d = algorithm::tail(curline);
|
|
}
|
|
// Bump Map
|
|
if (algorithm::firstToken(curline) == "map_Bump" || algorithm::firstToken(curline) == "map_bump" || algorithm::firstToken(curline) == "bump")
|
|
{
|
|
tempMaterial.map_bump = algorithm::tail(curline);
|
|
}
|
|
}
|
|
|
|
// Deal with last material
|
|
|
|
// Push Back loaded Material
|
|
LoadedMaterials.push_back(tempMaterial);
|
|
|
|
// Test to see if anything was loaded
|
|
// If not return false
|
|
if (LoadedMaterials.empty())
|
|
return false;
|
|
// If so return true
|
|
else
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
|
|
#endif//RAYTRYCPP_OBJ_LOADER_H
|