yet another .obj loader.

just another .obj parser.

disclaimer
there are at least 3 bugs and 27 typos in the code.

it’s ment to be a starting point, not a super finished tool. it works tho :wink:

supports faces, quads, normals and texture-coords 1/2/3D. no smoothing groups, lines, materials etc. (tex-coords not tested by me)

here’s a simple .obj file:

# a geodesic sphere
#

v  0.0000000000 0.0000000000 10.0000000000
v  8.9442720413 0.0000000000 4.4721360207
v  2.7639317513 8.5065088272 4.4721360207
v  -7.2360687256 5.2573103905 4.4721360207
v  -7.2360677719 -5.2573122978 4.4721360207
v  2.7639331818 -8.5065078735 4.4721360207
v  7.2360677719 -5.2573118210 -4.4721360207
v  7.2360682487 5.2573113441 -4.4721360207
v  -2.7639324665 8.5065078735 -4.4721360207
v  -8.9442720413 -0.0000007819 -4.4721360207
v  -2.7639288902 -8.5065088272 -4.4721360207
v  0.0000000000 0.0000000000 -10.0000000000
# 12 vertices

vt 0.5999999642 1.0000000000 10.0000000000
vt 0.5000000000 0.6475836635 10.0000000000
vt 0.6999999285 0.6475835443 10.0000009537
vt 0.7999999523 1.0000000000 10.0000000000
vt 0.9000000358 0.6475836635 10.0000000000
vt 0.0000000373 1.0000000000 10.0000000000
vt -0.0999999642 0.6475836635 10.0000000000
vt 0.1000000387 0.6475836635 10.0000000000
vt 0.2000000179 1.0000000000 10.0000000000
vt 0.3000000119 0.6475836635 10.0000000000
vt 0.4000000060 1.0000000000 10.0000000000
vt 0.4000000060 0.3524163961 10.0000000000
vt 0.5999999642 0.3524163961 10.0000000000
vt 0.8000000119 0.3524163961 10.0000000000
vt -0.1999999881 0.3524163961 10.0000000000
vt 0.0000000379 0.3524163961 10.0000000000
vt 0.2000000477 0.3524163961 10.0000000000
vt 0.6999999881 0.0000000000 10.0000000000
vt -0.0999999717 0.0000000000 10.0000000000
vt 0.1000000462 0.0000000000 10.0000000000
vt 0.3000000119 0.0000000000 10.0000000000
vt 0.5000000000 0.0000000000 10.0000000000
# 22 texture coords

g sphere
f 1/1 2/2 3/3 
f 1/4 3/3 4/5 
f 1/6 4/7 5/8 
f 1/9 5/8 6/10 
f 1/11 6/10 2/2 
f 2/2 7/12 8/13 
f 3/3 8/13 9/14 
f 4/7 9/15 10/16 
f 5/8 10/16 11/17 
f 6/10 11/17 7/12 
f 8/13 3/3 2/2 
f 9/14 4/5 3/3 
f 10/16 5/8 4/7 
f 11/17 6/10 5/8 
f 7/12 2/2 6/10 
f 12/18 9/14 8/13 
f 12/19 10/16 9/15 
f 12/20 11/17 10/16 
f 12/21 7/12 11/17 
f 12/22 8/13 7/12 

(save it to some.obj)

looks like this …

since it’s abstract, usage would be at least :

obj = new Simple[your_implementation]ObjFile();
obj.load("meshes/some.obj");

a minimum no-op adapter could look like :

public class SimpleOBJFileAdapter extends SimpleOBJFile
{
  // vectors

  public void vertex(float x, float y, float z) {}
  public void normal(float x, float y, float z) {}

  // these three should be indexed into the same list.
  public void textureCoord(float s) {}
  public void textureCoord(float x, float y){}
  public void textureCoord(float x, float y, float z) {}

  // indicies

  // these 3 functions are grouped.
  // if tex-coords or normals exists for the face,
  // they're called first - followd by the actual face.
  public void face(int v_a, int v_b, int v_c) {}
  public void faceTextureCoords(int vt_a, int vt_b, int vt_c) {}
  public void faceNormal(int vn_a, int vn_b, int vn_c) {}

