Say you created a really simple cube in 3DSmax right in the middle of the scene. Then you export this cube as an ASE file (ASCII Scene Export). Now you want to get the vertices of the cube which can then be used in OpenGL. I have used software like and etc that claim to be exporting the vertex data from ASE files to no avail. The cube that gave me just had one face.

Has anybody done this before?
Posted on 2008-12-12 05:43:48 by XCHG
Years back, a friend and I was toying around writing a software 3D engine. We tried parsing .3ds (not the newer .max) format directly, but there seemed to be some error in the specification, or with our code (there would be a few faces that were totally wrong, giving odd glitches when rendering).

So we moved to .ase format, which turned out pretty easy to parse. For faster loading, we made up our own binary format, and I wrote a ase->ownformat converter (very simple stuff, only face and vertex data, no colors or textures or multiple-mesh support or anything).

Anyway, what I'm trying to say is: try writing your own converter, and write GL code that renders those data structures, instead of trying to convert a 3d model to OpenGL calls.
Posted on 2008-12-12 05:52:02 by f0dder
For most stuff, .obj format does the job, and is the easiest to parse. That's why every modeler exports/imports from obj. Make a converter obj-bin. Usually a binary model-file will contain one or more meshes, each mesh will have its own material (shader-name, texture-name and shader-uniforms).
Posted on 2008-12-12 08:22:56 by Ultrano
Okay thanks for your responses but let me be more specific. When I want to draw a cube, I just do calls to the glVertex3f() function and create TRIANGLE STRIPs. So say you want to get these vertices from the ASE file? We have different sections such as VERTEX and FACE and etc in the ASE file. I just can't understand the difference :-? Could somebody give me an example here?
Posted on 2008-12-12 09:38:26 by XCHG
When one ignores the ancient OpenGL fluff, there are only two ways to specify vertex-data: with or without vtx-indexes. In both cases, you use GL_TRIANGLES.

typedef struct
float x,y,z;
}vec3;
typedef struct{
int v0,v1,v2; // index of vertex to use
}FACE;



//-------[ method 1 ]-------------------------------------[
vec3 Array1[1000*3]; // contains data for 1000 triangles, a lot of duplicates
void DrawArray(vec3* ptr,int numTriangles);
//--------------------------------------------------------/

//-------[ method 2 ]-----------------------------[
vec3 Array2[200]; // no duplicates
int Indices2[1000*3]; // indices into Array2, that specifies each triangle of which 3 vertices is formed
// FACE Indices2[1000]; // same as above.
void DrawIndexed(vec3* ptr,int* indices,int numTriangles)
//------------------------------------------------/


A vertex is a point in 3D space, with optional extra parameters attached to it. In modern gpus, a "face" is never more than those three ints "v0,v1,v2".
Posted on 2008-12-12 09:53:18 by Ultrano
So basically a face is a collection of three vertices?
Posted on 2008-12-12 10:09:55 by XCHG
Yes, in the world of realtime 3D.

And vertices usually have as a minimum this data:
typedef struct{
vec3 position;
vec3 normal; // length(normal) = 1.0
float texU,texV; // UV texture coordinate,  0..1 each
}TinyVertex;

