Sunday, September 15, 2024

Sierpinki Gasket in an Android App

This project is a modification of an existing app I created earlier.

~TLDR: clone the repo https://github.com/bmkamath2000/mukBoApps and open in android studio to run. 

Upon creating an android project in android studio there was a MainActivity.java automatically created in the src folder.

I instinctively guessed that I needed to declare a MyGLSurfaceView which extends a GLSurfaceView.

My guess turned true as I got a surface in the app when I created a run configuration and

launched the app using it.

I had some experience with rudimentary android app development over several years in the past.

package com.example.mukesh.seirpinskigasketandroid;

import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import java.lang.Runnable;

public class MainActivity extends AppCompatActivity {
    private MyGLSurfaceView mGLView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLView = new MyGLSurfaceView(this);
        setContentView(mGLView);
    }
}

As you can see onCreate of the MainActivity the view is set as content view.

In MyGLSurfaceView

I created a MyGLRenderer instance which is derived from GLSurfaceView.Renderer.

package com.example.mukesh.seirpinskigasketandroid;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
import android.view.KeyEvent;

public class MyGLSurfaceView extends GLSurfaceView {
    private final MyGLRenderer mRenderer;
    //variable for storing the time of first click
    long startTime;
    public MyGLSurfaceView(Context context) {
        super(context);

        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRenderer();

        // Set the Renderer for drawing on the GLSurfaceView
        setRenderer(mRenderer);
        // Render the view only when there is a change in the drawing data
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        // MotionEvent reports input details from the touch screen
        // and other input controls. In this case, you are only
        // interested in events where the touch position changed.

        //constant for defining the time duration between the click that can be
        //considered as double-tap
        final int MAX_DURATION = 200;

        if (e.getAction() == MotionEvent.ACTION_DOWN) {

            startTime = System.currentTimeMillis();
        }
        else if (e.getAction() == MotionEvent.ACTION_UP) {

            if(System.currentTimeMillis() - startTime >= MAX_DURATION)
            {
                //DOUBLE TAP
                mRenderer.mTriangle.n++;
                requestRender();
            }
            else {
                //SINGLE TAP
                if(mRenderer.mTriangle.n>0) {
                    mRenderer.mTriangle.n--;
                    requestRender();
                }
            }
        }
        return true;
    }
}

The MyGLRenderer declares a Triangle and draws it onDrawFrame.

package com.example.mukesh.seirpinskigasketandroid;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView;


import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLRenderer implements GLSurfaceView.Renderer {
    public Triangle mTriangle;
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        mTriangle.draw();
    }
    public static int loadShader(int type, String shaderCode){

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}

I have Created the Triangle class which is based on the android official site:

https://developer.android.com/develop/ui/views/graphics/opengl/draw

package com.example.mukesh.seirpinskigasketandroid;

import android.opengl.GLES20;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class Triangle {
    private final int mProgram;
    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
            0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f  // bottom right
    };
    float v[][]={{-1.0f,-0.5f,0.0f},{1.0f,-0.5f,0.0f},
            {0.0f,1.0f,0.0f}};
    float colors[][]={{1.0f,0.0f,0.0f},{0.0f,1.0f,0.0f},{0.0f,0.0f,1.0f},
    {0.0f,0.0f,0.0f}};
    public int n=4, prevn=0;
    int vc1=0;
    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();
    }
    void triangle(FloatBuffer fb,float a[],float b[],float c[])
    {
        fb.put(a);
        fb.put(b);
        fb.put(c);
        vc1+=3;
    }
    public int findnoofnodes(int nt)
    {
        if(nt<=1)
            return 3;
        else return ((findnoofnodes(nt-1) * 3) - 3);
    }


    void divide_tetra(FloatBuffer fb,float a[],float b[],float c[],int m)
    {
        float v1[]=new float[3],v2[]=new float[3],v3[]=new float[3];
        int j;
        if(m>0)
        {    /*compute three midpoints*/
            for(j=0;j<3;j++)
                v1[j]=(a[j]+b[j])/2;

            for(j=0;j<3;j++)
                v2[j]=(a[j]+c[j])/2;

            for(j=0;j<3;j++)
                v3[j]=(c[j]+b[j])/2;

            divide_tetra(fb,a,v2,v1,m-1);
            divide_tetra(fb,c,v3,v2,m-1);
            divide_tetra(fb,b,v1,v3,m-1);

        }
        else
            triangle(fb, a, b, c);      //draw triangle at end of recursion//
    }


    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 mPositionHandle;
    private int mColorHandle;

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX *4; // 4 bytes per vertex
    public void PopulateVBO()
    {
        final int nodesN = findnoofnodes(n+2);
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (number of coordinate values * 4 bytes per float)
                (nodesN* 16 ) +100
                //90000
        );
        // use the device hardware's native byte order
        bb.order(ByteOrder.nativeOrder());

        // create a floating point buffer from the ByteBuffer
        vertexBuffer = bb.asFloatBuffer();
        // add the coordinates to the FloatBuffer
        //vertexBuffer.put(triangleCoords);
        // set the buffer to read the first coordinate
        vc1=0;
        divide_tetra(vertexBuffer,v[0],v[1],v[2],n);
        System.out.println("Length of VB"+ vertexBuffer.capacity()+ "VC1"+vc1);
        vertexBuffer.position(0);
        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
    public void draw() {
        if(prevn != n && n > 0)
        {
            prevn = n;
            PopulateVBO();
        }
        // Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram);

        // get handle to vertex shader's vPosition member
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0,vc1);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