  public void quad(int v_a, int v_b, int v_c, int v_d) {}
  public void quadTextureCoords(int vt_a, int vt_b, int vt_c, int vt_d) { }
  public void quadNormal(int vn_a, int vn_b, int vn_c, int vn_d) {}

  //
  public void comment(String comment) {}
}

the abstract parser. whatever should happen to the data is up to the user :

package net.highteq.lws.core;

//~--- JDK imports ------------------------------------------------------------

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.regex.Pattern;

/**
 * spec as described here : http://www.martinreddy.net/gfx/3d/OBJ.spec.
 *
 * ment to be a starting point.
 *
 * NOT implementing all features, missing smoothing groups, lines, materials and more.
 *
 * @version        1.0, 2015.01.17
 * @author         bas
 */
@SuppressWarnings ( { "UseOfSystemOutOrSystemErr","DesignForExtension" } )
public abstract class SimpleOBJFile
{
  //~--- static fields --------------------------------------------------------

  private final static Pattern slashPattern = Pattern.compile("/");
  private final static Pattern spacePattern = Pattern.compile(" ");

  //~--- fields ---------------------------------------------------------------

  private boolean flipYZ;

  //~--- constructors ---------------------------------------------------------

  /**
   */
  public SimpleOBJFile()
  {
    this(false);
  }


  /**
   * @param flipVertexYZ boolean - change if the object is rotated by 90 deg.
   */
  public SimpleOBJFile(boolean flipVertexYZ)
  {
    this.flipYZ = flipVertexYZ;
  }


  //~--- methods --------------------------------------------------------------

  // indices

  /**
   * @param x float
   * @param y float
   * @param z float
   */
  public abstract void vertex(float x,float y,float z);

  /**
   * 1D texture coords
   *
   * @param s float
   */
  public abstract void textureCoord(float s);

  /**
   * 2D texture coords
   *
   * @param x float
   * @param y float
   */
  public abstract void textureCoord(float x,float y);

  /**
   * 3D texture coords
   *
   * @param x float
   * @param y float
   * @param z float
   */
  public abstract void textureCoord(float x,float y,float z);

  /**
   * @param x float
   * @param y float
   * @param z float
   */
  public abstract void normal(float x,float y,float z);

  // faces

  /**
   * plain face without normals or texture-coords
   *
   * @param v_a int
   * @param v_b int
   * @param v_c int
   */
  public abstract void face(int v_a,int v_b,int v_c);

  /**
   * @param vt_a int
   * @param vt_b int
   * @param vt_c int
   */
  public abstract void faceTextureCoords(int vt_a,int vt_b,int vt_c);

  /**
   * @param vn_a int
   * @param vn_b int
   * @param vn_c int
   */
  public abstract void faceNormal(int vn_a,int vn_b,int vn_c);

  //
  // quads
  //

  /**
   * @param v_a int
   * @param v_b int
   * @param v_c int
   * @param v_d int
   */
  public abstract void quad(int v_a,int v_b,int v_c,int v_d);

  /**
   * @param vt_a int
   * @param vt_b int
   * @param vt_c int
   * @param vt_d int
   */
  public abstract void quadTextureCoords(int vt_a,int vt_b,int vt_c,int vt_d);

  /**
   * @param vn_a int
   * @param vn_b int
   * @param vn_c int
   * @param vn_d int
   */
  public abstract void quadNormal(int vn_a,int vn_b,int vn_c,int vn_d);

  /**
   * @param comment String
   */
  public abstract void comment(String comment);

  /**
   * change these to match your logging tools. here it's just a STD out.
   *
   * @param ex Throwable
   * @param text String
   */
  @SuppressWarnings ( "MethodMayBeStatic" )
  protected void handleInfo(Throwable ex,String text)
  {
    if(text != null) System.out.println(text);
    if(ex != null) ex.printStackTrace(System.out);
  }

  /**
   * @param ex Throwable
   * @param text String
   */
  @SuppressWarnings ( "MethodMayBeStatic" )
  protected void handleError(Throwable ex,String text)
  {
    if(text != null) System.err.println(text);
    if(ex != null) ex.printStackTrace(System.err);
  }

