Yeah, I know. I have that resolution about coding something with 2D graphics and this is clearly meant for the 3D world. There still might be a use for this even there. There's some thought that using less texture files is better, which means that I'll still need to store UV coordinates for each object. So I'd still want an OBJ file to hold the two triangles making a rectangle, just to have a convenient place to store the UV coordinates. At least that's how I'm spinning this so as to not break that resolution so quickly.
The OBJ model format is really easy to deal with. Reading in text files is super easy, the only real trick is in getting the data sorted out. This is something I'm not entirely ready to do yet. Normals, UV positions, vertex coordinates, and faces I understand; all the other stuff in the file is beyond me. I get the idea behind material properties, but i'm not at all ready to think about putting them to use. The loader that I'll be making will be pretty basic, only pulling out the data I can make sense of and discarding the rest.
While I'm mentioning limitations I should also say that when my loader works on the file it's expecting triangles only. It wouldn't be hard to adjust it to use quads, I just want to stick with triangles right now. It's possible to mess up the coordinates of a quad such that it becomes an invalid shape to OpenGL, you can't do that with triangles. I'm still fairly new to all this stuff and "messing stuff up" is something I do a lot. Even cutting off one source for problems can be a big help when trying to figure stuff out.
Ok, disclaimers are done, let's see how to do this. If you open an OBJ file in your favorite text editor you'll see that it looks kind of like this:
1: # Blender v2.69 (sub 0) OBJ File: ''
2: # www.blender.org
3: mtllib Cube.mtl
4: o Cube_Cube.001
5: v 1.000000 -1.000000 -1.000000
6: v 1.000000 -1.000000 1.000000
7: v -1.000000 -1.000000 1.000000
8: v -1.000000 -1.000000 -1.000000
9: v 1.000000 1.000000 -1.000000
10: v 1.000000 1.000000 1.000000
11: v -1.000000 1.000000 1.000000
12: v -1.000000 1.000000 -1.000000
13: vt 0.000000 0.000000
14: vt 0.000000 1.000000
15: vt 1.000000 0.000000
16: vt 1.000000 1.000000
17: vn 0.000000 0.000000 -1.000000
18: vn -1.000000 -0.000000 -0.000000
19: vn -0.000000 -0.000000 1.000000
20: vn -0.000001 0.000000 1.000000
21: vn 1.000000 -0.000000 0.000000
22: vn 1.000000 0.000000 0.000001
23: vn 0.000000 1.000000 -0.000000
24: vn -0.000000 -1.000000 0.000000
25: usemtl None
26: s off
27: f 5/1/1 1/2/1 4/4/1
28: f 5/1/1 4/2/1 8/1/1
29: f 3/4/2 7/3/2 8/1/2
30: f 3/4/2 8/1/2 4/2/2
31: f 2/4/3 6/3/3 3/2/3
32: f 6/3/4 7/1/4 3/2/4
33: f 1/4/5 5/3/5 2/2/5
34: f 5/3/6 6/1/6 2/2/6
35: f 5/3/7 8/1/7 6/4/7
36: f 8/1/7 7/2/7 6/4/7
37: f 1/4/8 2/3/8 3/1/8
38: f 1/4/8 3/1/8 4/2/8
Pound, #, marks comments so we can ignore those lines.
Lines starting with v are vertex coordinates. The coordinates are in X, Y, Z order and separated by a space.
Lines with vt are UV coordinates. They are in U, V order and separated by a space.
Lines beginning with vn are for normals. Just like the vertexes they are in X, Y, Z ordder separated by a space.
The f marks lines that define faces. A face is a single polygon, in this example triangles. The three vertexes are separated by spaces. The numbers separated by forward slashes, /, are vertex number, UV number, and normal number. Were you to start counting v, vt, or vn lines you'd reach the numbers specified in the face lines. It's kind of like if you thought of these lines as elements in an index and the face line rattles off the indexes of those arrays. Just remember that these numbers start with 1, while arrays in C# start with zero.
The other lines probably have really useful information in them, it's just beyond my skills to work on so I'm ignoring them.
I like to take two passes on these files. The first to load all of the vertex, UV, and normal data into arrays. Then the second pass to work out the faces. I know the file stand says these lines should be grouped such that this isn't necessary, I just have a hard time accepting that external data will be exactly right all the time. Especially when I'll be generating this data by hand. Regardless the models only get loaded once, so this slight delay isn't likely to be too noticeable until I start working with hundreds of models.
On the first pass I read the file, then go through each line putting the values I want into some temporary lists:
1: public bool LoadOBJFile(string strFile, out Vector3[] av3VertexCoords, out Vector3[] av3NormalCoords, out Vector2 av2UVCoords) {
2: string strChunk;
3: string[] astrLines, astrLineParts, astrFaceParts;
4: int iCtr, iCnt, iFaceCount, iCurrVert, iSrcIndex;
5: List<Vector3> lv3Vertexes, lv3Normals;
6: List<Vector2> lv2UVCoords;
7: Vector3 v3New;
8: Vector2 v2New;
9: StreamReader srModel;
10:
11: iFaceCount = 0;
12:
13: lv3Vertexes = new List<Vector3>();
14: lv3Normals = new List<Vector3>();
15: v3New = new Vector3();
16:
17: lv2UVCoords = new List<Vector2>();
18: v2New = new Vector2();
19:
20: srModel = new StreamReader(strFile);
21: strChunk = srModel.ReadToEnd();
22: srModel.Close();
23:
24: astrLines = strChunk.Split('\n');
25:
26: //First pass through the file: Load all vertex information
27: for (iCtr = 0; iCtr < astrLines.Length; iCtr++) {
28: astrLineParts = astrLines[iCtr].Split(' ');
29:
30: switch (astrLineParts[0]) {
31: case "v" : //Vertex coordinates
32: v3New.X = float.Parse(astrLineParts[1], CultureInfo.InvariantCulture.NumberFormat);
33: v3New.Y = float.Parse(astrLineParts[2], CultureInfo.InvariantCulture.NumberFormat);
34: v3New.Z = float.Parse(astrLineParts[3], CultureInfo.InvariantCulture.NumberFormat);
35:
36: lv3Vertexes.Add(v3New);
37: break;
38: case "vt" : //Vertex texture coordinates (UV Coordinates)
39: v2New.X = float.Parse(astrLineParts[1], CultureInfo.InvariantCulture.NumberFormat);
40: v2New.Y = float.Parse(astrLineParts[2], CultureInfo.InvariantCulture.NumberFormat);
41:
42: lv2UVCoords.Add(v2New);
43: break;
44: case "vn" : //Vertex normal
45: v3New.X = float.Parse(astrLineParts[1], CultureInfo.InvariantCulture.NumberFormat);
46: v3New.Y = float.Parse(astrLineParts[2], CultureInfo.InvariantCulture.NumberFormat);
47: v3New.Z = float.Parse(astrLineParts[3], CultureInfo.InvariantCulture.NumberFormat);
48:
49: lv3Normals.Add(v3New);
50: break;
51: case "f" : //Face information, just count them
52: iFaceCount++;
53: break;
54: default : //Unknown line information, skip it
55: break;
56: }
57: }
58:
59: //Second pass loop omitted
60: }
The next pass goes through all the lines again, but this time builds the final arrays from the loaded data:
1: public bool LoadOBJFile(string strFile, out Vector3[] av3VertexCoords, out Vector3[] av3NormalCoords, out Vector2 av2UVCoords) {
2: //First pass loop omitted
3:
4: //Resize the class data arrays based on the number of faces
5: av3VertexCoords = new Vector3[iFaceCount * 3]; //FaceCount has the number of faces
6: av3NormalCoords = new Vector3[iFaceCount * 3]; //Each face has 3 vertexes
7:
8: av2UVCoords = new Vector2[iFaceCount * 3];
9:
10: //Second pass through the file: process all faces
11: iCurrVert = 0;
12: for (iCtr = 0; iCtr < astrLines.Length; iCtr++) {
13: astrLineParts = astrLines[iCtr].Split(' ');
14:
15: switch (astrLineParts[0]) {
16: case "f" : //Face information
17: for (iCnt = 1; iCnt <= 3; iCnt++) {
18: astrFaceParts = astrLineParts[iCnt].Split('/');
19:
20: //Copy vertex coordinates
21: iSrcIndex = Convert.ToInt32(astrFaceParts[0]) - 1; //Obj indexes start at 1, C# arrays start at 0
22: av3VertexCoords[iCurrVert].X = lv3Vertexes[iSrcIndex].X;
23: av3VertexCoords[iCurrVert].Y = lv3Vertexes[iSrcIndex].Y;
24: av3VertexCoords[iCurrVert].Z = lv3Vertexes[iSrcIndex].Z;
25:
26: //Copy UV Coordinates
27: iSrcIndex = Convert.ToInt32(astrFaceParts[1]) - 1;
28: av2UVCoords[iCurrVert].X = lv2UVCoords[iSrcIndex].X;
29: av2UVCoords[iCurrVert].Y = lv2UVCoords[iSrcIndex].Y;
30:
31: //Copy Normal values
32: iSrcIndex = Convert.ToInt32(astrFaceParts[2]) - 1;
33: av3NormalCoords[iCurrVert].X = lv3Normals[iSrcIndex].X;
34: av3NormalCoords[iCurrVert].Y = lv3Normals[iSrcIndex].Y;
35: av3NormalCoords[iCurrVert].Z = lv3Normals[iSrcIndex].Z;
36:
37: iCurrVert++;
38: }
39: break;
40: default : //Ignore all other lines
41: break;
42: }
43: }
44:
45: return true;
46: }
I use the temporary lists since C# doesn't have a means of changing array sizes easily. However, since the OpenGL functions expect a standard array I have to convert to normal arrays in the end. Once you've got the data in those arrays though, you're all set to feed the information to OpenGL.
No comments:
Post a Comment