The triangle is subdivided and on single or delayed click the number of triangles

triples or divides by a factor of 3.


Here is how it looks:



Thanks

Saturday, September 14, 2024

protobuf.dev getting started with protocol buffer example

 Head over to the site https://protobuf.dev

In that site you will find things to compile a C++ and many other language projects with address book. Now just take a deep breath and dive in.

Warning: This is easy in Linux and verry verry difficult in Windows

If you find the going tough in that site, come here for I will get you some personal touch.

write_to_file.cpp

#include <iostream>
#include <fstream>
#include <string>
#include "./proto/addressbook.pb.h"
using namespace std;

// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
  cout << "Enter person ID number: ";
  int id;
  cin >> id;
  person->set_id(id);
  cin.ignore(256, '\n');

  cout << "Enter name: ";
  getline(cin, *person->mutable_name());

  cout << "Enter email address (blank for none): ";
  string email;
  getline(cin, email);
  if (!email.empty()) {
    person->set_email(email);
  }

  while (true) {
    cout << "Enter a phone number (or leave blank to finish): ";
    string number;
    getline(cin, number);
    if (number.empty()) {
      break;
    }

    tutorial::Person::PhoneNumber* phone_number = person->add_phones();
    phone_number->set_number(number);

    cout << "Is this a mobile, home, or work phone? ";
    string type;
    getline(cin, type);
    if (type == "mobile") {
      phone_number->set_type(tutorial::Person::PHONE_TYPE_MOBILE);
    } else if (type == "home") {
      phone_number->set_type(tutorial::Person::PHONE_TYPE_HOME);
    } else if (type == "work") {
      phone_number->set_type(tutorial::Person::PHONE_TYPE_WORK);
    } else {
      cout << "Unknown phone type.  Using default." << endl;
    }
  }
}

// Main function:  Reads the entire address book from a file,
//   adds one person based on user input, then writes it back out to the same
//   file.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }

  tutorial::AddressBook address_book;

  {
    // Read the existing address book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!input) {
      cout << argv[1] << ": File not found.  Creating a new file." << endl;
    } else if (!address_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    }
  }

  // Add an address.
  PromptForAddress(address_book.add_people());

  {
    // Write the new address book back to disk.
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!address_book.SerializeToOstream(&output)) {
      cerr << "Failed to write address book." << endl;
      return -1;
    }
  }

  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();

  return 0;
}

read_from_file.cpp

#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;

// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
  for (int i = 0; i < address_book.people_size(); i++) {
    const tutorial::Person& person = address_book.people(i);

    cout << "Person ID: " << person.id() << endl;
    cout << "  Name: " << person.name() << endl;
    if (person.has_email()) {
      cout << "  E-mail address: " << person.email() << endl;
    }

    for (int j = 0; j < person.phones_size(); j++) {
      const tutorial::Person::PhoneNumber& phone_number = person.phones(j);

      switch (phone_number.type()) {
        case tutorial::Person::PHONE_TYPE_MOBILE:
          cout << "  Mobile phone #: ";
          break;
        case tutorial::Person::PHONE_TYPE_HOME:
          cout << "  Home phone #: ";
          break;
        case tutorial::Person::PHONE_TYPE_WORK:
          cout << "  Work phone #: ";
          break;
      }
      cout << phone_number.number() << endl;
    }
  }
}

// Main function:  Reads the entire address book from a file and prints all
//   the information inside.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
    return -1;
  }

  tutorial::AddressBook address_book;

  {
    // Read the existing address book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!address_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse address book." << endl;
      return -1;
    }
  }

  ListPeople(address_book);

  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();

  return 0;
}


Thursday, September 5, 2024

Compile and execute a Simple example of working with Protobuf in C++

You can learn a lot by doing.

Ditto with CMake project building and running in Ubuntu. 

An example cpp-protobuf-example in the github site https://github.com/afiskon/cpp-protobuf-example

comes up often in the google search and I tried compiling it in windows and it gave error.

-- Detecting CXX compile features

-- Detecting CXX compile features - done

