I'm having difficulties in displaying the 3D mesh model using the STL (binary) file I have in my Android Studio project. The below is my current code for the 3D model visualization activity.
package com.example.structurefrommotion;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MeshVisualizationActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isSupportedOpenGLES2()) {
// Log the result
Log.e("OpenGLCheck", "OpenGL ES 2.0 is not supported on this device");
// Optionally, display a message to the user or handle this case in another way.
return;
}
glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setEGLContextClientVersion(2); // Using OpenGL ES 2.0
glSurfaceView.setRenderer(new MeshRenderer());
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // Set render mode
setContentView(glSurfaceView);
}
private boolean isSupportedOpenGLES2() {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
return configurationInfo.reqGlEsVersion >= 0x20000;
}
private class MeshRenderer implements GLSurfaceView.Renderer {
private void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e("MeshVisualization", op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private int program;
private int numberOfVertices;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
checkGlError("glEnable depth test");
// Create OpenGL program
program = createProgram(vertexShaderCode, fragmentShaderCode);
checkGlError("create program");
GLES20.glUseProgram(program);
checkGlError("use program");
// Retrieve the model data from Firestore
String projectName = getIntent().getStringExtra("projectName");
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("meshes").document(projectName)
.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
@Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
final String meshDataB64 = documentSnapshot.getString("mesh_data");
// Log the retrieved base64 string to console
Log.d("MeshVisualization", "Retrieved mesh data (base64): " + meshDataB64);
// Decoding and OpenGL calls should be done on the GLSurfaceView's thread
glSurfaceView.queueEvent(new Runnable() {
@Override
public void run() {
byte[] meshDataBinary = android.util.Base64.decode(meshDataB64, android.util.Base64.DEFAULT);
// Log the size of the decoded binary data
Log.d("MeshVisualization", "Size of decoded binary data: " + meshDataBinary.length + " bytes");
List<Float> vertices = parseStlBinary(meshDataBinary);
numberOfVertices = vertices.size() / 3;
// Log the number of vertices parsed
Log.d("MeshVisualization", "Number of vertices parsed: " + numberOfVertices);
// Load vertices into a buffer
FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.size() * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
for (float vertex : vertices) {
vertexBuffer.put(vertex);
}
vertexBuffer.position(0);
// Pass the buffer to OpenGL
int positionHandle = GLES20.glGetAttribLocation(program, "vPosition");
// Check if the attribute location is -1
if (positionHandle == -1) {
Log.e("MeshVisualization", "Attribute 'vPosition' not found in the shader code or is not being used.");
return; // You can also handle this case differently if you like
}
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
// Set color for drawing the triangle
int colorHandle = GLES20.glGetUniformLocation(program, "vColor");
if (colorHandle == -1) {
Log.e("MeshVisualization", "Uniform 'vColor' not found in the shader code or is not being used.");
return; // You can also handle this case differently if you like
}
GLES20.glUniform4fv(colorHandle, 1, new float[]{0.63671875f, 0.76953125f, 0.22265625f, 1.0f}, 0);
}
});
}
});
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
checkGlError("glClear");
// Draw the triangles
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, numberOfVertices);
checkGlError("glDrawArrays");
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
private int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
// Check shader compilation status
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
String error = GLES20.glGetShaderInfoLog(shader);
GLES20.glDeleteShader(shader);
throw new RuntimeException("Shader not compiled: " + error);
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
// Check program linking status
int[] linked = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linked, 0);
if (linked[0] == 0) {
String error = GLES20.glGetProgramInfoLog(program);
GLES20.glDeleteProgram(program);
throw new RuntimeException("Program not linked: " + error);
}
return program;
}
private List<Float> parseStlBinary(byte[] meshDataBinary) {
List<Float> vertices = new ArrayList<>();
ByteBuffer buffer = ByteBuffer.wrap(meshDataBinary);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.position(84); // Skip 80-byte header and number of triangles (4 bytes)
while (buffer.remaining() > 0) {
// Skip normal (3 floats)
buffer.getFloat();
buffer.getFloat();
buffer.getFloat();
// Get vertices (3 floats per vertex, 3 vertices per triangle)
for (int i = 0; i < 9; i++) {
vertices.add(buffer.getFloat());
}
// Skip attribute byte count (2 bytes)
buffer.getShort();
}
return vertices;
}
}
}
After running the application and get to the activity file above, all I see is a black screen without the 3D mesh model displayed, they were no errors for this activity file as well. I have checked the data retrieved as well and it is all there and the vertices were parsed with no issue. Anyone knows what is preventing the 3D mesh model to be displayed?
You are binding the array to the vertexAttribPointer the wrong way. Here is your code.
The vertexBuffer should be bound using
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer)And the last parameter in
glVertexAttribPointeris actually meant foroffset.Fix:
Assuming offset = 0. Fixed version would be:
Now the call to
GLES20.glDrawArraysin yourOnDrawFrame()should be able to access the buffer.