We are finally back to working on AGE; picking up where we left off, the OBJ loader. After all, who wants to hard-code all of those vertices!?
There's not much documentation on the format, but it's that simple! The current state of implementations is pretty sorry with respect to Garbage Generation; ArrayList<Float>
is a path to ruin, even for a small model. There's that, then there's use of some parser like StringTokenizer
or worse yet, Scanner
! We looked at the Java 7 source, there's lots of "state" in there!
Since one of our top goals is to avoid inducing GC, a better implementation is warranted. So let's go over what we absolutely cannot have in the implementation:
ArrayList<any boxed type>
Scanner
, StringTokenizer
, etc. because they are laden with state and new
.Pattern
or anything related to Regex for similar reasons.That's pretty strict, but not actually. Within a line of input, there are only two delimiters:
Given the simple tests involved, a straightforward scan-ahead-until-separator function is employed. Using the start and one-after-end that this function returns, are a perfect match for passing to String.substring()
to get the necessary string. This may seem like extra work, but it avoids any superfluous string creation by any of our friends up in the other list.
So the actual parsing part of it is now covered without any "parser" overhead, we can look at how are we going to store all of these floats?
Well, that's pretty obvious: we use float[]
. But we use it in a smart way: with a page-oriented wrapper class that handles array re-sizes when we are about to overflow. We use initial capacity and expand-by in the constructor, and call its put()
method to append 2/3/4 tuples into the array.
So that's good for getting the raw data staged up. Recall there may be Position, Normal, Texture coordinates in the OBJ file, and normals and textures are optional. Now comes the fun part: the Faces.
The final "stage" of the OBJ file contains the 3-tuples for triangles or 4-tuples for quads; we only consider triangles here. A quad can be decomposed; good luck maintaining winding.
Let's consider why we are loading this data: to create Vertex Attribute Arrays for OpenGL! We are going with the "interleaved" format, where all of a vertex's data is stored in one "entry" of contiguous floats, of a specific total size, or "stride".
Given all of the setup above, the a face is one, two, or three indices into the corresponding arrays. They are one-based, so "1/1/1" would refer to the first vertex in each "page", in order Position, Texture, Normal. If there are no Texture, then "1//1" and if there is only Position, then "1".
The parser builds an AGE InterleavedVertexGeometry
instance based on traversing the faces, and creating the vertex attributes according to the above indices. This in turn can be used in one or more DrawableGameObject
GOs with varying materials, most likely TextureMaterial
. There is no color information directly generated in OBJ files, and the IVG won't contain any.
Notice we are also avoiding using IndexedVertexGeometry
since this format has some limitations, mostly that complicated models don't benefit much, since all the attributes must match the same index, and this would require much "shuffling" or re-staging to get everything to "line-up". We decided it's easier to just "unwind" the faces directly.
These changes are upcoming, along with completing the texturing implementation. In AGE, texturing consists of:
Texture
(GL Resource)TextureMaterial
(for use in rendering, obtains a Texture
)Currently you can make textures from drawable resources in your APK, but obviously you can get a Bitmap
from many sources!
So there it is! Starting exporting your models now!