  /**
   * @param chr char
   * @param line String
   */
  private void unhandled(char chr,String line)
  {
    handleInfo(null,"unhandled type : " + chr + " line : " + line);
  }

  /**
   * @param file String
   */
  public final void load(String file)
  {
    try
    {
      load(Files.newInputStream(new File(file).toPath()));
    }
    catch(final Exception ex)
    {
      handleError(ex,"file " + file + " not loaded.");
    }
  }

  /**
   * @param from InputStream
   */
  public final void load(InputStream from)
  {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(from,"utf-8")))
    {
      String        line;
      final float[] floats3 = new float[3];
      final int[]   v       = new int[4];
      final int[]   vt      = new int[4];
      final int[]   vn      = new int[4];
      int           lineNo  = 0;

      while(( line = reader.readLine() ) != null)
      {
        ++lineNo;

        try
        {
          if(!line.isEmpty())
          {
            final char chr = line.charAt(0);

            switch(chr)
            {
              case '#' :                                   // comment
              {
                final int    idx     = line.length() > 1 ? Character.isWhitespace(line.charAt(1)) ? 2 : 1 : 1;
                final String comment = line.substring(idx);

                if(!comment.isEmpty()) comment(comment);
                break;
              }
              case 'g' :                                   // group, ignored
              {
                break;
              }
              case 'v' :                                   // vertex
              {
                final char t = line.charAt(1);

                if(Character.isWhitespace(t))
                {
                  final String[] s = splitSpace(line);
                  final float    a = Float.parseFloat(s[s.length - 3]);
                  final float    b = Float.parseFloat(s[s.length - 2]);
                  final float    c = Float.parseFloat(s[s.length - 1]);

                  if(flipYZ) vertex(a,-c,b);
                  else vertex(a,b,c);
                }
                else if(t == 'n')                          // vn normal
                {
                  final int vertexDimensions = parseFloats(line,floats3);

                  if(vertexDimensions != 3)
                    handleError(new IllegalArgumentException(),"normal vector is off : num : " + vertexDimensions + " values : " + Arrays.toString(floats3) + " line :" + lineNo);
                  else normal(floats3[0],floats3[1],floats3[2]);
                }
                else if(t == 't')                          // vt, texture coords
                {
                  final int numTextureCoords = parseFloats(line,floats3);

                  switch(numTextureCoords)
                  {
                    case 1 :
                      textureCoord(floats3[0]);
                      break;
                    case 2 :
                      textureCoord(floats3[0],floats3[1]);
                      break;
                    case 3 :
                      textureCoord(floats3[0],floats3[1],floats3[2]);
                      break;
                    default :
                      handleError(new IllegalArgumentException(),"texture coords are off : num : " + numTextureCoords + " values : " + Arrays.toString(floats3) + " line :" + lineNo);
                  }
                }
                else
                {
                  handleInfo(new IllegalArgumentException(),"unhandled vertex data : 2nd char : " + t + " line : " + line);
                }

                break;
              }
              case 'f' :                                   // face
              {
                final String[] s          = splitSpace(line);
                final boolean  isQuad     = s.length > 4;  // some exporters support 4-vertex-faces.
                boolean        hasVT      = false;
                boolean        hasVN      = false;
                final int      numIndices = isQuad ? 4 : 3;

                for(int i = 0; i < numIndices; i++)
                {
                  final String[] e = splitSlash(s[i + 1]);

                  v[i] = Integer.parseInt(e[0]) - 1;

                  if(e.length > 1 && !e[1].isEmpty())
                  {
                    hasVT = true;
                    vt[i] = Integer.parseInt(e[1]) - 1;
                  }

                  if(e.length > 2 && !e[2].isEmpty())
                  {
                    hasVN = true;
                    vn[i] = Integer.parseInt(e[2]) - 1;
                  }
                }

                if(isQuad)
                {
                  if(hasVT) quadTextureCoords(vt[0],vt[1],vt[2],vt[3]);
                  if(hasVN) quadNormal(vn[0],vn[1],vn[2],vn[3]);

                  quad(v[0],v[1],v[2],v[3]);
                }
                else
                {
                  if(hasVT) faceTextureCoords(vt[0],vt[1],vt[2]);
                  if(hasVN) faceNormal(vn[0],vn[1],vn[2]);

                  face(v[0],v[1],v[2]);
                }

                break;
              }
              case 's' :                                   // smoothing group, ignored.
              {
                break;
              }
              case 'u' :                                   // usemtl, .. anything starting with u, ignored.
              {
                break;
              }
              default :
                unhandled(chr,line);                       // pass out something to debug.
                break;
            }
          }
        }
        catch(final Exception ex)
        {
          handleError(ex,"line : " + line);
          break;                                           // parsing errors. uncomment this if you want to continue even with errors.
        }
      }
    }
    catch(final Exception ex)
    {
      handleError(ex,null);                                // IO errors.
    }
  }

  /**
   * @param input CharSequence
   * @return String[]
   */
  private static String[] splitSlash(CharSequence input)
  {
    return slashPattern.split(input,0);
  }

  /**
   * @param input CharSequence
   * @return String[]
   */
  private static String[] splitSpace(CharSequence input)
  {
    return spacePattern.split(input,0);
  }

  /**
   * @param line String
   * @param out float[]
   * @return int
   */
  @SuppressWarnings ( "ValueOfIncrementOrDecrementUsed" )
  private static int parseFloats(CharSequence line,float[] out)
  {
    final String[] p   = splitSpace(line);
    int            idx = 0;

    for(int i = 1; i < p.length; i++)
    {
      if(!p[i].isEmpty()) out[idx++] = Float.parseFloat(p[i]);
    }

    return idx;
  }
}

