Collada format interleaved index buffer?

I’m trying to parse and render collada format.

I’m kinda stuck here. I have these indexes that I need to parse.


2 0 0 0 1 0 3 1 2 1 1 1
[triangle 1 [position normal] [position normal] [position normal] ] 
[triangle 2 [position normal] [position normal] [position normal] ]

However, I cannot find much info about opengl interleaved index buffer. All I found was a thread saying that opengl doesn’t support interleaved indexing and that I would need to process the data first. Is this correct step to take? Or could I somehow use this data in a different way?

I take it you want to parse the node of a collada file.

is the indexes


<p>2 0 0 0 1 0 3 1 2 1 1 1<p>

so I presume you have parsed the


<input semantic="VERTEX" source="#your-mesh-vertices" offset="0"/>

and also


<input semantic="NORMAL" source="#your-mesh-normals" offset="1"/>


int normal_offset =1;

so with that in mind you need a class to hold your triangle data I call mine Face but you can call it triangle if you like.


public class Face 
{

// Indices for each vertex point, 3 points for each face.
		int v1, v2, v3, n1,n2, n3;

                public Face(int v1, int v2, int v3){
			this.v1 = v1;
			this.v2 = v2;
			this.v3 = v3;
	
		}
                
		public void setNormal(int n1,int n2, int n3)
                {
			this.n1 = n1;
			this.n2 = n2;
			this.n3 = n3;
		}
}
                

You need the stride of your array which is the number of inputs… 2 in this case.


int triStride =2;

So putting it together we can loop through p to save the array in a format that is easy to use.


for (int i = 0; i < p.length;i+= 3*triStride)
        {
               Face face = new Face(p[i],p[i+triStride],p[i+triStride+triStride]);

               face.setNormal(p[i+normal_offset],
                                      p[i+normal_offset+triStride],
                                      p[i+normal_offset+triStride+triStride]);
               facesArrayList.addFace(face);
        }        

With the faces created you can now decide if you want to create an interleaved index buffer or just separated type. I use interleaved so I explain that one.

I use a class called Vertex to store the data in a per vertex way instead of per face, which is then used to create an interleaved buffer without repeated vertex data. I’ve striped out all the checks that I usually make for brevity, also usually the vertex class has UV color and bone data included.


public class Vertex {
    private final float[] xyz = new float[3]; 
    private float[] norm;
    private int size;

   public void setVertex(Vec3 vec){
        xyz[0] = vec.x;
        xyz[1] = vec.y;
        xyz[2] = vec.z;
        size = 3;
    }
    
    public void setNormal(Vec3 vec){
        norm = new float[3]; 
        norm[0] = vec.x;
        norm[1] = vec.y;
        norm[2] = vec.z;
        size +=3;
    }

public float[] getVerts(){
    float[] verts = new float[size];              
    int i = 0;                
    System.arraycopy(xyz, 0, verts, i, 3);
    i += 3;
    System.arraycopy(norm, 0, verts, i, 3);
    i += 3;// for other data not included here....
    return verts;
}

Vertex also overrides the equals method and hashCode

The next step is to take the ArrayList of faces, ArrayList of verts and ArrayList of norms; ( which I presume you have already filled from the nodes and respectively), and put them in to the a vertexs ArrayList, and also put indexs into an indexes ArrayList (demonstrated next).


 for(Face each : faces)
            {
                 
                // vertex 1 -----------------------------------------------
                Vertex v = new Vertex();              
                v.setVertex(verts.get(each.v1)); 
                v.setNormal(norms.get(each.n1));                   

// check to see if the vertex already exists, if it does just ad the index to the index array if not add it do both.               

                short index = (short) this.vertexs.indexOf(v);      
                if (index < 0) {                    
                    this.vertexs.add(v);
                    index = (short) (this.vertexs.size() - 1);
                }
                indexes.add(index);

                // vertex 2 ------------------------------------------------
// do the same for each vertex in the face.

                v = new Vertex(); 
                v.setVertex(verts.get(each.v2));                 
                v.setNormal(norms.get(each.n2));   

                index = (short) this.vertexs.indexOf(v);        
                if (index < 0) {                      
                    this.vertexs.add(v);
                    index = (short) (this.vertexs.size() - 1);
                }
                indexes.add(index);

                // vertex 3      -------------------------------------------

                v = new Vertex();
                v.setVertex(verts.get(each.v3)); 
                v.setNormal(norms.get(each.n3));   
  
                index = (short) this.vertexs.indexOf(v);        
                if (index < 0) {                    
                    this.vertexs.add(v);
                    index = (short) (this.vertexs.size() - 1);
                }
                indexes.add(index);
                }
            }
         }


The last part is for you to decide which you put the data straight into a FloatBuffer and IntBuffer or just use regular java arrays. It depends on what you want to do with the data. I personally convert it to my own model format and save in a binary file to load later, but you might want to check that your code works first and view your model.

You can get the data into arrays with this following code.


            int size = vertexs.get(0).size();

            float[] vertices = new float[vertexs.size() * size];		

            // puts the vertex data in to a float array.
            for (int i = 0;i < vertexs.size();i++){
                System.arraycopy(vertexs.get(i).getVerts(), 0, vertices, i*size, size);			
            }

            // put the indexes into an short array
            short[] ind = new short[indexes.size()];
            for (int i = 0; i < indexes.size();i++){
                            ind[i] = indexes.get(i);				
            }