Posted on 2008-12-12 10:23:37 by Ultrano
Ah I see. Thanks. So for example, if this is how I draw my cube in VC++:

  glBegin(GL_TRIANGLE_STRIP);
    /* Front side */
    glColor3f(+1.0f, +0.0f, +0.0f);
    glVertex3f(+0.0f, +0.0f, +0.0f);
    glVertex3f(+1.0f, 0.0f, +0.0f);
    glVertex3f(+0.0f, +1.0f, +0.0f);
    glVertex3f(+1.0f, +1.0f, +0.0f);
    /* The Top side */
    glColor3f(+0.0f, +1.0f, +0.0f);
    glVertex3f(+0.0f, +1.0f, -1.0f);
    glVertex3f(+1.0f, +1.0f, -1.0f);
    /* The back face */
    glColor3f(+0.0f, +0.0f, +1.0f);
    glVertex3f(+0.0f, +0.0f, -1.0f);
    glVertex3f(+1.0f, +0.0f, -1.0f);
    /* The bottom face */
    glColor3f(+1.0f, +1.0f, +1.0f);
    glVertex3f(+0.0f, +0.0f, +0.0f);
    glVertex3f(+1.0f, +0.0f, +0.0f);
    /* Draw the right face */
    glColor3f(+0.05f, +0.5f, +0.5f);
    glVertex3f(+1.0f, +0.0f, +0.0f);
    glVertex3f(+1.0f, +0.0f, -1.0f);
    glVertex3f(+1.0f, +1.0f, +0.0f);
    glVertex3f(+1.0f, +1.0f, -1.0f);
    /* Draw the left face */
    glColor3f(+0.05f, +0.8f, +0.5f);
    glVertex3f(+0.0f, +0.0f, +0.0f);
    glVertex3f(+0.0f, +0.0f, -1.0f);
    glVertex3f(+0.0f, +1.0f, +0.0f);
    glVertex3f(+0.0f, +1.0f, -1.0f);
  glEnd(); 


What I want is to create a cube like this in 3DSMax and let it generate the vertices for me, not even the colors, and I just put the vertices in my code. Is that doable?
Posted on 2008-12-12 10:28:53 by XCHG
Yes, let's first do it without a converter. And we'll use the data, seen in an ".obj" exported file. Here's a cube/box:

o cube.obj
mtllib cube.mtl

v -1.275 -1.1 1.6
v -1.275 -1.1 -0.75
v -1.275 1.1 1.6
v -1.275 1.1 -0.75
v 1.275 -1.1 1.6
v 1.275 -1.1 -0.75
v 1.275 1.1 1.6
v 1.275 1.1 -0.75
usemtl Default

f 2 6 5 1
f 5 7 3 1
f 6 8 7 5
f 4 8 6 2
f 3 4 2 1
f 7 8 4 3

As you see, each line is a command with syntax "commandName params,..".  The command "v" specifies a vertex-position, the command "f" specifies face-indices. Ignore the other commands, for now.

I'll be typing in C++, for easier understanding.


typedef struct{
float x,y,z;
}vec3;
typedef struct{
int v0,v1,v2,v3;
}quad;



vec3 AllVertices[8]={
{-1.275,-1.1, 1.6},
{-1.275,-1.1, -0.75},
{-1.275, 1.1, 1.6},
{-1.275, 1.1, -0.75},
{1.275, -1.1, 1.6},
{1.275, -1.1, -0.75},
{1.275, 1.1, 1.6},
{1.275, 1.1, -0.75}
};

quad AllQuads[6]={ // ouch, note that these are 1-based, not 0-based indices
{2,6,5,1},
{5,7,3,1},
{6,8,7,5},
{4,8,6,2},
{3,4,2,1},
{7,8,4 3}
};



void DrawCube(){
glBegin(GL_QUADS);
for(int curQuad=0;curQuad<6;curQuad++){
quad* q = &AllQuads;

glVertex3fv(&AllVertices);
glVertex3fv(&AllVertices);
glVertex3fv(&AllVertices);
glVertex3fv(&AllVertices);
}
glEnd();
}


Easy, isn't it :D
Posted on 2008-12-12 10:59:20 by Ultrano
Alright I see. So we have indices to the array of vertices. And we pick the first index from the array of indices, make it 0-based, then look into the array of vertices and get that index and put it as X, Y and Z?
Posted on 2008-12-12 11:00:54 by XCHG
Yup :)
Notice how this artificially increases the number of vertices, sent to the gpu (we send duplicates many times).
But don't worry about that, for now. Heck, even Quake3 did that initially.

In real code, we could have 1MB of vertex-data and 0.2MB index-data, and by duplicating the vtx-data, we can take-up 10MB easily !
OpenGL provides ways to simply upload the AllVertices[] and AllQuads[] to VRAM, and make the gpu use them. (smart caching of computed data is kept by the gpu)