… here’s a very simple mesh class based on array lists implementing the thing above :

package net.highteq.lws.core;

//~--- JDK imports ------------------------------------------------------------

import java.util.ArrayList;

/**
 * very plain mesh class feeded by a .obj file.
 *
 * @version        1.0, 2015.01.17
 * @author         bas
 */
@SuppressWarnings ( "DesignForExtension" )
public class SimpleArrayObjFile extends SimpleOBJFile
{
  //~--- fields ---------------------------------------------------------------

  /**  */
  public ArrayList<vec3>  vertices          = null;
  public ArrayList<vec2>  textureCoords     = null;
  public ArrayList<vec3>  normals           = null;

  /**  */
  public ArrayList<ivec3> faces             = null;
  public ArrayList<ivec3> faceNormals       = null;
  public ArrayList<ivec3> faceTextureCoords = null;
  public ArrayList<ivec4> quads             = null;
  public ArrayList<ivec4> quadNormals       = null;
  public ArrayList<ivec4> quadTextureCoords = null;

  //~--- methods --------------------------------------------------------------

  /**
   * @param x float
   * @param y float
   * @param z float
   */
  @Override public final void vertex(float x,float y,float z)
  {
    if(vertices == null) vertices = new ArrayList<>(30);

    vertices.add(new vec3(x,y,z));
  }

  /**
   * @param s float
   */
  @Override public void textureCoord(float s)
  {
    // not supporting here.
  }

  /**
   * @param x float
   * @param y float
   */
  @Override public final void textureCoord(float x,float y)
  {
    if(textureCoords == null) textureCoords = new ArrayList<>(30);

    textureCoords.add(new vec2(x,y));
  }

  /**
   * @param x float
   * @param y float
   * @param z float
   */
  @Override public void textureCoord(float x,float y,float z)
  {
    // not supporting here.
  }

  /**
   * @param x float
   * @param y float
   * @param z float
   */
  @Override public final void normal(float x,float y,float z)
  {
    if(normals == null) normals = new ArrayList<>(30);

    normals.add(new vec3(x,y,z));
  }

  //
  //
  //

  /**
   * @param v_a int
   * @param v_b int
   * @param v_c int
   */
  @Override public final void face(int v_a,int v_b,int v_c)
  {
    if(faces == null) faces = new ArrayList<>(30);

    faces.add(new ivec3(v_a,v_b,v_c));
  }