Incidentally you can use the same code to parse so long as the node only contains 3s (triangulate from your modeling program first)

Next step is in opengl to use the interleaved buffer.


 int VERTEX_POS_SIZE = 3;
int VERTEX_NORMAL_SIZE = 3;
int FLOAT_SIZE_BYTES = 4;
stride = FLOAT_SIZE_BYTES * VERTEX_POS_SIZE +VERTEX_NORMAL_SIZE ; // length of the vertex 3 + 3 


// create buffer
int vertBuffer= glGenBuffer();

glBindBuffer(GL_ARRAY_BUFFER, vertBuffer);
		glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);//vertices are what what made in the previous post.
		glBindBuffer(GL_ARRAY_BUFFER, 0);

in indexBuffer = glGenBuffer();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer );
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);//indices where also made in the previous post.
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

//bind buffer
glBindBuffer(GL_ARRAY_BUFFER, vertBuffer);
 

glEnableVertexAttribArray(shader.getPositionHandle()); // however you get your handles your self
glEnableVertexAttribArray(shader.getNormalHandle()); // dito


int offset =0;

            glVertexAttribPointer(shader.getPositionHandle(), 
                            VERTEX_POS_SIZE, GL_FLOAT, stride, 0);		
            offset += FLOAT_SIZE_BYTES *3;
		
            glVertexAttribPointer(shader.getNormalHandle(),
                            VERTEX_NORMAL_SIZE, GL_FLOAT, stride, offset);            
            offset += FLOAT_SIZE_BYTES*3;

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer );
//draw
glDrawElements(GL_TRIANGLES, vertices.length, GL_UNSIGNED_SHORT, 0);


// unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(shader.getPositionHandle());
glDisableVertexAttribArray(shader.getNormalHandle());

My code presumes the verts and indexs are in the format your methods can take… you might need to convert to FloatBuffer or IntBuffer first.

This code is not the latest as it works for GLES2 so I’m sure there is a proper way to do it for 4+

Thanks for the post, but I just wanted to know if I can plug the data from collada to opengl directly, without having to convert data in any way.

/facepalm given that the collada format isn’t in the format of an interleaved array then no. you have to convert it as described.

Just started working again on the parser, and I came up with a huge problem. I exported a single quad (triangulated) from Blender, however the position values are just plain wrong.


         <float_array id="Cube-mesh-positions-array" count="12">1 1 -1 -0.9999997 1 -1 1 0.9999995 1 -1 1 1</float_array>
          <technique_common>
            <accessor source="#Cube-mesh-positions-array" count="4" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>

This is supposed to be a face looking directly at the camera, but as you can see, there are positions that are not on the Z -1. In blender, whole plane is displayed at z = -1.

Am I doing something wrong?

Edit----------
Coordinates are supposed to look something like this

1 1 -1 -1 1 -1 1 -1 -1 -1 -1 -1

Basically all on the same Z plane making up a nice square.
I get a stupid looking quad with the exported coordinates.

Two things to think about.

  1. By default, Blender exports with a z-up system whereas chances are you want a y-up system. In the export screen in Blender, look over the options in the bottom right hand corner.

  2. With COLLADA you cannot just look at a single isolated element. This will be accessed from an (well actually a which is referenced from a (or similar) inside a inside a referenced by an inside a tree of s inside a referenced by an inside the (That was from memory and some of it might be wrong). And every step along the way there can be stuff that will change the way it works. I expect that one or more of the s of the scenegraph contains a transform which is distorting your quad because you are ignoring it.

So COLLADA isn’t that complex but there is a lot of work to do to make a full parser. If you are going to make a fully functional parser, then you have to start in the node and follow the references all the way down to the actual data collecting all the information along the way. It is no simple matter.

However there is (I believe) another option in the Blender export screen that lets you pre-apply any kind of transform to the geometry before exporting. I believe this will mean that any transforms will be applied to the geometry rather than defined in the scenegraph.

Hope this helps, Quew8

Thanks for info, I will look into it when I get home.

It seems that blender doesn’t support up axis change when exporting. Blender exports Z-up, and gluPerspective is Y-up.

I guess I will just try to change the perspective matrix so that everything is like in blender.

better change everything so that everthing is like you like it to have.

you could modify the importer and swizzle the vertices like …

// assuming a b c are floats ...
if(flipYZ) vertex(a,-c,b);
else vertex(a,b,c);

you get the idea.

I think a lot easier way is to rotate modelview matrix. It gives me just the result I want.


glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glRotatef(90, 1, 0, 0);

EDIT---------------------------

If someone is thinking that I want to write a fully functional collada parser, think again.

ja, that works too :slight_smile: … but it rotates everything and not just the imported file

Ah sorry. Haven’t used Blender in a while, turns out it was the Wavefront export that had those options. Well the other thing is that in the asset node in the COLLADA document, you will find an element called which, exporting from Blender, has the value “Z_UP”. You can read this element to work out how to transform the data to get it into the y-up format.

And I would recommend transforming the vertices as you parse them. As @basil_ says, best to get it in the format you like and are used to. Otherwise you are just adding another layer of complexity which is wholly unnecessary and can only lead to bad things.