By the way, avoid triangle-strips, they're utterly useless. Even GL_QUADS (which are NOT supported by hardware) are a thousand times more useful. (the videocard driver internally splits quads into triangles)
Posted on 2008-12-12 11:36:46 by Ultrano
So this is what happened:

1) I created a simple cube in 3DSMax as shown in the image below.
2) I exported it as an OBJ with the settings that I have shown in the second image and I got this file:

# 3ds Max Wavefront OBJ Exporter v0.94b - (c)2007 guruware
# File Created: 12.12.2008 17:18:34

#
# object Box01
#

v  0.1011 0.0674 0.0000
v  0.1011 9.9730 0.0000
v  10.0067 9.9730 0.0000
v  10.0067 0.0674 0.0000
v  0.1011 0.0674 9.4340
v  10.0067 0.0674 9.4340
v  10.0067 9.9730 9.4340
v  0.1011 9.9730 9.4340
# 8 vertices

g Box01
f 1 2 3
f 3 4 1
f 5 6 7
f 7 8 5
f 1 4 6
f 6 5 1
f 4 3 7
f 7 6 4
f 3 2 8
f 8 7 3
f 2 1 5
f 5 8 2
# 12 faces


To start with the vertices are scaled much more than I expected them to be. My view port is smaller than this.


3) I imported it into the program:

struct TOpenGLVertex{
 GLfloat x;
 GLfloat y;
 GLfloat z;
};

struct TOpenGLFace{
 int Index1;
 int Index2;
 int Index3;
};


And then the rendering:



 struct TOpenGLVertex Vertices[8] = {
   {0.10110f  ,0.0674f, 0.0000f},
   {0.10110f  ,9.9730f, 0.0000f},
   {10.0067f ,9.9730f, 0.0000f},
   {10.0067f ,0.0674f, 0.0000f},
   {0.10110f  ,0.0674f, 9.4340f},
   {10.0067f ,0.0674f, 9.4340f},
   {10.0067f ,9.9730f, 9.4340f},
   {0.10110f  ,9.9730f, 9.4340f}
 };


 struct TOpenGLFace Faces[12] = {
   {1, 2, 3},
   {3, 4, 1},
   {5, 6, 7},
   {7, 8, 5},
   {1, 4, 6},
   {6, 5, 1},
   {4, 3, 7},
   {7, 6, 4},
   {3, 2, 8},
   {8, 7, 3},
   {2, 1, 5},
   {5, 8, 2}
 };

 glBegin(GL_TRIANGLE_STRIP);
   int FaceCounter = 0;
   for (FaceCounter = 0; FaceCounter < 12; FaceCounter++){
     TOpenGLVertex CurrentVertex1 = Vertices.Index1 - 1];
     TOpenGLVertex CurrentVertex2 = Vertices.Index1 - 1];
     TOpenGLVertex CurrentVertex3 = Vertices.Index1 - 1];
     glVertex3f(CurrentVertex1.x, CurrentVertex1.y, CurrentVertex1.z);
     glVertex3f(CurrentVertex2.x, CurrentVertex2.y, CurrentVertex2.z);
     glVertex3f(CurrentVertex3.x, CurrentVertex3.y, CurrentVertex3.z);
   }
 glEnd();


I also zoomed out of the scene to see if something was hiding somewhere off my view but I didn't get anything.
Posted on 2008-12-12 11:49:42 by XCHG
Like I said, avoid triangle-strips :).
Simply put GL_TRIANGLES there, instead of GL_TRIANGLE_STRIP.