  /**
   * @param vt_a int
   * @param vt_b int
   * @param vt_c int
   */
  @Override public final void faceTextureCoords(int vt_a,int vt_b,int vt_c)
  {
    if(faceTextureCoords == null) faceTextureCoords = new ArrayList<>(30);

    faceTextureCoords.add(new ivec3(vt_a,vt_b,vt_c));
  }

  /**
   * @param vn_a int
   * @param vn_b int
   * @param vn_c int
   */
  @Override public final void faceNormal(int vn_a,int vn_b,int vn_c)
  {
    if(faceNormals == null) faceNormals = new ArrayList<>(30);

    faceNormals.add(new ivec3(vn_a,vn_b,vn_c));
  }

  //
  //
  //

  /**
   * @param v_a int
   * @param v_b int
   * @param v_c int
   * @param v_d int
   */
  @Override public final void quad(int v_a,int v_b,int v_c,int v_d)
  {
    if(quads == null) quads = new ArrayList<>(30);

    quads.add(new ivec4(v_a,v_b,v_c,v_d));
  }

  /**
   * @param vt_a int
   * @param vt_b int
   * @param vt_c int
   * @param vt_d int
   */
  @Override public final void quadTextureCoords(int vt_a,int vt_b,int vt_c,int vt_d)
  {
    if(quadTextureCoords == null) quadTextureCoords = new ArrayList<>(30);

    quadTextureCoords.add(new ivec4(vt_a,vt_b,vt_c,vt_d));
  }

  /**
   * @param vn_a int
   * @param vn_b int
   * @param vn_c int
   * @param vn_d int
   */
  @Override public final void quadNormal(int vn_a,int vn_b,int vn_c,int vn_d)
  {
    if(quadNormals == null) quadNormals = new ArrayList<>(30);

    quadNormals.add(new ivec4(vn_a,vn_b,vn_c,vn_d));
  }

  /**
   * @param comment String
   */
  @Override public final void comment(String comment)
  {
    handleInfo(null,"comment : " + comment);
  }

  /**
   */
  public void dump()
  {
    handleInfo(null,"num vertices : " + vertices.size());
    handleInfo(null,"num tex-coords : " + ( textureCoords == null ? 0 : textureCoords.size() ));
    handleInfo(null,"num normals : " + ( normals == null ? 0 : normals.size() ));
    handleInfo(null,"num faces : " + ( faces == null ? 0 : faces.size() ));
    handleInfo(null,"num quads : " + ( quads == null ? 0 : quads.size() ));
  }

  /**
   */
  public void clear()
  {
    vertices          = null;
    textureCoords     = null;
    normals           = null;
    faces             = null;
    faceTextureCoords = null;
    faceNormals       = null;
    quads             = null;
    quadTextureCoords = null;
    quadNormals       = null;
  }

  //
  // i guess you'd like to use your own vec-math-lib instead. here's a basic version.
  //

  /**
   * @version        1.0, 2015.01.17
   * @author         bas
   */
  public final static class vec3
  {
    public float x,y,z;

    /**
     */
    public vec3(){}


    /**
     * @param x float
     * @param y float
     * @param z float
     */
    public vec3(float x,float y,float z)
    {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  }


  /**
   * @version        1.0, 2015.01.17
   * @author         bas
   */
  public final static class ivec3
  {
    public int x,y,z;

    /**
     */
    public ivec3(){}


    /**
     * (non-Javadoc)
     * @param x int
     * @param y int
     * @param z int
     */
    public ivec3(int x,int y,int z)
    {
      this.x = x;
      this.y = y;
      this.z = z;
    }
  }


  /**
   * @version        1.0, 2015.01.17
   * @author         bas
   */
  public final static class ivec4
  {
    public int x,y,z,w;

    /**
     */
    public ivec4(){}


    /**
     * @param x int
     * @param y int
     * @param z int
     * @param w int
     */
    public ivec4(int x,int y,int z,int w)
    {
      this.x = x;
      this.y = y;
      this.z = z;
      this.w = w;
    }
  }


  /**
   * @version        1.0, 2015.01.17
   * @author         bas
   */
  public final static class vec2
  {
    public float x,y,z;

