Reputation: 8463
I am trying to load an .obj file into my Android application and display it using OpenGL 2.
You can find the file here: EDIT: I removed the file, you can use any .obj file that contains the values mentiones below for testing.
There are a lot of similar questions on stackoverflow but I did not find a simple solution that does not require some large library.
The file only contains the following value types:
I tried libgdx, which worked ok, but it is a bit overkill for what I need.
I tried the oObjLoader https://github.com/seanrowens/oObjLoader without the LWJGL. The parsing seems to work, but how can I display the values in a simple scene?
The next step is to attach an image as a texture to the object. But for now I would be happy to display the file as it is.
I am open to different solutions like pre-converting the file, because it will only be this one ever within the application.
Status update Basic loading and displaying works now, as shown in my own answer.
Upvotes: 19
Views: 21235
Reputation: 1
I'll expand upon user16386527's answer. Because in order for the texture to display properly on the object you need to make some changes. The code is in kotlin but converting to Java shoudln't be a problem.
val fileStr = "foo/model.obj"
val inputStream: InputStream = this.javaClass.classLoader.getResourceAsStream(fileStr) ?: throw IOException("Classpath resource not found")
val verticesTemp: MutableList<Float> = mutableListOf()
val normalsTemp: MutableList<Float> = mutableListOf()
val textureCoordsTemp: MutableList<Float> = mutableListOf()
val facesTemp: MutableList<String> = mutableListOf()
val sc = Scanner(inputStream)
while (sc.hasNext()) {
val ln = sc.nextLine()
val split = ln.split(" ")
when (split[0]) {
"v" -> {
verticesTemp.add(split[1].toFloat())
verticesTemp.add(split[2].toFloat())
verticesTemp.add(split[3].toFloat())
}
"vn" -> {
normalsTemp.add(split[1].toFloat())
normalsTemp.add(split[2].toFloat())
normalsTemp.add(split[3].toFloat())
}
"vt" -> {
textureCoordsTemp.add(split[1].toFloat())
textureCoordsTemp.add(-split[2].toFloat()) // Flip the texture if needed
}
"f" -> {
when (split.size) {
5 -> {
// triangle 1
facesTemp.add(split[1])
facesTemp.add(split[2])
facesTemp.add(split[3])
// triangle 2
facesTemp.add(split[1])
facesTemp.add(split[3])
facesTemp.add(split[4])
}
4 -> {
// triangle
facesTemp.add(split[1])
facesTemp.add(split[2])
facesTemp.add(split[3])
}
else -> {
println("[OBJ] Unknown face format: $ln")
}
}
}
}
sc.close()
val vertices = mutableListOf<Float>()
val normals = mutableListOf<Float>()
val textureCoords = mutableListOf<Float>()
val indices = mutableListOf<Short>()
for (face in facesTemp) {
val indicesArray = face.split(" ")
for (indexInfo in indicesArray) {
val index = indexInfo.split("/")
val vertexIndex = index[0].toInt() - 1
val textureIndex = index[1].toInt() - 1
val normalIndex = index[2].toInt() - 1
// Extract vertex position, texture coordinate, and normal data
vertices.add(verticesTemp[vertexIndex * 3])
vertices.add(verticesTemp[vertexIndex * 3 + 1])
vertices.add(verticesTemp[vertexIndex * 3 + 2])
textureCoords.add(textureCoordsTemp[textureIndex * 2])
textureCoords.add(textureCoordsTemp[textureIndex * 2 + 1])
normals.add(normalsTemp[normalIndex * 3])
normals.add(normalsTemp[normalIndex * 3 + 1])
normals.add(normalsTemp[normalIndex * 3 + 2])
// Add the index for the current vertex data
indices.add(indices.size.toShort())
}
}
// Convert mutable lists to arrays
val verticesArray = vertices.toFloatArray()
val normalsArray = normals.toFloatArray()
val textureCoordsArray = textureCoords.toFloatArray()
val indicesArray = indices.toShortArray()
Upvotes: 0
Reputation:
Try this code when you have 'obj' contents to get all Vertices
Indices
Normals
Texture Coords
I hope it will give some help here's the code:
void readObj(String objContents) {
final float[] vertices;
final float[] normals;
final float[] textureCoords;
final short[] indices;
Vector<Float> verticesTemp = new Vector<>();
Vector<Float> normalsTemp = new Vector<>();
Vector<Float> textureCoordsTemp = new Vector<>();
Vector<String> facesTemp = new Vector<>();
String[] lines = objContents.split("\n");
for (String line : lines) {
String[] parts = line.split(" ");
switch (parts[0]) {
case "v":
verticesTemp.add(Float.parseFloat(parts[1]));
verticesTemp.add(Float.parseFloat(parts[2]));
verticesTemp.add(Float.parseFloat(parts[3]));
break;
case "vn":
normalsTemp.add(Float.parseFloat(parts[1]));
normalsTemp.add(Float.parseFloat(parts[2]));
normalsTemp.add(Float.parseFloat(parts[3]));
break;
case "vt":
textureCoordsTemp.add(Float.parseFloat(parts[1]));
textureCoordsTemp.add(Float.parseFloat(parts[2]));
break;
case "f":
facesTemp.add(parts[1]);
facesTemp.add(parts[2]);
facesTemp.add(parts[3]);
break;
}
}
vertices = new float[verticesTemp.size()];
normals = new float[normalsTemp.size()];
textureCoords = new float[textureCoordsTemp.size()];
indices = new short[facesTemp.size()];
for (int i = 0, l = verticesTemp.size(); i < l; i++) {
vertices[i] = verticesTemp.get(i);
}
for (int i = 0, l = normalsTemp.size(); i < l; i++) {
normals[i] = normalsTemp.get(i);
}
for (int i = 0, l = textureCoordsTemp.size(); i < l; i++) {
textureCoords[i] = textureCoordsTemp.get(i);
}
for (int i = 0, l = facesTemp.size(); i < l; i++) {
indices[i] = (short) (Short.parseShort(facesTemp.get(i).split("/")[0]) - 1);
}
// now all vertices, normals, textureCoords and indices are ready
}
Upvotes: 0
Reputation: 51
Try with this project that found me on Github. https://github.com/WenlinMao/android-3d-model-viewer
This is a demo of OpenGL ES 2.0. It is an android application with a 3D engine that can load Wavefront OBJ, STL, DAE & glTF files. The application is based on andresoviedo's project which can be found here with an additional function of loading and rendering glTF format.
The purpose of this application is to learn and share how to draw using OpenGLES and Android. As this is my first android app, it is highly probable that there are bugs; but I will try to continue improving the app and adding more features.
This project is open source, and contain classes that can solve your problem!
Upvotes: 5
Reputation: 8463
I ended up writing a new parser, it can be used like this to build FloatBuffers to use in your Renderer:
ObjLoader objLoader = new ObjLoader(context, "Mug.obj");
numFaces = objLoader.numFaces;
// Initialize the buffers.
positions = ByteBuffer.allocateDirect(objLoader.positions.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
positions.put(objLoader.positions).position(0);
normals = ByteBuffer.allocateDirect(objLoader.normals.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
normals.put(objLoader.normals).position(0);
textureCoordinates = ByteBuffer.allocateDirect(objLoader.textureCoordinates.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
textureCoordinates.put(objLoader.textureCoordinates).position(0);
and here's the parser:
public final class ObjLoader {
public final int numFaces;
public final float[] normals;
public final float[] textureCoordinates;
public final float[] positions;
public ObjLoader(Context context, String file) {
Vector<Float> vertices = new Vector<>();
Vector<Float> normals = new Vector<>();
Vector<Float> textures = new Vector<>();
Vector<String> faces = new Vector<>();
BufferedReader reader = null;
try {
InputStreamReader in = new InputStreamReader(context.getAssets().open(file));
reader = new BufferedReader(in);
// read file until EOF
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(" ");
switch (parts[0]) {
case "v":
// vertices
vertices.add(Float.valueOf(parts[1]));
vertices.add(Float.valueOf(parts[2]));
vertices.add(Float.valueOf(parts[3]));
break;
case "vt":
// textures
textures.add(Float.valueOf(parts[1]));
textures.add(Float.valueOf(parts[2]));
break;
case "vn":
// normals
normals.add(Float.valueOf(parts[1]));
normals.add(Float.valueOf(parts[2]));
normals.add(Float.valueOf(parts[3]));
break;
case "f":
// faces: vertex/texture/normal
faces.add(parts[1]);
faces.add(parts[2]);
faces.add(parts[3]);
break;
}
}
} catch (IOException e) {
// cannot load or read file
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
//log the exception
}
}
}
numFaces = faces.size();
this.normals = new float[numFaces * 3];
textureCoordinates = new float[numFaces * 2];
positions = new float[numFaces * 3];
int positionIndex = 0;
int normalIndex = 0;
int textureIndex = 0;
for (String face : faces) {
String[] parts = face.split("/");
int index = 3 * (Short.valueOf(parts[0]) - 1);
positions[positionIndex++] = vertices.get(index++);
positions[positionIndex++] = vertices.get(index++);
positions[positionIndex++] = vertices.get(index);
index = 2 * (Short.valueOf(parts[1]) - 1);
textureCoordinates[normalIndex++] = textures.get(index++);
// NOTE: Bitmap gets y-inverted
textureCoordinates[normalIndex++] = 1 - textures.get(index);
index = 3 * (Short.valueOf(parts[2]) - 1);
this.normals[textureIndex++] = normals.get(index++);
this.normals[textureIndex++] = normals.get(index++);
this.normals[textureIndex++] = normals.get(index);
}
}
}
Upvotes: 11