Related
I'm currently trying to make a Wavefront (.obj) file loader for an OpenGL project. The method I'm currently using goes line-by-line and separates the vertex positions, texture positions and normal positions in vectors (std::vectors) and I'm storing their indices (vertex, texture and normal indices) in three separate vectors (from the 'f' lines of the file, for each face).
I'm having trouble sorting the vector full of texture coordinates based on the texture indices. I'm able to render the vertices in the correct positions because my 'loader' class calls for the indices, but I can't figure out how to sort the texture coordinates in any way, so the textures look offset on some triangles as a result.
Image of cube with offset textures:
Image of texture (.png), how it should be on each face:
EDIT: Here is a link to both the .obj file and .mtl file.
Google Drive.
Here is my OBJLoader.cpp file:
rawObj.open(filePath); // Open file
while (!rawObj.eof()) {
getline(rawObj, line); // Read line
// Read values from each line
// starting with a 'v' for
// the vertex positions with
// a custom function (gets the word in a line
// at position i)
if (strWord(line, 1) == "v") {
for (int i = 2; i <= 4; i++) {
std::string temp;
temp = strWord(line, i);
vertexStrings.push_back(temp);
}
// Same for texture positions
} else if (strWord(line, 1) == "vt") {
for (int i = 2; i <= 3; i++) {
std::string temp;
temp = strWord(line, i);
textureStrings.push_back(temp);
}
// Same for normal positions
} else if (strWord(line, 1) == "vn") { // normals
for (int i = 2; i <= 4; i++) {
std::string temp;
temp = strWord(line, i);
normalStrings.push_back(temp);
}
// Separate each of the three vertices and then separate
// each vertex into its vertex index, texture index and
// normal index
} else if (strWord(line, 1) == "f") { // faces (indices)
std::string temp;
for (int i = 2; i <= 4; i++) {
temp = strWord(line, i);
chunks.push_back(temp);
k = std::stoi(strFaces(temp, 1));
vertexIndices.push_back(k-1);
l = std::stoi(strFaces(temp, 2));
textureIndices.push_back(l-1);
m = std::stoi(strFaces(temp, 3));
normalIndices.push_back(m-1);
}
}
}
// Convert from string to float
for (auto &s : vertexStrings) {
std::stringstream parser(s);
float x = 0;
parser >> x;
vertices.push_back(x);
}
for (auto &s : textureStrings) {
std::stringstream parser(s);
float x = 0;
parser >> x;
texCoords.push_back(x);
}
// Y coords are from top left instead of bottom left
for (int i = 0; i < texCoords.size(); i++) {
if (i % 2 != 0)
texCoords[i] = 1 - texCoords[i];
}
// Passes vertex positions, vertex indices and texture coordinates
// to loader class
return loader.loadToVao(vertices, vertexIndices, texCoords);
}
I've tried inserting the values (vector.insert) from texCoords[textureIndices[i]] in a loop but that didn't work and made the output worse. I tried a simple:
tempVec[i] = texCoords[textureIndices[i]]
in a for loop but that didn't work either.
I've gone through the whole project and I determined that the sorting is the cause of the issue, because when I plug in hard-coded values for the cube it works perfectly and the textures aren't offset at all. (The OpenGL commands / image loader are working as they should.)
Ultimately, is there another way to sort the texCoords based on the textureIndices?
If there are different indexes for vertex coordinates and texture coordinates, then the vertex positions must be "duplicated".
The vertex coordinate and its attributes (like texture coordinate) form a tuple. Each vertex coordinate must have its own texture coordinates and attributes. You can think of a 3D vertex coordinate and a 2D texture coordinate as a single 5D coordinate.
See Rendering meshes with multiple indices.
Let's assume that you have a .obj file like this:
v -1 -1 -1
v 1 -1 -1
v -1 1 -1
v 1 1 -1
v -1 -1 1
v 1 -1 1
v -1 1 1
v 1 1 1
vt 0 0
vt 0 1
vt 1 0
vt 1 1
vn -1 0 0
vn 0 -1 0
vn 0 0 -1
vn 1 0 0
vn 0 1 0
vn 0 0 1
f 3/1/1 1/2/1 5/4/1 7/3/1
f 1/1/2 2/2/2 3/4/2 6/3/2
f 3/1/3 4/2/3 2/4/3 1/3/3
f 2/1/4 4/2/4 8/4/4 6/3/4
f 4/1/5 3/2/5 7/4/5 8/3/5
f 5/1/6 6/2/6 8/4/6 7/3/6
From this you have to find all the combinations of vertex coordinate, texture texture coordinate and normal vector indices, which are used in the face specification:
0 : 3/1/1
1 : 1/2/1
2 : 5/4/1
3 : 7/3/1
4 : 1/1/2
5 : 2/2/2
6 : 3/4/2
7 : 6/3/2
8 : ...
Then you have to create a vertex coordinate, texture coordinate and normal vector array corresponding to the array of index combinations.
The vertex coordinates and its attributes can either be combined in one array to data sets, or to three arrays with equal number of attributes:
index vx vy vz u v nx ny nz
0 : -1 1 -1 0 0 -1 0 0
1 : -1 -1 -1 0 1 -1 0 0
2 : -1 -1 1 1 1 -1 0 0
3 : -1 1 1 1 0 -1 0 0
4 : -1 -1 -1 0 0 0 -1 0
5 : 1 -1 -1 0 1 0 -1 0
6 : -1 1 -1 1 1 0 -1 0
7 : 1 -1 1 1 0 0 -1 0
8 : ...
See the very simple c++ function, which can read an .obj file, like that you linked to.
The function reads a file and writes the data to an element vector and an attribute vector.
Note, the function can be optimized and does not care about performance.
For a small file (like cube3.obj which you liked to), that doesn't matter, but for a large file,
especially the linear search in the index table, will have to be improved.
I just tried to give you an idea how to read an .obj file and how to create an element and attribute vector, which can be directly used to draw an mesh with the use of OpenGL.
#include <vector>
#include <array>
#include <string>
#include <fstream>
#include <strstream>
#include <algorithm>
bool load_obj(
const std::string filename,
std::vector<unsigned int> &elements,
std::vector<float> &attributes )
{
std::ifstream obj_stream( filename, std::ios::in );
if( !obj_stream )
return false;
// parse the file, line by line
static const std::string white_space = " \t\n\r";
std::string token, indices, index;
float value;
std::vector<float> v, vt, vn;
std::vector<std::array<unsigned int, 3>> f;
for( std::string line; std::getline( obj_stream, line ); )
{
// find first non whispce characterr in line
size_t start = line.find_first_not_of( white_space );
if ( start == std::string::npos )
continue;
// read the first token
std::istringstream line_stream( line.substr(start) );
line_stream.exceptions( 0 );
line_stream >> token;
// ignore comment lines
if ( token[0] == '#' )
continue;
// read the line
if ( token == "v" ) // read vertex coordinate
{
while ( line_stream >> value )
v.push_back( value );
}
else if ( token == "vt" ) // read normal_vectors
{
while ( line_stream >> value )
vt.push_back( value );
}
else if ( token == "vn" ) // read normal_vectors
{
while ( line_stream >> value )
vn.push_back( value );
}
else if ( token == "f" )
{
// read faces
while( line_stream >> indices )
{
std::array<unsigned int, 3> f3{ 0, 0, 0 };
// parse indices
for ( int j=0; j<3; ++ j )
{
auto slash = indices.find( "/" );
f3[j] = std::stoi(indices.substr(0, slash), nullptr, 10);
if ( slash == std::string::npos )
break;
indices.erase(0, slash + 1);
}
// add index
auto it = std::find( f.begin(), f.end(), f3 );
elements.push_back( (unsigned int)(it - f.begin()) );
if ( it == f.end() )
f.push_back( f3 );
}
}
}
// create array of attributes from the face indices
for ( auto f3 : f )
{
if ( f3[0] > 0 )
{
auto iv = (f3[0] - 1) * 3;
attributes.insert( attributes.end(), v.begin() + iv, v.begin() + iv + 3 );
}
if ( f3[1] > 0 )
{
auto ivt = (f3[1] - 1) * 2;
attributes.insert( attributes.end(), vt.begin() + ivt, vt.begin() + ivt + 2 );
}
if ( f3[2] > 0 )
{
auto ivn = (f3[2] - 1) * 3;
attributes.insert( attributes.end(), vn.begin() + ivn, vn.begin() + ivn + 3 );
}
}
return true;
}
I wanted to implement this (adding textures for obj file) into my engine for a long time and your Question got me the mood to actually do it :).
The image you provided as texture looks more like a preview than a texture. Also the texture coordinates does not correspond to it as you can see in preview:
If you look at the texture coordinates:
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
there are just 2 numbers:
0.736102
0.263898
Which makes sense for axis aligned quad or square sub-image in the texture which isnot present in your texture. Also the number of texture points makes no sense 20 it should be just 4. Hence the confusions you got.
Anyway Rabbid76 is right you need to duplicate points ... Its relatively easy so:
extract all positions, colors, texture points and normals
from your obj file into separate tables. So parse lines starting with v,vt,vn and create 4 tables from it. Yes 4 as color is sometimes encoded in v as v x y z r g b as output from some 3D scanners.
So you should have something like this:
double ppos[]= // v
{
-1.000000, 1.000000, 1.000000,
-1.000000,-1.000000,-1.000000,
-1.000000,-1.000000, 1.000000,
-1.000000, 1.000000,-1.000000,
1.000000,-1.000000,-1.000000,
1.000000, 1.000000,-1.000000,
1.000000,-1.000000, 1.000000,
1.000000, 1.000000, 1.000000,
};
double pcol[]= // v
{
};
double ptxr[]= // vt
{
0.736102,0.263898,
0.263898,0.736102,
0.263898,0.263898,
0.736102,0.263898,
0.263898,0.736102,
0.263898,0.263898,
0.736102,0.263898,
0.263898,0.736102,
0.263898,0.263898,
0.736102,0.263898,
0.263898,0.736102,
0.263898,0.263898,
0.736102,0.263898,
0.263898,0.736102,
0.263898,0.263898,
0.736102,0.736102,
0.736102,0.736102,
0.736102,0.736102,
0.736102,0.736102,
0.736102,0.736102,
};
double pnor[]= // vn
{
-0.5774, 0.5774, 0.5774,
-0.5774,-0.5774,-0.5774,
-0.5774,-0.5774, 0.5774,
-0.5774, 0.5774,-0.5774,
0.5774,-0.5774,-0.5774,
0.5774, 0.5774,-0.5774,
0.5774,-0.5774, 0.5774,
0.5774, 0.5774, 0.5774,
};
process faces f
now you should handle the above tables as temp data and create real data for your mesh from scratch into new structure (or load it directly to VBOs). So what you need is to reindex all the f data to unique combinations of all the indexes present. To do that you need to keep track what you already got. For that I amusing this structure:
class vertex
{
public:
int pos,txr,nor;
vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
};
so create empty list of vertex now process first f line and extract indexes
f 1/1/1 2/2/2 3/3/3
so for each point (process just one at a time) in the face extract its ppos,ptxr,pnor index. Now check if it is already present in your final mesh data. If yes use its index instead. If not add new point to all tables your mesh have (pos,col,txr,nor) and use index of newly added point.
When all points of a face was processed add the face with the reindexed indexes into your final mesh faces and process next f line.
Just for sure here is my Wavefront OBJ loader C++ class I am using in my engine (but it depends on the engine itself so you can not use it directly it is just to see the structure of code and how to encode this... as starting with this from scratch might be difficult).
//---------------------------------------------------------------------------
//--- Wavefront obj librrary ver: 2.11 --------------------------------------
//---------------------------------------------------------------------------
#ifndef _model_obj_h
#define _model_obj_h
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class model_obj
{
public:
class vertex
{
public:
int pos,txr,nor;
vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
};
OpenGL_VAO obj;
model_obj();
~model_obj();
void reset();
void load(AnsiString name);
int save(OpenGL_VAOs &vaos);
};
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
model_obj::model_obj()
{
reset();
}
//---------------------------------------------------------------------------
model_obj::~model_obj()
{
reset();
}
//---------------------------------------------------------------------------
void model_obj::reset()
{
obj.reset();
}
//---------------------------------------------------------------------------
void model_obj::load(AnsiString name)
{
int adr,siz,hnd;
BYTE *dat;
reset();
siz=0;
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); return; }
FileRead(hnd,dat,siz);
FileClose(hnd);
AnsiString s,s0,t;
int a,i,j;
double alpha=1.0;
List<double> f;
List<int> pos,txr,nor;
List<double> ppos,pcol,pnor,ptxr; // OBJ parsed data
vertex v;
List<vertex> pv;
f.allocate(6);
ppos.num=0;
pcol.num=0;
pnor.num=0;
ptxr.num=0;
obj.reset();
// purpose, location, type,datatype,datacomponents,pack_acc);
obj.addVBO(_OpenGL_VBO_purpose_pos ,vbo_loc_pos , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_col ,vbo_loc_col , GL_ARRAY_BUFFER,GL_FLOAT, 4, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_txr0,vbo_loc_txr0, GL_ARRAY_BUFFER,GL_FLOAT, 2, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_nor ,vbo_loc_nor , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_fac , -1,GL_ELEMENT_ARRAY_BUFFER, GL_INT, 3, 0.0);
obj.draw_mode=GL_TRIANGLES;
obj.rep.reset();
obj.filename=name;
_progress_init(siz); int progress_cnt=0;
for (adr=0;adr<siz;)
{
progress_cnt++; if (progress_cnt>=1024) { progress_cnt=0; _progress(adr); }
s0=txt_load_lin(dat,siz,adr,true);
a=1; s=str_load_str(s0,a,true);
// clear temp vector in case of bug in obj file
f.num=0; for (i=0;i<6;i++) f.dat[i]=0.0;
if (s=="v")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
if (f.num>=3)
{
ppos.add(f[0]);
ppos.add(f[1]);
ppos.add(f[2]);
}
if (f.num==6)
{
pcol.add(f[3]);
pcol.add(f[4]);
pcol.add(f[5]);
}
}
else if (s=="vn")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
pnor.add(f[0]);
pnor.add(f[1]);
pnor.add(f[2]);
}
else if (s=="vt")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
ptxr.add(f[0]);
ptxr.add(f[1]);
}
else if (s=="f")
{
pos.num=0;
txr.num=0;
nor.num=0;
for (;;)
{
s=str_load_str(s0,a,true); if (s=="") break;
for (t="",i=1;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) pos.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) txr.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) nor.add(str2int(t)-1);
}
// reindex and or duplicate vertexes if needed
for (i=0;i<pos.num;i++)
{
// wanted vertex
v.pos=pos[i];
if (txr.num>0) v.txr=txr[i]; else v.txr=-1;
if (nor.num>0) v.nor=nor[i]; else v.nor=-1;
// is present in VBO?
for (j=0;j<pv.num;j++)
if (v==pv[j])
{ pos[i]=j; j=-1; break; }
// if not add it
if (j>=0)
{
j=v.pos; j=j+j+j; if (pcol.num>0) obj.addpntcol(ppos[j+0],ppos[j+1],ppos[j+2],pcol[j+0],pcol[j+1],pcol[j+2],alpha);
else obj.addpnt (ppos[j+0],ppos[j+1],ppos[j+2]);
j=v.nor; j=j+j+j; if (v.nor>=0) obj.addnor (pnor[j+0],pnor[j+1],pnor[j+2]);
j=v.txr; j=j+j; if (v.txr>=0) obj.addtxr (ptxr[j+0],ptxr[j+1]);
pos[i]=pv.num; pv.add(v);
}
}
for (i=2;i<pos.num;i++) obj.addface(pos[0],pos[i-1],pos[i]);
}
}
_progress_done();
delete[] dat;
}
//---------------------------------------------------------------------------
int model_obj::save(OpenGL_VAOs &vaos)
{
int vaoix0=-1;
OpenGL_VBO *vn=obj.getVBO(_OpenGL_VBO_purpose_nor );
if (vn->data.num==0) obj.nor_compute();
vaos.vao=obj;
vaoix0=vaos.add(obj);
return vaoix0;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
It does not use the *.mtl file yet (I hardcoded the texture for the preview).
PS. if I use this as texture:
The result looks like this:
I use a lot of mine own stuff here so some explanations:
str_load_str(s,i,true) returns string representing first valid word from index i in string s. The true means just that i is updated with new position in s.
str_load_lin(s,i,true) returns string representing line (till CR or LF or CRLF or LFCR) from index i in string s. The true means just that i is updated with new position after that line.
txt_load_... is the same but instead of reading from string it reads form BYTE* or CHAR* if you want.
Beware AnsiString is indexed form 1 and BYTE*,CHAR* from 0.
I also use mine dynamic list template so:
List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list
xxx[7] access array element (safe)
xxx.dat[7] access array element (unsafe but fast direct access)
xxx.num is the actual used size of the array
xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items
Here the updated faster reindex code with textures from mtl file (otherstuff is ignored and only single object/texture is supported for now):
//---------------------------------------------------------------------------
//--- Wavefront obj librrary ver: 2.11 --------------------------------------
//---------------------------------------------------------------------------
#ifndef _model_obj_h
#define _model_obj_h
//---------------------------------------------------------------------------
class model_obj
{
public:
class vertex
{
public:
int pos,txr,nor;
vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
int operator < (vertex &a)
{
if (pos>a.pos) return 0;
if (pos<a.pos) return 1;
if (txr>a.txr) return 0;
if (txr<a.txr) return 1;
if (nor<a.nor) return 1;
return 0;
}
void ld(int p,int t,int n) { pos=p; txr=t; nor=n; }
};
class vertexes
{
public:
List<vertex> pv; // vertexes in order
List<int> ix; // inex sort ASC for faster access
int m; // power of 2 >= ix.num
vertexes(){}; vertexes(vertexes& a){ *this=a; }; ~vertexes(){}; vertexes* operator = (const vertexes *a) { *this=*a; return this; }; /*vertexes* operator = (const vertexes &a) { ...copy... return this; };*/
void reset() { m=0; pv.num=0; ix.num=0; }
bool get(int &idx,vertex &v) // find idx so pv[idx]<=v and return if new vertex was added
{
int i,j;
// handle first point
if (ix.num<=0)
{
m=1;
idx=0;
pv.add(v);
ix.add(0);
return true;
}
// bin search closest idx
for (j=0,i=m;i;i>>=1)
{
j|=i;
if (j>=ix.num) { j^=i; continue; }
if (v<pv.dat[ix.dat[j]]) j^=i;
}
// stop if match found
idx=ix.dat[j];
if (v==pv.dat[idx]) return false;
// add new index,vertex if not
idx=pv.num; pv.add(v); j++;
if (j>=ix.num) ix.add(idx);
else ix.ins(j,idx);
if (ix.num>=m+m) m<<=1;
return true;
}
};
struct material
{
AnsiString nam,txr;
material(){}; material(material& a){ *this=a; }; ~material(){}; material* operator = (const material *a) { *this=*a; return this; }; /*material* operator = (const material &a) { ...copy... return this; };*/
};
List<material> mat;
OpenGL_VAO obj;
model_obj();
~model_obj();
void reset();
void load(AnsiString name);
int save(OpenGL_VAOs &vaos);
};
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
model_obj::model_obj()
{
reset();
}
//---------------------------------------------------------------------------
model_obj::~model_obj()
{
reset();
}
//---------------------------------------------------------------------------
void model_obj::reset()
{
obj.reset();
mat.reset();
}
//---------------------------------------------------------------------------
void model_obj::load(AnsiString name)
{
AnsiString path=ExtractFilePath(name);
int adr,siz,hnd;
BYTE *dat;
reset();
siz=0;
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); return; }
FileRead(hnd,dat,siz);
FileClose(hnd);
AnsiString s,s0,t;
int a,i,j;
double alpha=1.0;
List<double> f;
List<int> pos,txr,nor;
List<double> ppos,pcol,pnor,ptxr; // OBJ parsed data
vertex v;
vertexes pver;
material m0,*m=NULL;
f.allocate(6);
pver.reset();
ppos.num=0;
pcol.num=0;
pnor.num=0;
ptxr.num=0;
obj.reset();
// purpose, location, type,datatype,datacomponents,pack_acc);
obj.addVBO(_OpenGL_VBO_purpose_pos ,vbo_loc_pos , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_col ,vbo_loc_col , GL_ARRAY_BUFFER,GL_FLOAT, 4, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_txr0,vbo_loc_txr0, GL_ARRAY_BUFFER,GL_FLOAT, 2, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_nor ,vbo_loc_nor , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_fac , -1,GL_ELEMENT_ARRAY_BUFFER, GL_INT, 3, 0.0);
obj.draw_mode=GL_TRIANGLES;
obj.rep.reset();
obj.filename=name;
_progress_init(siz); int progress_cnt=0;
for (adr=0;adr<siz;)
{
progress_cnt++; if (progress_cnt>=1024) { progress_cnt=0; _progress(adr); }
s0=txt_load_lin(dat,siz,adr,true);
a=1; s=str_load_str(s0,a,true);
// clear temp vector in case of bug in obj file
f.num=0; for (i=0;i<6;i++) f.dat[i]=0.0;
if (s=="v")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
if (f.num>=3)
{
ppos.add(f[0]);
ppos.add(f[1]);
ppos.add(f[2]);
}
if (f.num==6)
{
pcol.add(f[3]);
pcol.add(f[4]);
pcol.add(f[5]);
}
}
else if (s=="vn")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
pnor.add(f[0]);
pnor.add(f[1]);
pnor.add(f[2]);
}
else if (s=="vt")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
ptxr.add(f[0]);
ptxr.add(f[1]);
}
else if (s=="f")
{
pos.num=0;
txr.num=0;
nor.num=0;
for (;;)
{
s=str_load_str(s0,a,true); if (s=="") break;
for (t="",i=1;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) pos.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) txr.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) nor.add(str2int(t)-1);
}
// reindex and or duplicate vertexes if needed
for (i=0;i<pos.num;i++)
{
// wanted vertex
v.pos=pos[i];
if (txr.num>0) v.txr=txr[i]; else v.txr=-1;
if (nor.num>0) v.nor=nor[i]; else v.nor=-1;
if (pver.get(pos[i],v)) // is present in VBO? if not add it
{
j=v.pos; j=j+j+j; if (pcol.num>0) obj.addpntcol(ppos[j+0],ppos[j+1],ppos[j+2],pcol[j+0],pcol[j+1],pcol[j+2],alpha);
else obj.addpnt (ppos[j+0],ppos[j+1],ppos[j+2]);
j=v.nor; j=j+j+j; if (v.nor>=0) obj.addnor (pnor[j+0],pnor[j+1],pnor[j+2]);
j=v.txr; j=j+j; if (v.txr>=0) obj.addtxr (ptxr[j+0],ptxr[j+1]);
}
}
for (i=2;i<pos.num;i++) obj.addface(pos[0],pos[i-1],pos[i]);
}
else if (s=="mtllib")
{
AnsiString s1;
int adr,siz,hnd;
BYTE *dat;
// extract mtl filename
s=str_load_str(s0,a,true);
s+=str_load_lin(s0,a,true);
// load it to memory
siz=0;
hnd=FileOpen(path+s,fmOpenRead);
if (hnd<0) continue;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); continue; }
FileRead(hnd,dat,siz);
FileClose(hnd);
// extract textures and stuff
m=&m0;
for (adr=0;adr<siz;)
{
s1=txt_load_lin(dat,siz,adr,true);
a=1; s=str_load_str(s1,a,true);
if (s=="newmtl")
{
s=str_load_str(s1,a,true);
s+=str_load_lin(s1,a,true);
mat.add();
m=&mat[mat.num-1];
m->nam=s;
m->txr="";
}
else if (s=="map_Kd")
{
s=str_load_str(s1,a,true);
s+=str_load_lin(s1,a,true);
m->txr=s;
}
}
delete[] dat;
m=NULL;
}
else if (s=="usemtl")
{
// extract material name
s=str_load_str(s0,a,true);
s+=str_load_lin(s0,a,true);
// find it in table
for (m=mat.dat,i=0;i<mat.num;i++,m++)
if (m->nam==s) { i=-1; break; }
if (i>=0) m=NULL;
}
}
// textures
for (i=0;i<mat.num;i++)
if (mat[i].txr!="")
{
OpenGL_VAO::_TXR txr;
txr.ix=-1;
txr.unit=txr_unit_map;
txr.filename=mat[i].txr;
txr.txrtype=GL_TEXTURE_2D;
txr.repeat=GL_REPEAT;
obj.txr.add(txr);
}
_progress_done();
delete[] dat;
}
//---------------------------------------------------------------------------
int model_obj::save(OpenGL_VAOs &vaos)
{
int vaoix0=-1,i;
OpenGL_VBO *vn=obj.getVBO(_OpenGL_VBO_purpose_nor );
if (vn) if (vn->data.num==0) obj.nor_compute();
vaos.vao=obj;
vaoix0=vaos.add(obj);
return vaoix0;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
Appart the added materials (just texture and material name for now) I changed the re-indexing so the vertextes are index sorted and binary search is used to obtain vertex index on demand. With this 100K faces Standford dragon (3.4MByte) is loaded in 3.7sec:
I have an image2DArray in my compute shaders with 7 slices.
I can write in it with the function imageStore without problem and also display these textures.
My problem comes with the initialization, I try to initialize my textures but I can't. Indeed, I make a loop for the initialization :
for(int i=0; i<N; i++){
imageStore( outputTexture , ivec3(texel, i), vec4(0));
}
When N = 7, nothing is displayed but when N < 7 everything works well and my textures initialized.
Is someone can explain me why I can't initialize correctly my image2DArray ?
Edit :
What I test to see that : try to write in all slices of the texture and display it. It works fine but data from the previous frame stay if I don't initialize the texture. So, I initialize all pixels of the slices to 0 but nothing display anymore if N=7.
Some code :
#version 430 compatibility
layout(rgba8) coherent uniform image2DArray outputTexture;
...
void main(){
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 outSize = imageSize( outputTexture ).xy;
if( texel.x >= outSize.x || texel.y >= outSize.y )
return;
initializeMeshSet( meshSet );
vec4 pWorld = texelFetch(gBuffer[0],texel,0);
pWorld /= pWorld.w;
vec4 nWorld = texelFetch(gBuffer[1],texel,0);
nWorld /= nWorld.w;
if( length(nWorld.xyz) < 0.1 ){
for(int i=0; i<4; i++){
imageStore( outputTexture , ivec3(texel, i), vec4(0));
}
return;
}
if(nbFrame == 0){
float value = treatment(texel, pWorld, nWorld.xyz, outSize.x);
imageStore( outputTexture, ivec3(texel, 0), vec4(vec3(value),1.0));
imageStore( outputTexture, ivec3(texel, 1), vec4(0.0,0.0,0.0, 1.0));
}
else if(nbFrame == 1){
float value = treatment2(texel, pWorld, nWorld.xyz, outSize.x);
vec3 previousValue = imageLoad(outputTexture, ivec3(texel, 1)).xyz * (nbFrame - 1);
value += previousValue;
value /= nbFrame;
imageStore( outputTexture, ivec3(texel, 1), vec4(vec3(value), 1.0));
}
}
I'm trying to draw a rainbow-coloured plot legend in openGL. Here is what I've got so far:
glBegin(GL_QUADS);
for (int i = 0; i != legendElements; ++i)
{
GLfloat const cellColorIntensity = (GLfloat) i / (GLfloat) legendElements;
OpenGL::pSetHSV(cellColorIntensity*360.0f, 1.0f, 1.0f);
// draw the ith legend element
GLdouble const xLeft = xBeginRight - legendWidth;
GLdouble const xRight = xBeginRight;
GLdouble const yBottom = (GLdouble)i * legendHeight /
(GLdouble)legendElements + legendHeight;
GLdouble const yTop = yBottom + legendHeight;
glVertex2d(xLeft, yTop); // top-left
glVertex2d(xRight, yTop); // top-right
glVertex2d(xRight, yBottom); // bottom-right
glVertex2d(xLeft, yBottom); // bottom-left
}
glEnd();
legendElements is the number of discrete squares that make up the "rainbow". xLeft,xRight,yBottom and yTop are the vertices that make up each of the squared.
where the function OpenGL::pSetHSV looks like this:
void pSetHSV(float h, float s, float v)
{
// H [0, 360] S and V [0.0, 1.0].
int i = (int)floor(h / 60.0f) % 6;
float f = h / 60.0f - floor(h / 60.0f);
float p = v * (float)(1 - s);
float q = v * (float)(1 - s * f);
float t = v * (float)(1 - (1 - f) * s);
switch (i)
{
case 0: glColor3f(v, t, p);
break;
case 1: glColor3f(q, v, p);
break;
case 2: glColor3f(p, v, t);
break;
case 3: glColor3f(p, q, v);
break;
case 4: glColor3f(t, p, v);
break;
case 5: glColor3f(v, p, q);
}
}
I got that function from http://forum.openframeworks.cc/t/hsv-color-setting/770
However, when I draw this it looks like this:
What I would like is a spectrum of Red,Green,Blue,Indigo,Violet (so I want to iterate linearly through the Hue. However, this doesn't really seem to be what's happening.
I don't really understand how the RGB/HSV conversion in pSetHSV() works so it's hard for me to identify the problem..
EDIT: Here is the fixed version, as inspired by Jongware (the rectangles were being drawn incorrectly):
// draw legend elements
glBegin(GL_QUADS);
for (int i = 0; i != legendElements; ++i)
{
GLfloat const cellColorIntensity = (GLfloat) i / (GLfloat) legendElements;
OpenGL::pSetHSV(cellColorIntensity * 360.0f, 1.0f, 1.0f);
// draw the ith legend element
GLdouble const xLeft = xBeginRight - legendWidth;
GLdouble const xRight = xBeginRight;
GLdouble const yBottom = (GLdouble)i * legendHeight /
(GLdouble)legendElements + legendHeight + yBeginBottom;
GLdouble const yTop = yBottom + legendHeight / legendElements;
glVertex2d(xLeft, yTop); // top-left
glVertex2d(xRight, yTop); // top-right
glVertex2d(xRight, yBottom); // bottom-right
glVertex2d(xLeft, yBottom); // bottom-left
}
glEnd();
I generate spectral colors like this:
void spectral_color(double &r,double &g,double &b,double l) // RGB <- lambda l = < 380,780 > [nm]
{
if (l<380.0) r= 0.00;
else if (l<400.0) r=0.05-0.05*sin(M_PI*(l-366.0)/ 33.0);
else if (l<435.0) r= 0.31*sin(M_PI*(l-395.0)/ 81.0);
else if (l<460.0) r= 0.31*sin(M_PI*(l-412.0)/ 48.0);
else if (l<540.0) r= 0.00;
else if (l<590.0) r= 0.99*sin(M_PI*(l-540.0)/104.0);
else if (l<670.0) r= 1.00*sin(M_PI*(l-507.0)/182.0);
else if (l<730.0) r=0.32-0.32*sin(M_PI*(l-670.0)/128.0);
else r= 0.00;
if (l<454.0) g= 0.00;
else if (l<617.0) g= 0.78*sin(M_PI*(l-454.0)/163.0);
else g= 0.00;
if (l<380.0) b= 0.00;
else if (l<400.0) b=0.14-0.14*sin(M_PI*(l-364.0)/ 35.0);
else if (l<445.0) b= 0.96*sin(M_PI*(l-395.0)/104.0);
else if (l<510.0) b= 0.96*sin(M_PI*(l-377.0)/133.0);
else b= 0.00;
}
l is input wavelength [nm] < 380,780 >
r,g,b is output RGB color < 0,1 >
This is simple rough sin wave approximation of real spectral color data. You can also create table from this and interpolate it or use texture ... output colors are:
there are also different approaches like:
linear color - composite gradients
like this: http://www.physics.sfasu.edu/astro/color/spectra.html
but the output is not good enough for me
human eye X,Y,Z sensitivity curves integration
you have to have really precise X,Y,Z curves, even slight deviation causes 'unrealistic' colors like in this example
To make it better you have to normalize colors and add exponential sensitivity corrections. Also these curves are changing with every generation and are different in different regions of world. So unless you are doing some special medical/physics softs it is not a good idea to go this way.
| <- 380nm ----------------------------------------------------------------- 780nm -> |
[edit1] here is mine new physically more accurate conversion
I strongly recommend to use this approach instead (it is more accurate and better in any way)
Well, not completely right. Here I made a javascript example of it.
Sodium yellow (589nm) is too orange and Halpha red (656nm) is too brown....
Save this example into an HTML file (jquery needed) and load it into a browser:
page.html?l=[nanometers]
<!DOCTYPE html>
<html><head>
<script src='jquery.js'></script>
<script>
/*
* Return parameter value of name (case sensitive !)
*/
function get_value(parametername)
{
readvalue=(location.search ? location.search.substring(1) : false);
if (readvalue)
{
parameter=readvalue.split('&');
for (i=0; i<parameter.length; i++)
{
if (parameter[i].split('=')[0] == parametername)
return parameter[i].split('=')[1];
}
}
return false;
}
function spectral_color(l) // RGB <- lambda l = < 380,780 > [nm]
{
var M_PI=Math.PI;
var r=0,g,b;
if (l<380.0) r= 0.00;
else if (l<400.0) r=0.05-0.05*Math.sin(M_PI*(l-366.0)/ 33.0);
else if (l<435.0) r= 0.31*Math.sin(M_PI*(l-395.0)/ 81.0);
else if (l<460.0) r= 0.31*Math.sin(M_PI*(l-412.0)/ 48.0);
else if (l<540.0) r= 0.00;
else if (l<590.0) r= 0.99*Math.sin(M_PI*(l-540.0)/104.0);
else if (l<670.0) r= 1.00*Math.sin(M_PI*(l-507.0)/182.0);
else if (l<730.0) r=0.32-0.32*Math.sin(M_PI*(l-670.0)/128.0);
else r= 0.00;
if (l<454.0) g= 0.00;
else if (l<617.0) g= 0.78*Math.sin(M_PI*(l-454.0)/163.0);
else g= 0.00;
if (l<380.0) b= 0.00;
else if (l<400.0) b=0.14-0.14*Math.sin(M_PI*(l-364.0)/ 35.0);
else if (l<445.0) b= 0.96*Math.sin(M_PI*(l-395.0)/104.0);
else if (l<510.0) b= 0.96*Math.sin(M_PI*(l-377.0)/133.0);
else b= 0.00;
var rgb = Math.floor(r*256)*65536+Math.floor(g*256)*256 + Math.floor(b*256);
rgb = '000000' + rgb.toString(16);
rgb = '#' + rgb.substr(-6).toUpperCase();
$('#color').html([r,g,b,rgb,l]);
$('body').css('background-color', rgb);
}
</script>
</head><body>
<div id='color'></div>
<script>
spectral_color(get_value('l'));
</script>
</body>
</html>
I am using Assimp to load a .3ds file, and a QGLWidget derived class to draw it using PaintGL(). It works with small .3ds files (some KBytes) but if i try to render bigger files (like 1MBytes) the application crashes. Is my way too bad and twisted? Am I doing something wrong?
With qDebug i understand that paintGL() works correctly.The problem is in ourRender method,because if i obscure the
for (int t = 0; t < p->getFaces().count(); ++t)
and in detail
glVertex3f(f.getVerticesArray()[s].getX(),f.getVerticesArray();
cycle it all works fast (but obviously nothing is painted) except grid and axis.With it,and loading some complicated 3ds,it crashes
my hardware is
Phenom II X3 2.10ghz,4GB and 6650M (last drivers)
On a Celeron 2.1 Ghz it crash
BUT on a i7 the program starts but render #2FPS (if I dont use "ourRender" method,it renders at 120fps on my pc)
void GLWidget::paintGL()
{
qDebug << "Start PaintGL() #" << times;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt(objCamera->mPos.x, objCamera->mPos.y, objCamera->mPos.z,
0, objCamera->mView.y, 0,
objCamera->mUp.x, objCamera->mUp.y, objCamera->mUp.z);
if (drawLines) glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
else glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
draw_grid();
drawAxis();
ourRender(this->scenaa);
qDebug << "Close PaintGL() #" << times;
}
And this is the "ourRender" method:
void GLWidget::ourRender(const Scene* sc){
QHash<QString, SceneObject*>& hash=sc->getObj();
double molt =1/20;
int counter =0;
for (QHash<QString,SceneObject*>::ConstIterator i = hash.begin();i!=hash.end();++i) {
aiMatrix4x4 minchia(1,0,0,molt*20,0,1,0,molt*20,0,0,1,molt*20,0,0,0,1);
aiTransposeMatrix4(&minchia);
glPushMatrix();
const Mesh* p = dynamic_cast<Mesh*>(i.value());
glMultMatrixf((float*) &minchia);
if(p){
for (int t = 0; t < p->getFaces().count(); ++t) {
Face f = p->getFaces()[t];
GLenum face_mode;
switch(f.getVerticesArray().count()) {
case 1: face_mode = GL_POINTS; break;
case 2: face_mode = GL_LINES; break;
case 3: face_mode = GL_TRIANGLES; break;
default: face_mode = GL_POLYGON; break;
}
glBegin(face_mode);
QList<Vector3D> lista = f.getVerticesArray();
for(int s = 0; s < lista.count(); s++) {
if (p->getNormals().count()>0)
--------->glVertex3f(f.getVerticesArray()[s].getX(),f.getVerticesArray()[s].getY(),f.getVerticesArray()[s].getZ());
}
glEnd();
}
}
glPopMatrix();
molt+=13;
counter++;
}
glPopMatrix();
}
...in the derived QGLWidget class costructor...
SceneImporter* aa = new AssimpAdapter();
Scene * nuovo=aa->importFile("C:/Users/nicola/Desktop/Source/aces.3ds");
scenaa=nuovo;
We solved the problem modifying "ourender" method (using references instead of copyes)
void GLWidget::ourRender(Scene *&sc){
QHash<QString, SceneObject*>& hash=sc->getObj();
int counter =0;
for (QHash<QString,SceneObject*>::ConstIterator i = hash.begin();i!=hash.end();++i) {
aiMatrix4x4 m;
aiTransposeMatrix4(&m);
glPushMatrix();
const Mesh* p = dynamic_cast<Mesh*>(i.value());
glMultMatrixf((float*) &m);
if(p){
//apply_material(aaFuori.GetScene()->mMaterials[counter]);
QList<Face>& faccie=p->getFaces();
for (int t = 0; t < faccie.count(); ++t) {
Face f = faccie[t];
GLenum face_mode;
switch(f.getVerticesArray().count()) {
case 1: face_mode = GL_POINTS; break;
case 2: face_mode = GL_LINES; break;
case 3: face_mode = GL_TRIANGLES; break;
default: face_mode = GL_POLYGON; break;
}
glBegin(face_mode);
QList<Vector3D>& lista = f.getVerticesArray();
int conta=lista.count();
glVertex3f(lista[0].x,lista[0].y,lista[0].z);
glVertex3f(lista[1].x,lista[1].y,lista[1].z);
glVertex3f(lista[2].x,lista[2].y,lista[2].z);
glEnd();
}
}
glPopMatrix();
counter++;
}
Now we can render 8MBytes .3Ds #4fps during camera rotations (instead of application crash).Could someone of you give us an opinion about this result?is it good or bad?
Optimizazion.The fact is that for every vertex we were accessing to a QList 3 times.Now we've modified it,and instead of Qlist we use a Vector3D* array that saves the position of the vertices and so we can use the GL method glVertex3fv((GLfloat*)array[numface].posvertex); So,given the pointer to the face,its much faster than before (4 to 10 fps on the same scene).
void GLWidget::ourRender(Scene *sc){
QHash<QString, SceneObject*>& hash=sc->getObj();
aiMatrix4x4 m;
for (QHash<QString,SceneObject*>::ConstIterator i = hash.begin();i!=hash.end();++i) {
aiTransposeMatrix4(&m);
glPushMatrix();
Mesh* p = dynamic_cast<Mesh*>(i.value());
glMultMatrixf((float*) &m);
if(p){
QList<Face>& faccie=p->getFaces();
int numerofacce=faccie.count();
for (int t = 0; t < numerofacce; ++t) {
Face& f = faccie[t];
GLenum face_mode;
Vector3D* lista=f.arrayVertici;
switch(f.getVerticesArray().count()) {
case 1:
face_mode = GL_POINTS;
glBegin(face_mode);
glVertex3fv((GLfloat*)lista[0].pos);
break;
case 2:
face_mode = GL_LINES;
glBegin(face_mode);
glVertex3fv((GLfloat*)lista[0].pos);
glVertex3fv((GLfloat*)lista[1].pos);
break;
case 3:
face_mode = GL_TRIANGLES;
glBegin(face_mode);
glVertex3fv(&lista[0].pos[0]);
glVertex3fv(&lista[1].pos[0]);
glVertex3fv(&lista[2].pos[0]);
break;
default: face_mode = GL_POLYGON; break;
}
glEnd();
}
}
glPopMatrix();
counter++;
}
glPopMatrix();
}
Where Vector3D is initialized like this:
Vector3D::Vector3D(double x, double y, double z) {
setX(x);
setY(y);
setZ(z);
pos[0]=x; //vertex1
pos[1]=y; //vertex2
pos[2]=z; //vertex3
}
PS:Grimmy suggests me to use DisplayLists (discovered right now).Tomorrow I will try them.
I am working on a OpenGL application in C++ for some months now and never faced this problem before.
Coding in Visual Studio 2012 I can either run the application inside of the IDE or launch the executable file manually. In both cases I can choose between the debug and the release build. The following issue only appears when I start the release build executable myself. Otherwise everything works fine.
The application crashes sometimes when I add new forms into the scene on the fly. My operating system Windows 8 64bit lets me Debug the program from the crash dialog which indeed didn't help so much since it is a release build with less debug informations available at runtime, but it at least told me that the application crashes near the draw call glDrawTriangles(). There is only one draw call inside a loop in my code.
It drives me crazy that the crash only occurs irregular. Sometimes the application runs quite well for some minutes, sometimes it crashes immediately, and sometimes it runs some seconds. But I think is is important to know that the application only crashes right after new forms are inserted into the scene which I first generate in another thread and second create the OpenGL buffers in the main thread.
Here are the problem details that were shown in the Windows crash dialog. It seems like the driver of my videocard ATI Radeon 7870 crashes.
Problem signature:
Problem Event Name: APPCRASH
Application Name: Application.exe
Application Version: 0.0.0.0
Application Timestamp: 50f1491a
Fault Module Name: atioglxx.dll
Fault Module Version: 6.14.10.11931
Fault Module Timestamp: 50650037
Exception Code: c0000005
Exception Offset: 001108ef
OS Version: 6.2.9200.2.0.0.256.27
Locale ID: 1031
Additional Information 1: 5861
Additional Information 2: 5861822e1919d7c014bbb064c64908b2
Additional Information 3: dac6
Additional Information 4: dac6c2650fa14dd558bd9f448e23afd1
Read our privacy statement online:
http://go.microsoft.com/fwlink/?linkid=190175
If the online privacy statement is not available, please read our privacy statement offline:
C:\Windows\system32\en-US\erofflps.txt
What I've done so far is updating my video card drivers and debugging my application since I notices that the result were not reliable since the crash occurs spontaneously. There are many source files and to be honest I am not sure which is effecting the bug. It might be a file called terrain.cpp so I paste the code here.
#pragma once
#include "system.h"
#include "debug.h"
#include <vector>
#include <cstdlib>
#include <future>
using namespace std;
#include <GLEW/glew.h>
#include <SFML/OpenGL.hpp>
#include <SFML/Graphics/Image.hpp>
using namespace sf;
#include <GLM/glm.hpp>
#include <GLM/gtc/noise.hpp>
using namespace glm;
#include "settings.h"
#include "camera.h"
#include "form.h"
#include "transform.h"
#include "terrain.h"
#include "shader.h"
#include "movement.h"
typedef detail::tvec3<int> vec3i;
class ComponentTerrain : public Component
{
void Init()
{
auto wld = Global->Add<StorageTerrain>("terrain");
tasking = false;
Texture();
Listeners();
}
void Update()
{
auto wld = Global->Get<StorageTerrain>("terrain");
auto stg = Global->Get<StorageSettings>("settings");
auto cam = Global->Get<StorageCamera>("camera");
auto cks = Entity->Get<StorageChunk>();
int Distance = (int)(.5f * stg->Viewdistance / CHUNK_X / 2);
Debug::Info("Terrain chunk distance " + to_string(Distance));
for(int X = -Distance; X <= Distance; ++X)
for(int Z = -Distance; Z <= Distance; ++Z)
{
addChunk(X + (int)cam->Position.x / CHUNK_X, 0, Z + (int)cam->Position.z / CHUNK_Z);
}
for(auto chunk : wld->chunks)
{
auto chk = cks.find(chunk.second);
float distance = (float)vec3(chunk.first[0] * CHUNK_X - cam->Position.x, chunk.first[1] * CHUNK_Y - cam->Position.y, chunk.first[2] * CHUNK_Z - cam->Position.z).length();
if(distance > stg->Viewdistance)
deleteChunk(chunk.first[0], chunk.first[1], chunk.first[2]);
}
if(tasking)
{
if(task.wait_for(chrono::milliseconds(0)) == future_status::ready)
{
tasking = false;
Data data = task.get();
Buffers(data);
}
}
else
{
for(auto chunk : cks)
if(chunk.second->changed)
{
tasking = true;
chunk.second->changed = false;
task = async(launch::async, &ComponentTerrain::Mesh, this, Data(chunk.first));
break;
}
}
}
struct Data
{
Data() {}
Data(unsigned int id) : id(id) {}
unsigned int id;
vector<float> Vertices, Normals, Texcoords;
vector<int> Elements;
};
future<Data> task;
bool tasking;
Image texture;
void Listeners()
{
Event->Listen("SystemInitialized", [=]{
auto cam = Global->Get<StorageCamera>("camera");
cam->Position = vec3(0, CHUNK_Y, 0);
cam->Angles = vec2(0.75, -0.25);
});
Event->Listen("InputBindChunk", [=]{
addChunk(rand() % 5, 0, rand() % 5);
addChunk(rand() % 5, 0, rand() % 5);
addChunk(rand() % 5, 0, rand() % 5);
});
}
unsigned int getChunk(int X, int Y, int Z)
{
auto wld = Global->Get<StorageTerrain>("terrain");
array<int, 3> key = {X, Y, Z};
auto i = wld->chunks.find(key);
return (i != wld->chunks.end()) ? i->second : 0;
}
int addChunk(int X, int Y, int Z)
{
auto wld = Global->Get<StorageTerrain>("terrain");
auto shd = Global->Get<StorageShader>("shader"); // moved this line
unsigned int id = getChunk(X, Y, Z);
if(!id)
{
id = Entity->New();
Entity->Add<StorageChunk>(id);
auto frm = Entity->Add<StorageForm>(id); // moved this line
auto tsf = Entity->Add<StorageTransform>(id);
frm->Program = shd->Program; // moved this line
tsf->Position = vec3(X * CHUNK_X, Y * CHUNK_Y, Z * CHUNK_Z);
Generate(id, X, Y, Z);
array<int, 3> key = {X, Y, Z};
wld->chunks.insert(make_pair(key, id));
}
return id;
}
void deleteChunk(int X, int Y, int Z)
{
auto wld = Global->Get<StorageTerrain>("terrain");
unsigned int id = getChunk(X, Y, Z);
if(id < 1) return;
array<int, 3> key = {X, Y, Z};
wld->chunks.erase(key);
Entity->Delete<StorageChunk>(id);
Entity->Delete<StorageForm>(id);
Entity->Delete<StorageTransform>(id);
// free buffers
}
void Generate(unsigned int id, int X, int Y, int Z)
{
auto cnk = Entity->Get<StorageChunk>(id);
cnk->changed = true;
for(int x = 0; x < CHUNK_X; ++x) {
const float i = X + (float)x / CHUNK_X;
for(int z = 0; z < CHUNK_Z; ++z) {
const float j = Z + (float)z / CHUNK_Z;
double height_bias = 0.30;
double height_base = 0.50 * (simplex(0.2f * vec2(i, j)) + 1) / 2;
double height_fine = 0.20 * (simplex(1.5f * vec2(i, j)) + 1) / 2;
int height = (int)((height_bias + height_base + height_fine) * CHUNK_Y);
for(int y = 0; y < height; ++y) cnk->blocks[x][y][z] = true;
} }
}
#define TILES_U 4
#define TILES_V 4
Data Mesh(Data data)
{
auto cnk = Entity->Get<StorageChunk>(data.id);
auto *Vertices = &data.Vertices, *Normals = &data.Normals, *Texcoords = &data.Texcoords;
auto *Elements = &data.Elements;
const vec2 grid(1.f / TILES_U, 1.f / TILES_V);
int n = 0;
for(int X = 0; X < CHUNK_X; ++X)
for(int Y = 0; Y < CHUNK_Y; ++Y)
for(int Z = 0; Z < CHUNK_Z; ++Z)
if(cnk->blocks[X][Y][Z])
{
int Tile = Clamp(rand() % 2 + 1, 0, TILES_U * TILES_V - 1);
for(int dim = 0; dim < 3; ++dim) { int dir = -1; do {
vec3i neigh = Shift(dim, vec3i(dir, 0, 0)) + vec3i(X, Y, Z);
if(Inside(neigh, vec3i(0), vec3i(CHUNK_X, CHUNK_Y, CHUNK_Z) - 1))
if(cnk->blocks[neigh.x][neigh.y][neigh.z])
{ dir *= -1; continue; }
for(float i = 0; i <= 1; ++i)
for(float j = 0; j <= 1; ++j)
{
vec3 vertex = vec3(X, Y, Z) + floatify(Shift(dim, vec3i((dir+1)/2, i, j)));
Vertices->push_back(vertex.x); Vertices->push_back(vertex.y); Vertices->push_back(vertex.z);
}
vec3 normal = normalize(floatify(Shift(dim, vec3i(dir, 0, 0))));
for(int i = 0; i < 4; ++i)
{
Normals->push_back(normal.x); Normals->push_back(normal.y); Normals->push_back(normal.z);
}
vec2 position = (vec2(Tile % TILES_U, Tile / TILES_U) + .25f) * grid;
Texcoords->push_back(position.x); Texcoords->push_back(position.y);
Texcoords->push_back(position.x + grid.x/2); Texcoords->push_back(position.y);
Texcoords->push_back(position.x); Texcoords->push_back(position.y + grid.y/2);
Texcoords->push_back(position.x + grid.x/2); Texcoords->push_back(position.y + grid.y/2);
if(dir == -1) {
Elements->push_back(n+0); Elements->push_back(n+1); Elements->push_back(n+2);
Elements->push_back(n+1); Elements->push_back(n+3); Elements->push_back(n+2);
} else {
Elements->push_back(n+0); Elements->push_back(n+2); Elements->push_back(n+1);
Elements->push_back(n+1); Elements->push_back(n+2); Elements->push_back(n+3);
}
n += 4;
dir *= -1; } while(dir > 0); }
}
return data;
}
void Buffers(Data data)
{
auto frm = Entity->Get<StorageForm>(data.id);
glGenBuffers(1, &frm->Positions);
glBindBuffer(GL_ARRAY_BUFFER, frm->Positions);
glBufferData(GL_ARRAY_BUFFER, data.Vertices.size() * sizeof(float), &(data.Vertices[0]), GL_STATIC_DRAW);
glGenBuffers(1, &frm->Normals);
glBindBuffer(GL_ARRAY_BUFFER, frm->Normals);
glBufferData(GL_ARRAY_BUFFER, data.Normals.size() * sizeof(float), &(data.Normals[0]), GL_STATIC_DRAW);
glGenBuffers(1, &frm->Texcoords);
glBindBuffer(GL_ARRAY_BUFFER, frm->Texcoords);
glBufferData(GL_ARRAY_BUFFER, data.Texcoords.size() * sizeof(float), &(data.Texcoords[0]), GL_STATIC_DRAW);
glGenBuffers(1, &frm->Elements);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, frm->Elements);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.Elements.size() * sizeof(int), &data.Elements[0], GL_STATIC_DRAW);
glGenTextures(1, &frm->Texture);
glBindTexture(GL_TEXTURE_2D, frm->Texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.getSize().x, texture.getSize().y, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture.getPixelsPtr());
glGenerateMipmap(GL_TEXTURE_2D);
}
void Texture()
{
Image image;
bool result = image.loadFromFile("forms/textures/terrain.png");
if(!result){ Debug::Fail("Terrain texture loading fail"); return; }
Vector2u size = Vector2u(image.getSize().x / TILES_U, image.getSize().y / TILES_V);
texture.create(image.getSize().x * 2, image.getSize().y * 2, Color());
for(int u = 0; u < TILES_U; ++u)
for(int v = 0; v < TILES_V; ++v)
{
Image tile, quarter;
tile.create(size.x, size.y, Color());
tile.copy(image, 0, 0, IntRect(size.x * u, size.y * v, size.x, size.y), true);
quarter.create(size.x, size.y, Color());
quarter.copy(tile, 0, 0, IntRect(size.x / 2, size.y / 2, size.x / 2, size.y / 2), true);
quarter.copy(tile, size.x / 2, 0, IntRect(0, size.y / 2, size.x / 2, size.y / 2), true);
quarter.copy(tile, 0, size.y / 2, IntRect(size.x / 2, 0, size.x / 2, size.y / 2), true);
quarter.copy(tile, size.x / 2, size.y / 2, IntRect(0, 0, size.x / 2, size.y / 2), true);
texture.copy(quarter, (u * 2 ) * size.x, (v * 2 ) * size.y, IntRect(0, 0, 0, 0), true);
texture.copy(quarter, (u * 2 + 1) * size.x, (v * 2 ) * size.y, IntRect(0, 0, 0, 0), true);
texture.copy(quarter, (u * 2 ) * size.x, (v * 2 + 1) * size.y, IntRect(0, 0, 0, 0), true);
texture.copy(quarter, (u * 2 + 1) * size.x, (v * 2 + 1) * size.y, IntRect(0, 0, 0, 0), true);
}
}
template <typename T>
inline T Clamp(T Value, T Min, T Max)
{
if(Value < Min) return Min;
if(Value > Max) return Max;
return Value;
}
bool Inside(vec3i Position, vec3i Min, vec3i Max)
{
if(Position.x < Min.x || Position.y < Min.y || Position.z < Min.z) return false;
if(Position.x > Max.x || Position.y > Max.y || Position.z > Max.z) return false;
return true;
}
inline vec3i Shift(int Dimension, vec3i Vector)
{
if (Dimension % 3 == 1) return vec3i(Vector.z, Vector.x, Vector.y);
else if (Dimension % 3 == 2) return vec3i(Vector.y, Vector.z, Vector.x);
else return Vector;
}
vec3 floatify(vec3i Value)
{
return vec3(Value.x, Value.y, Value.z);
}
};
However, you can find the whole code on Github.
Do you have any idea what could cause the crash or how to find the bug? Please let me know if you need more information and of which kind.
Thanks to #doomster I could find and fix the bug now.
The renderer component loops over a vector of forms to draw them. The asynchronous thread added new forms to that vector but their buffers were created later in the main thread after the generated vertices were returned. That means that drawing and form adding ran in parallel. The crash occurred when the renderer tried to render a form without any created buffers yet.
I inserted comments the code above to highlight the three lines I moved from addChunk() to Buffers() for fixing the issue. I sill do not know why only the release build executable crashed but that doesn't really matter any more.
There are two things I would verify here:
You are using a future<T>. I'm not sure, but if it runs in a different thread, that could have a negative impact on OpenGL which I have heard can behave sensitive to multithreaded use. Treat this as a rumour though, I'm not really sure about it, but trying to convert to single-threaded code is worth an attempt.
You can activate debug symbols in a release build, too. This should at least give you a usable backtrace when the crash happens. Actually, VS's Debug/Release settings are just default settings but without intrinsic meaning, so you can modify the Debug settings in steps until it matches the Release settings. That should give you a variant that fails while still being usable in a debugger.