    /**
     */
    public vec2(){}


    /**
     * @param x float
     * @param y float
     */
    public vec2(float x,float y)
    {
      this.x = x;
      this.y = y;
    }
  }
}

to get all this rolling here are some examples how we could use this :

this is based on immediate mode, just to be explanatory. compiling a VBO “thing” out of this should be the next step.

public static class SimpleObjFileTools
{
   /**
   * assuming blending and color is set up before.
   *
   * @param obj SimpleArrayObjFile
   */
  public static void drawPoints(SimpleArrayObjFile obj)
  {
    GL11.glBegin(GL11.GL_POINTS);

    for(int i = 0; i < obj.vertices.size(); i++)
    {
      final SimpleArrayObjFile.vec3 v = obj.vertices.get(i);

      GL11.glVertex3f(v.x,v.y,v.z);
    }

    GL11.glEnd();
  }

  /**
   * @param obj SimpleArrayObjFile
   */
  public static void drawFaces(SimpleArrayObjFile obj)
  {
    if(obj.faces == null) return;
    if(obj.faceNormals == null) return;

    GL11.glBegin(GL11.GL_TRIANGLES);

    for(int i = 0; i < obj.faces.size(); i++)
    {
      final SimpleArrayObjFile.ivec3 face       = obj.faces.get(i);
      final SimpleArrayObjFile.ivec3 faceNormal = obj.faceNormals.get(i);
      //
      final SimpleArrayObjFile.vec3  v_a        = obj.vertices.get(face.x);
      final SimpleArrayObjFile.vec3  v_b        = obj.vertices.get(face.y);
      final SimpleArrayObjFile.vec3  v_c        = obj.vertices.get(face.z);
      //
      final SimpleArrayObjFile.vec3  vn_a       = obj.normals.get(faceNormal.x);
      final SimpleArrayObjFile.vec3  vn_b       = obj.normals.get(faceNormal.y);
      final SimpleArrayObjFile.vec3  vn_c       = obj.normals.get(faceNormal.z);

      setColorFromNormal(vn_a);
      GL11.glVertex3f(v_a.x,v_a.y,v_a.z);
      setColorFromNormal(vn_b);
      GL11.glVertex3f(v_b.x,v_b.y,v_b.z);
      setColorFromNormal(vn_c);
      GL11.glVertex3f(v_c.x,v_c.y,v_c.z);
    }

    GL11.glEnd();
  }


  /**
   * and the quads version.
   *
   * @param obj SimpleArrayObjFile
   */
  public static void drawQuads(SimpleArrayObjFile obj)
  {
    if(obj.quads == null) return;
    if(obj.quadNormals == null) return;

    GL11.glBegin(GL11.GL_TRIANGLES);

    for(int i = 0; i < obj.quads.size(); i++)
    {
      final SimpleArrayObjFile.ivec4 quad       = obj.quads.get(i);
      final SimpleArrayObjFile.ivec4 quadNormal = obj.quadNormals.get(i);
      //
      final SimpleArrayObjFile.vec3  v_a        = obj.vertices.get(quad.x);
      final SimpleArrayObjFile.vec3  v_b        = obj.vertices.get(quad.y);
      final SimpleArrayObjFile.vec3  v_c        = obj.vertices.get(quad.z);
      final SimpleArrayObjFile.vec3  v_d        = obj.vertices.get(quad.w);
      //
      final SimpleArrayObjFile.vec3  vn_a       = obj.normals.get(quadNormal.x);
      final SimpleArrayObjFile.vec3  vn_b       = obj.normals.get(quadNormal.y);
      final SimpleArrayObjFile.vec3  vn_c       = obj.normals.get(quadNormal.z);
      final SimpleArrayObjFile.vec3  vn_d       = obj.normals.get(quadNormal.w);

      //
      setColorFromNormal(vn_a);
      GL11.glVertex3f(v_a.x,v_a.y,v_a.z);
      setColorFromNormal(vn_b);
      GL11.glVertex3f(v_b.x,v_b.y,v_b.z);
      setColorFromNormal(vn_c);
      GL11.glVertex3f(v_c.x,v_c.y,v_c.z);
      //
      setColorFromNormal(vn_a);
      GL11.glVertex3f(v_a.x,v_a.y,v_a.z);
      setColorFromNormal(vn_c);
      GL11.glVertex3f(v_c.x,v_c.y,v_c.z);
      setColorFromNormal(vn_d);
      GL11.glVertex3f(v_d.x,v_d.y,v_d.z);
    }

    GL11.glEnd();
  }