-- Could NOT find Protobuf (missing: Protobuf_INCLUDE_DIR)

Its difficult to install protobuf in windows but I did it in Ubuntu(which is just an app in windows now with technology of windows subsystem for linux-WSL).

First I did a git clone with this cmd: 

git clone https://github.com/afiskon/cpp-protobuf-example.git

then I changed directory to  

cd cpp-protobuf-example/

After that I created a build directory by 

mkdir build 

followed by 

cd build 

followed by

cmake .. 

which gave the same error as in windows. In ubuntu its much easier to install required libraries.

sudo apt-get install protobuf-compiler libprotobuf-dev

this command installed everything for me and I was good to go build the app.

cmake ..

worked like charm with following output:

-- Found Protobuf: /usr/lib/x86_64-linux-gnu/libprotobuf.so (found version "3.12.4")

-- Configuring done

-- Generating done

-- Build files have been written to: /home/kamathbol/GitHub/cpp-protobuf-example/build

then the command

cmake --build .

[ 20%] Running cpp protocol buffer compiler on src/proto/Game.proto

[ 40%] Building CXX object CMakeFiles/proto.dir/Game.pb.cc.o

[ 60%] Linking CXX static library libproto.a

[ 60%] Built target proto

[ 80%] Building CXX object CMakeFiles/main.dir/src/Main.cpp.o

[100%] Linking CXX executable main

[100%] Built target main

then upon running the main

./main

Saving heroes...

Loading heroes...


Name: eax

HP: 50

XP: 256

Class: warrior

Weapon: sword

Arrows: 15


Name: afiskon

HP: 25

XP: 1024

Class: mage

Spellbook: fireball, thunderbolt,

Mana: 100

BTW here is the Main.cpp source that gets you this Output:

#include <iostream>
#include <fstream>
#include <stdexcept>
#include <Game.pb.h>

using namespace std;
using namespace me::eax::examples::game;

void saveHero(const char* fname, const Hero& hero) {
    fstream out(fname, ios::out | ios::trunc | ios::binary);
    if(!hero.SerializeToOstream(&out))
        throw runtime_error("saveHero() failed");
}

void loadHero(const char* fname, Hero& hero) {        
    fstream in(fname, ios::in | ios::binary);
    if(!hero.ParseFromIstream(&in))
        throw runtime_error("loadHero() failed");
}

void printHero(const Hero& hero) {
    cout << "Name: " << hero.name() << endl;
    cout << "HP: " << hero.hp() << endl;
    cout << "XP: " << hero.xp() << endl;

    if(hero.has_mage_info()) {
        cout << "Class: mage" << endl;
        cout << "Spellbook: ";
        for(int i = 0; i < hero.mage_info().spellbook_size(); i++) {
            switch(hero.mage_info().spellbook(i)) {
                case Spell::FIREBALL:
                    cout << "fireball, ";
                    break;
                case Spell::THUNDERBOLT:
                    cout << "thunderbolt, ";
                    break;
                default:
                    cout << "(unknown spell), ";
                    break;
            }
        }
        cout << endl;
        cout << "Mana: " << hero.mage_info().mana() << endl;
    } else if(hero.has_warrior_info()) {
        cout << "Class: warrior" << endl;
        cout << "Weapon: " << (
                hero.warrior_info().weapon() == Weapon::SWORD ? "sword" :
                hero.warrior_info().weapon() == Weapon::BOW ? "bow" :
                "(unknown weapon)"
            ) << endl;
        cout << "Arrows: " << hero.warrior_info().arrows_number() << endl;
    } else {
        cout << "Class: (unknown class)" << endl;
    }

    cout << endl;
}

int main() {
    // Verify that the version of the library that we linked against is
    // compatible with the version of the headers we compiled against.
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    Hero warrior;
    warrior.set_name("eax");
    warrior.set_hp(50);
    warrior.set_xp(256);
    warrior.mutable_warrior_info()->set_weapon(Weapon::SWORD);
    warrior.mutable_warrior_info()->set_arrows_number(15);

    Hero mage;
    mage.set_name("afiskon");
    mage.set_hp(25);
    mage.set_xp(1024);
    mage.mutable_mage_info()->add_spellbook(Spell::FIREBALL);
    mage.mutable_mage_info()->add_spellbook(Spell::THUNDERBOLT);
    mage.mutable_mage_info()->set_mana(100);

    cout << "Saving heroes..." << endl;
    saveHero("eax.dat", warrior);
    saveHero("afiskon.dat", mage);

    cout << "Loading heroes..." << endl;
    Hero warrior2;
    Hero mage2;
    loadHero("eax.dat", warrior2);
    loadHero("afiskon.dat", mage2);

    cout << endl;
    printHero(warrior2);
    printHero(mage2);
}