Triangle-strip is an extremely-special case, where your indices are something like [0,1,2], [3,2,1], [2,3,4], [5,4,3]. You can only draw one thing with a triangle-strip: a carpet. Yes, only a carpet. No boxes, no human heads, no architecture; just a carpet.
Posted on 2008-12-12 11:59:59 by Ultrano
Yeah I had noticed that but I liked Triangle Strips :( They make you think where the next vertex goes lol. But the important thing is that I fixed it:

  struct TOpenGLVertex Vertices[8] = {
    {0.10110f, 0.0000f, -0.0674f},
    {0.10110f, 0.0000f, -9.9730f},
    {10.0067f, 0.0000f, -9.9730f},
    {10.0067f, 0.0000f, -0.0674f},
    {0.10110f, 9.4340f, -0.0674f},
    {10.0067f, 9.4340f, -0.0674f},
    {10.0067f, 9.4340f, -9.9730f},
    {0.10110f, 9.4340f, -9.9730f},
  };


  struct TOpenGLFace Faces[12] = {
    {1, 2, 3},
    {3, 4, 1},
    {5, 6, 7},
    {7, 8, 5},
    {1, 4, 6},
    {6, 5, 1},
    {4, 3, 7},
    {7, 6, 4},
    {3, 2, 8},
    {8, 7, 3},
    {2, 1, 5},
    {5, 8, 2},
  };

  glBegin(GL_TRIANGLES);
    int FaceCounter = 0;
    for (FaceCounter = 0; FaceCounter < 12; FaceCounter++){
      TOpenGLVertex CurrentVertex1 = Vertices.Index1 - 1];
      TOpenGLVertex CurrentVertex2 = Vertices.Index2 - 1];
      TOpenGLVertex CurrentVertex3 = Vertices.Index3 - 1];
      glVertex3f(CurrentVertex1.x, CurrentVertex1.y, CurrentVertex1.z);
      glVertex3f(CurrentVertex2.x, CurrentVertex2.y, CurrentVertex2.z);
      glVertex3f(CurrentVertex3.x, CurrentVertex3.y, CurrentVertex3.z);
    }
  glEnd();


My Y and Z indices were in the wrong place and also I had copy and pasted the first 3 lines in the loop and all of them were Faces.Index1 instead of

Faces.Index1
Faces.Index2
Faces.Index3

That's really cool. I got a little cube flying around here. Thanks a lot Ultrano. You are great.
Posted on 2008-12-12 12:02:45 by XCHG
You're welcome;
But always remember: triangle-strip = carpet, absolutely nothing else. Yeah, it makes you think how to arrange your vertices... to draw a carpet ... but there is a much harder/fun thing around indexed-triangles ;). Here's the thing: optimize the indices, so that there is minimum cache-stalls. The gpu keeps in cache only the last 15 vertices. (each computed, cached vertex is 256 bytes!). This is not actually a memory-problem, but "caching" a vertex actually means "recomputing it via your shader".
Posted on 2008-12-12 12:20:15 by Ultrano
Oh I see. The reason I was going for Triangle Strips was that all the tutorials I was reading for OpenGL ES were using it for the iPhone and this what I wanted is also going to be ported to iPhone with OpenGL ES. I just have to check there and see if GL_TRAINGLES is supported. I think it is but I have to double check.
Posted on 2008-12-13 02:51:09 by XCHG
Very late to the party, but GL_TRIANGLES should always be supported, it's the most basic type of primitive in OpenGL (or pretty much any 3D software these days).
Strips are often used because they are very efficient in terms of processing and storage. Most objects can be stored in just a few triangle strips (the surface 'wraps around' the object... you can use degenerate triangles to 'connect' different parts of a mesh).
A cube can be done in only one strip (unless per-vertex data prevents the re-use of certain vertices).

However, this should be seen as a final optimization. It's more convenient to use triangle lists during testing and processing of data.
You can just optimize triangle lists into strips once the data is final. I rarely use triangle strips myself, because I never get to that stage :)

Triangle fans however... they have very little practical use. Generally you can't squeeze a whole lot of triangles into a fan, so you need a lot of fans, meaning a lot of draw calls, meaning a lot of overhead. I believe one nVidia presentation once said "If you think you need a triangle fan, you've done something wrong".

At any rate, do always try to use indexed geometry.

My Java engine also used .ASE files for the Croissant 9 demo. If you have any questions regarding that format, perhaps I can answer them.
Posted on 2009-11-11 05:49:39 by Scali