  /**
   * @param normal SimpleArrayObjFile.vec3
   */
  public static void setColorFromNormal(SimpleArrayObjFile.vec3 normal)
  {
    final float r = normal.x*0.5f + 0.5f;
    final float g = normal.y*0.5f + 0.5f;
    final float b = normal.z*0.5f + 0.5f;
//  final float r = Math.abs(normal.x);
//  final float g = Math.abs(normal.y);
//  final float b = Math.abs(normal.z);

    GL11.glColor4f(r,g,b,1.0f);
  }
}

with that you could do something like :

// somewhere around init :
obj = new SimpleArrayObjFile();
obj.load("meshes/some.obj");
obj.dump();

// somewhere in the render loop :

// normal bleding, depth tested

GL11.glPointSize(3.0f);
GL11.glLineWidth(2.0f);
GL11.glColor4f(0.5f,0.5f,0.5f,0.25f);
SimpleObjFileTools.drawFaces(obj);
SimpleObjFileTools.drawQuads(obj);
GL11.glColor4f(0.1f,0.1f,0.1f,1.0f);
SimpleObjFileTools.drawPoints(obj);


… if we dont have any normals going on, add these to the tools :

  /**
   * ignoring quads
   *
   * @param obj SimpleArrayObjFile
   */
  public static void computeFaceNormals(SimpleArrayObjFile obj)
  {
    obj.normals = new ArrayList<>(30);

    if(obj.faces != null)
    {
      obj.faceNormals = new ArrayList<>(30);

      final float FLOAT_ERROR = 0.00000001f;

      for(int i = 0; i < obj.faces.size(); i++)
      {
        final SimpleArrayObjFile.ivec3 face         = obj.faces.get(i);
        final SimpleArrayObjFile.vec3  v_a          = obj.vertices.get(face.x);
        final SimpleArrayObjFile.vec3  v_b          = obj.vertices.get(face.y);
        final SimpleArrayObjFile.vec3  v_c          = obj.vertices.get(face.z);
        final SimpleArrayObjFile.vec3  normal       = computeNormal(v_a,v_b,v_c);

        /**  */
        int                            normal_index = -1;

        /** test if normal is already indexed : */
        for(int j = 0; j < obj.normals.size(); j++)
        {
          final SimpleArrayObjFile.vec3 test = obj.normals.get(j);

          if(Math.abs(test.x - normal.x) < FLOAT_ERROR && Math.abs(test.y - normal.y) < FLOAT_ERROR && Math.abs(test.z - normal.z) < FLOAT_ERROR)
          {
            normal_index = j;
            break;
          }
        }

        if(normal_index == -1)
        {
          normal_index = obj.normals.size();
          obj.normals.add(normal);
        }

        obj.faceNormals.add(new SimpleArrayObjFile.ivec3(normal_index,normal_index,normal_index));  // all 3 vertices get the same normal
      }
    }
  }

