So I have a small .obj
parser that can parse the vertices and draw it on screen:
void loadObj(char *fname)
{
FILE *fp;
int read;
GLfloat x, y, z;
char ch;
_model = glGenLists(1);
fp = fopen(fname, "r");
if (!fp)
{
printf("can't open file %s\n", fname);
exit(1);
}
glPointSize(2.0);
glNewList(_model, GL_COMPILE);
{
glPushMatrix();
glBegin(GL_POINTS);
while (!(feof(fp)))
{
read = fscanf(fp, "%c %f %f %f", &ch, &x, &y, &z);
if (read == 4 && ch == 'v')
{
glVertex3f(x, y, z);
}
}
glEnd();
}
glPopMatrix();
glEndList();
fclose(fp);
}
void drawModel()
{
glPushMatrix();
glTranslatef(0, 0.00, 0.00);
glColor3f(1.0, 0.23, 0.27);
glScalef(10, 10, 10);
glRotatef(_modelRot, 0, 1, 0);
glCallList(_model);
glPopMatrix();
}
Point is, the output is only vertices, like this:
How can I modify this to at-least show the 3D form between points without adding 3rd party libraries? This is something I am looking for:
Thanks. More code can be provided if required.
Your object parser and rendering calls are not complete.
The first part of the .obj file contains vertex data. Including position, texture coordinate and normal data. The second part contains the information, how the verticies are interconnected.
# List of geometric vertices, with (x,y,z[,w]) coordinates, w is optional and defaults to 1.0.
v 0.123 0.234 0.345 1.0 # first vertex
v ... # second vertex
...
# List of texture coordinates, in (u, v [,w]) coordinates, these will vary between 0 and 1, w is optional and defaults to 0.
vt 0.500 1 [0] # first texture coordinate
vt ... # second
...
# List of vertex normals in (x,y,z) form; normals might not be unit vectors.
vn 0.707 0.000 0.707 # first normal
vn ... # second
...
# Parameter space vertices in ( u [,v] [,w] ) form; free form geometry statement ( see below )
vp 0.310000 3.210000 2.100000
vp ...
...
# Polygonal face element (see below)
f 1 2 3 # face of the first, second and third vertex
f 3/1 4/2 5/3 # face of the third, fourth and fifth vertex with the first second and third texture coordinate
f 6/4/1 3/5/3 7/6/5 # face of sixth, thrid and seventh vertex, fourth, fifth and sixth texture coordinate and first thrid and fifth normal
f 7//1 8//2 9//3 # similar to the line over but without texture coordinates
f ...
...
The listing is taken from here: https://en.wikipedia.org/wiki/Wavefront_.obj_file
OpenGl is not able to map the vertex data together in the way, .obj files allow it to be. So you have to make a data structure with all verticies, one for all texels and one for all normals.
Then you can parse the faces and build your vertex data, by collecting the right position, texel and normals to build a complete face.
After that step, you can use this combination to draw your primitives with GL_TRIANGLES or GL_QUADS.
Here is the loader, that I wrote a while ago for OpenGl 4.x
#pragma once
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <glm/glm.hpp>
#include <GL/glew.h>
#include "System/Log.hpp"
#include "Graphics/Primitives/Object.hpp"
#include "Graphics/Primitives/Material.hpp"
namespace Loader {
template <class ObjectT = Graphics::Primitives::Object>
class ObjectLoader {
private:
const std::string fullPath;
std::vector<Graphics::Primitives::VertexGroup> objects;
std::map<std::string, Graphics::Primitives::Material> materials;
std::string prefixPath;
std::vector<glm::vec3> verticies;
std::vector<glm::vec2> texels;
std::vector<glm::vec3> normals;
std::vector<glm::uvec3> faces;
std::vector<glm::vec3> index_verticies;
std::vector<glm::vec2> index_texels;
std::vector<glm::vec3> index_normals;
Graphics::Primitives::Material material;
std::map<std::string, GLuint> indexDb;
std::string getPrefixPath();
std::vector<std::string> explode(std::string str, char delimiter = ' ');
glm::vec3 stringsToVec(const std::vector<std::string> parts, unsigned int begin);
void loadMaterial(std::string fileName);
Graphics::Primitives::VertexGroup flush();
public:
ObjectLoader(std::string fileName);
ObjectT load();
};
template <class ObjectT>
std::string ObjectLoader<ObjectT>::getPrefixPath() {
unsigned int lastSlash = 0;
for(int i = fullPath.size(); i > 0; i--) {
if(fullPath[i] == '/') {
lastSlash = i;
break;
}
}
std::string prefixPath = fullPath.substr(0, lastSlash);
prefixPath += "/";
return prefixPath;
}
template <class ObjectT>
std::vector<std::string> ObjectLoader<ObjectT>::explode(std::string str, char delimiter) {
std::vector<std::string> result;
std::stringstream data(str);
std::string line;
while(std::getline(data,line,delimiter)) {
result.push_back(line);
}
return result;
}
template <class ObjectT>
glm::vec3 ObjectLoader<ObjectT>::stringsToVec(const std::vector<std::string> parts, unsigned int begin) {
glm::vec3 result;
if(parts.size() > begin + 2) {
result.x = std::atof(parts[begin].c_str());
result.y = std::atof(parts[begin+1].c_str());
result.z = std::atof(parts[begin+2].c_str());
} else
if(parts.size() > begin) {
result.x = std::atof(parts[begin].c_str());
result.y = std::atof(parts[begin].c_str());
result.z = std::atof(parts[begin].c_str());
}
return result;
}
template <class ObjectT>
void ObjectLoader<ObjectT>::loadMaterial(std::string fileName) {
std::ifstream materialFile(fileName);
std::string line;
Graphics::Primitives::Material material;
std::string materialName;
bool initialised = false;
while(std::getline(materialFile, line)) {
//System::Log::msg << " " << line << std::endl;
std::vector<std::string> parts = explode(line);
if(parts.size() > 0) {
if(parts[0] == "newmtl") {
if(initialised) {
materials.insert(std::make_pair(materialName, material));
System::Log::msg << "Loaded material: " << materialName << std::endl;
}
materialName = parts[1];
initialised = true;
material = Graphics::Primitives::Material();
} else
if(parts[0] == "Ns") {
if(parts.size() > 1) {
material.specularExponent = std::atof(parts[1].c_str());
}
} else
if(parts[0] == "Ka") {
material.ambientReflectance = stringsToVec(parts,1);
} else
if(parts[0] == "Kd") {
material.diffuseReflectance = stringsToVec(parts,1);
} else
if(parts[0] == "Ks") {
material.specularReflectance = stringsToVec(parts,1);
} else
//if(parts[0] == "Ke") {
//No idea what this value means, maybe transmission filter aka Tf?
//} else
if(parts[0] == "Ni") {
//Optical density ignored for now
} else
if(parts[0] == "d") {
material.dissolve = std::atof(parts[1].c_str());
} else
if(parts[0] == "map_Ka") {
material.textureStack.push_back(Graphics::Ogl::loadTexture(parts[1]));
} else
if(parts[0] == "map_Kd") {
material.textureStack.push_back(Graphics::Ogl::loadTexture(parts[1]));
} else
if(parts[0] == "map_Ks") {
material.textureStack.push_back(Graphics::Ogl::loadTexture(parts[1]));
}
}
}
materials.insert(std::make_pair(materialName, material));
System::Log::msg << "Loaded material: " << materialName << std::endl;
}
template <class ObjectT>
Graphics::Primitives::VertexGroup ObjectLoader<ObjectT>::flush()
{
Graphics::Ogl::VertexArrayObject vao = Graphics::Ogl::makeVertexArrayObject(
std::vector<Graphics::Ogl::ArrayBufferObject>({
Graphics::Ogl::makeArrayBufferObject(index_verticies),
Graphics::Ogl::makeArrayBufferObject(index_normals),
Graphics::Ogl::makeArrayBufferObject(index_texels),
Graphics::Ogl::makeIndexBufferObject(faces)
})
);
System::Log::msg << "Flushing buffers: vertecies(" << index_verticies.size()
<< "), texels(" << index_texels.size()
<< "), normals(" << index_normals.size()
<< "), faces(" << faces.size() << ")" << std::endl;
Graphics::Primitives::VertexGroup obj(vao, material);
glCheckError();
faces.clear();
index_verticies.clear();
index_texels.clear();
index_normals.clear();
indexDb.clear();
return obj;
}
template <class ObjectT>
ObjectLoader<ObjectT>::ObjectLoader(std::string fileName) :
fullPath(fileName),
prefixPath(getPrefixPath())
{}
template <class ObjectT>
ObjectT ObjectLoader<ObjectT>::load() {
System::Log::msg << "Loading Object from file: " << fullPath << std::endl;
std::ifstream objectFile(fullPath);
std::string line;
while(std::getline(objectFile,line)) {
//System::Log::msg << line << std::endl;
if(line[0] == '#' || line[0] == 'o' || line[0] == 'g') {
continue;
}
std::vector<std::string> substrs = explode(line);
if(substrs.size() == 0) {
continue;
}
if( substrs[0] == "v") {
//Add new Vertex to index buffer
glm::vec3 vertex;
if(substrs.size() > 3) {
vertex.x = std::stof(substrs[1]);
vertex.y = std::stof(substrs[2]);
vertex.z = std::stof(substrs[3]);
verticies.push_back(vertex);
} else {
System::Log::err << "Vertex with less than 3 coordinates." << std::endl;
}
} else if(substrs[0] == "vt") {
//Add new Texel to index buffer
glm::vec2 texel;
if(substrs.size() > 2) {
texel.x= std::stof(substrs[1]);
texel.y = std::stof(substrs[2]);
texels.push_back(texel);
} else {
System::Log::err << "Texel with less than 2 coordinates." << std::endl;
}
} else if(substrs[0] == "vn") {
//Add new Normal to index buffer
glm::vec3 normal;
if(substrs.size() > 3) {
normal.x = std::stof(substrs[1]);
normal.y = std::stof(substrs[2]);
normal.z = std::stof(substrs[3]);
normals.push_back(normal);
} else {
System::Log::err << "Normal with less than 3 coordinates." << std::endl;
}
} else if(substrs[0] == "f") {
if(texels.size() == 0) texels.push_back(glm::vec2(0,0));
if(normals.size() == 0) normals.push_back(glm::vec3(0,0,0));
//Lookup in index db;
glm::uvec3 face;
for(unsigned int faceIndex = 1; faceIndex < 4; faceIndex++) {
std::string vtn = substrs[faceIndex];
try {
//Try to find index combination in db
face[faceIndex-1] = indexDb.at(substrs[faceIndex]);
//Index found
} catch (std::exception e) {
//Index not found, now to the hard part
//Create new index in indexDb
GLuint newFace = indexDb.size();
face[faceIndex-1] = newFace;
indexDb.insert(std::make_pair(substrs[faceIndex], newFace));
//Create new vtn triple in buffers
std::vector<std::string> components = explode(substrs[faceIndex],'/');
if(components[1].size() == 0) components[1] = "0";
if(components[2].size() == 0) components[2] = "0";
auto clipValue = [](std::string& number) -> GLuint {
GLuint result = std::atoi(number.c_str());
if(result > 0) result--;
return result;
};
const GLuint vi = clipValue(components[0]);
const GLuint ti = clipValue(components[1]);
const GLuint ni = clipValue(components[2]);
if(verticies.size() > vi) {
index_verticies.push_back(verticies[vi]);
} else {
System::Log::err << "Error: Invalid vertex index. (Index="<< vi <<", LoadedVerticies=" << verticies.size() << ")" << std::endl;
}
if(texels.size() > ti) {
index_texels.push_back(texels[ti]);
}
if(normals.size() > ni) {
index_normals.push_back(normals[ni]);
}
}
}
//Add new Face to Mesh
faces.push_back(face);
//System::Log::msg << "Face: " << face.x << " " << face.y << " " << face.z << std::endl;
} else if(substrs[0] == "usemtl") {
//Flush last mesh
if(faces.size() > 0) {
objects.push_back(flush());
}
//Use new material
material = materials.at(substrs[1]);
System::Log::msg << "Use Material:" << substrs[1] << std::endl;
} else if(substrs[0] == "s") {
//Smoothing
//TODO:
} else if(substrs[0] == "mtllib") {
//Load new materials
loadMaterial(prefixPath+substrs[1]);
} else {
System::Log::err << "Unknown prefix in file" << std::endl;
}
}
if(faces.size() > 0) {
objects.push_back(flush());
}
System::Log::msg << "Done loading object." << std::endl;
return ObjectT(objects);
}
} // End of namespace Loader
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With