Android Studio (java) OpenGL ES 3D Model in STL Format

142 views Asked by At

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?

1

There are 1 answers

0
Timmy Chan On

You are binding the array to the vertexAttribPointer the wrong way. Here is your code.

GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, --> vertexBuffer <--);

The vertexBuffer should be bound using glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer)

And the last parameter in glVertexAttribPointer is actually meant for offset.


Fix:

Assuming offset = 0. Fixed version would be:

GLES20.glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer)
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, 0);

Now the call to GLES20.glDrawArrays in your OnDrawFrame() should be able to access the buffer.