  /**
   * @param a SimpleArrayObjFile.vec3
   * @param b SimpleArrayObjFile.vec3
   * @param c SimpleArrayObjFile.vec3
   * @return SimpleArrayObjFile.vec3
   */
  @SuppressWarnings ( "NumericCastThatLosesPrecision" )
  public static SimpleArrayObjFile.vec3 computeNormal(SimpleArrayObjFile.vec3 a,SimpleArrayObjFile.vec3 b,SimpleArrayObjFile.vec3 c)
  {
    final SimpleArrayObjFile.vec3 normal = new SimpleArrayObjFile.vec3();

    /**  */
    final float                   x1     = a.x - b.x;
    final float                   y1     = a.y - b.y;
    final float                   z1     = a.z - b.z;
    final float                   x2     = a.x - c.x;
    final float                   y2     = a.y - c.y;
    final float                   z2     = a.z - c.z;

    // cross xyz1 with xyz2
    normal.x = y1*z2 - y2*z1;
    normal.y = z1*x2 - z2*x1;
    normal.z = x1*y2 - x2*y1;

    final float magSquared = normal.x*normal.x + normal.y*normal.y + normal.z*normal.z;

    if(magSquared > 0.0f)
    {
      final float mag = (float)( 1.0/Math.sqrt(magSquared) );

      normal.x *= mag;
      normal.y *= mag;
      normal.z *= mag;
    }

    return normal;
  }

so now the usage could be :

obj = new SimpleArrayObjFile();
obj.load("meshes/come.obj");
SimpleObjFileTools.computeFaceNormals(obj);
obj.dump();

one last thing, sometimes everytime you load some object file, it’s either translated to somewhere way off, in a scale which does not fit your renderings or both.

so here’s a little tool to center and rescale any .obj to get a good starting point. add it to the tool-set :

  /**
   * @param obj SimpleArrayObjFile
   * @return float[]
   */
  public static SimpleArrayObjFile.vec3[] computeBounds(SimpleArrayObjFile obj)
  {
    if(obj.vertices == null || obj.vertices.size() < 2) throw new IllegalStateException();

    final SimpleArrayObjFile.vec3 firstVertex = obj.vertices.get(0);
    final SimpleArrayObjFile.vec3 min         = new SimpleArrayObjFile.vec3(firstVertex.x,firstVertex.y,firstVertex.z);
    final SimpleArrayObjFile.vec3 max         = new SimpleArrayObjFile.vec3(firstVertex.x,firstVertex.y,firstVertex.z);

    for(int i = 1; i < obj.vertices.size(); i++)
    {
      final SimpleArrayObjFile.vec3 v = obj.vertices.get(i);

      if(v.x < min.x) min.x = v.x;
      if(v.y < min.y) min.y = v.y;
      if(v.z < min.z) min.z = v.z;
      if(v.x > max.x) max.x = v.x;
      if(v.y > max.y) max.y = v.y;
      if(v.z > max.z) max.z = v.z;
    }

    return new SimpleArrayObjFile.vec3[]{ min,max };
  }

  /**
   * @param a float
   * @param b float
   * @param c float
   * @return float
   */
  public static float max3(float a,float b,float c)
  {
    return( a > b ) ? (( a > c ) ? a : c ) : (( b > c ) ? b : c );
  }

  /**
   * @param obj SimpleArrayObjFile
   * @param normalizeTo float
   */
  public static void centerAndNormalize(SimpleArrayObjFile obj,float normalizeTo)
  {
    final SimpleArrayObjFile.vec3[] bounds = computeBounds(obj);
    final SimpleArrayObjFile.vec3   center = new SimpleArrayObjFile.vec3();

    center.x = ( bounds[0].x + bounds[1].x )*0.5f;
    center.y = ( bounds[0].y + bounds[1].y )*0.5f;
    center.z = ( bounds[0].z + bounds[1].z )*0.5f;

    final float w           = bounds[1].x - bounds[0].x;  // max - min
    final float h           = bounds[1].y - bounds[0].y;
    final float d           = bounds[1].z - bounds[0].z;
    final float longestEdge = max3(w,h,d);
    final float scale       = normalizeTo/longestEdge;

    for(int i = 0; i < obj.vertices.size(); i++)
    {
      final SimpleArrayObjFile.vec3 v = obj.vertices.get(i);

      v.x -= center.x;
      v.y -= center.y;
      v.z -= center.z;
      v.x *= scale;
      v.y *= scale;
      v.z *= scale;
    }
  }

:

obj = new SimpleArrayObjFile();
obj.load("meshes/some.obj");
SimpleObjFileTools.computeFaceNormals(obj);
obj.dump();
SimpleObjFileTools.centerAndNormalize(obj,500.0f); // 500.0f is the desired size here

//

SimpleObjFileTools.drawFaces(obj)

// win

i think you can start converting to a VBO just by looking on the immediate mode drawings.

o/