content: package com.gamatechno.vrlib.plugins;
  
  import android.content.Context;
  import android.opengl.GLES20;
  
  import com.gamatechno.vrlib.MD360Director;
  import com.gamatechno.vrlib.MD360DirectorFactory;
  import com.gamatechno.vrlib.MD360Program;
  import com.gamatechno.vrlib.MDVRLibrary;
  import com.gamatechno.vrlib.common.MDDirection;
  import com.gamatechno.vrlib.model.MDMainPluginBuilder;
  import com.gamatechno.vrlib.model.MDPosition;
  import com.gamatechno.vrlib.objects.MDAbsObject3D;
  import com.gamatechno.vrlib.objects.MDObject3DHelper;
  import com.gamatechno.vrlib.strategy.projection.ProjectionModeManager;
  import com.gamatechno.vrlib.texture.MD360Texture;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.FloatBuffer;
  import java.nio.ShortBuffer;
  
  import static com.gamatechno.vrlib.common.GLUtil.glCheck;
  
  /**
   * Created by hzqiujiadi on 16/8/20.
   * hzqiujiadi ashqalcn@gmail.com
   */
  public class MDMultiFishEyePlugin extends MDAbsPlugin {
  
      private MD360Program mProgram;
  
      private MD360Program mBitmapProgram;
  
      private MD360Texture mTexture;
  
      private ProjectionModeManager mProjectionModeManager;
  
      private MDMesh mConverterObject3D;
  
      private MD360Director mFixedDirector;
  
      private MDDrawingCache mDrawingCache;
  
      public MDMultiFishEyePlugin(MDMainPluginBuilder builder, float radius, MDDirection direction) {
          mTexture = builder.getTexture();
          mProgram = new MD360Program(builder.getContentType());
          mBitmapProgram = new MD360Program(MDVRLibrary.ContentType.FBO);
  
          mProjectionModeManager = builder.getProjectionModeManager();
  
          mFixedDirector = new MD360DirectorFactory.OrthogonalImpl().createDirector(0);
          mConverterObject3D = new MDMesh(radius, direction);
          mDrawingCache = new MDDrawingCache();
      }
  
      @Override
      public void initInGL(Context context) {
          mProgram.build(context);
          mBitmapProgram.build(context);
          mTexture.create();
  
          MDObject3DHelper.loadObj(context, mConverterObject3D);
      }
  
      @Override
      public void beforeRenderer(int totalWidth, int totalHeight) {
          mFixedDirector.setViewport(totalWidth, totalHeight);
          mDrawingCache.bind(totalWidth, totalHeight);
          drawConverter(totalWidth,totalHeight);
          mDrawingCache.unbind();
      }
  
      @Override
      public void renderer(int index, int width, int height, MD360Director director) {
  
          MDAbsObject3D object3D = mProjectionModeManager.getObject3D();
          // check obj3d
          if (object3D == null) return;
  
          // Update Projection
          director.setViewport(width, height);
  
          // Set our per-vertex lighting program.
          mBitmapProgram.use();
          glCheck("MDPanoramaPlugin mProgram use");
  
          // mTexture.texture(mProgram);
          GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mDrawingCache.getTextureOutput());
  
          object3D.uploadVerticesBufferIfNeed(mBitmapProgram, index);
          object3D.uploadTexCoordinateBufferIfNeed(mBitmapProgram, index);
  
          // Pass in the combined matrix.
          director.beforeShot();
          director.shot(mBitmapProgram, getModelPosition());
          object3D.draw();
      }
  
      @Override
      public void destroyInGL() {
          mTexture = null;
      }
  
      @Override
      protected MDPosition getModelPosition() {
          return mProjectionModeManager.getModelPosition();
      }
  
      @Override
      protected boolean removable() {
          return false;
      }
  
      private void drawConverter(int width, int height){
          GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
          glCheck("MDMultiFisheyeConvertLinePipe glClear");
  
          int itemWidth = width / 2;
          for (int index = 0; index < 2; index++){
              GLES20.glViewport(itemWidth * index, 0, itemWidth, height);
              GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
              GLES20.glScissor(itemWidth * index, 0, itemWidth, height);
  
              mProgram.use();
              mTexture.texture(mProgram);
  
              mFixedDirector.setViewport(itemWidth, height);
              mConverterObject3D.uploadVerticesBufferIfNeed(mProgram, index);
              mConverterObject3D.uploadTexCoordinateBufferIfNeed(mProgram, index);
  
              // Pass in the combined matrix.
              mFixedDirector.beforeShot();
              mFixedDirector.shot(mProgram, MDPosition.getOriginalPosition());
  
              mConverterObject3D.draw();
  
              GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
          }
  
      }
  
      private class MDMesh extends MDAbsObject3D {
  
          private static final String TAG = "MDMesh";
  
          private final MDDirection direction;
  
          private final float radius;
  
          public MDMesh(float radius, MDDirection direction) {
              this.radius = radius;
              this.direction = direction;
          }
  
          @Override
          protected void executeLoad(Context context) {
              generateMesh(this);
          }
  
          private void generateMesh(MDAbsObject3D object3D){
              final float PI = (float) Math.PI;
              int rows = 16;
              int columns = 16;
              int numPoint = (rows + 1) * (columns + 1);
              short r, s;
              float z = -8;
              float R = 1f/(float) rows;
              float S = 1f/(float) columns;
  
              float[] vertexs = new float[numPoint * 3];
              float[] texcoords = new float[numPoint * 2];
              float[] texcoords2 = new float[numPoint * 2];
              short[] indices = new short[numPoint * 6];
  
              int t = 0;
              int v = 0;
              for(r = 0; r < rows + 1; r++) {
                  for(s = 0; s < columns + 1; s++) {
  
                      vertexs[v++] = (s * S * 2 - 1);
                      vertexs[v++] = (r * R * 2 - 1);
                      vertexs[v++] = z;
  
                      float FOV = 3.141592654f; // FOV of the fisheye, eg: 180 degrees
                      float width = 1;
                      float height = 1;
  
                      float theta = PI * (s * S - 0.5f); // -pi to pi
                      float phi = PI * (r * R - 0.5f);  // -pi/2 to pi/2
  
                      float psphx = (float) (Math.cos(phi) * Math.sin(theta));
                      float psphy = (float) (Math.cos(phi) * Math.cos(theta));
                      float psphz = (float) Math.sin(phi);
  
                      theta = (float) Math.atan2(psphz, psphx);
                      phi = (float) Math.atan2(Math.sqrt(psphx*psphx + psphz*psphz), psphy);
                      float rr = radius * phi / FOV;
  
                      float a = (float) (0.5f * width + rr * Math.cos(theta));
                      float b = (float) (0.5f * height + rr * Math.sin(theta));
  
                      if (direction == MDDirection.HORIZONTAL){
                          texcoords[t*2] = a * 0.5f;
                          texcoords[t*2 + 1] = b;
                          texcoords2[t*2] = a * 0.5f + 0.5f;
                          texcoords2[t*2 + 1] = b;
                      } else {
                          texcoords[t*2] = a;
                          texcoords[t*2 + 1] = b * 0.5f;
                          texcoords2[t*2] = a;
                          texcoords2[t*2 + 1] = b * 0.5f + 0.5f;
                      }
  
  
                      t++;
  
                  }
              }
  
  
  
              int counter = 0;
              int sectorsPlusOne = columns + 1;
              for(r = 0; r < rows; r++){
                  for(s = 0; s < columns; s++) {
                      short k0 = (short) ((r) * sectorsPlusOne + (s+1));  // (c)
                      short k1 = (short) ((r+1) * sectorsPlusOne + (s));    //(b)
                      short k2 = (short) (r * sectorsPlusOne + s);       //(a);
                      short k3 = (short) ((r) * sectorsPlusOne + (s+1));  // (c)
                      short k4 = (short) ((r+1) * sectorsPlusOne + (s+1));  // (d)
                      short k5 = (short) ((r+1) * sectorsPlusOne + (s));    //(b)
  
                      indices[counter++] = k0;
                      indices[counter++] = k1;
                      indices[counter++] = k2;
                      indices[counter++] = k3;
                      indices[counter++] = k4;
                      indices[counter++] = k5;
                  }
              }
  
              // initialize vertex byte buffer for shape coordinates
              ByteBuffer bb = ByteBuffer.allocateDirect(
                      // (# of coordinate values * 4 bytes per float)
                      vertexs.length * 4);
              bb.order(ByteOrder.nativeOrder());
              FloatBuffer vertexBuffer = bb.asFloatBuffer();
              vertexBuffer.put(vertexs);
              vertexBuffer.position(0);
  
              // initialize vertex byte buffer for shape coordinates
              ByteBuffer ee = ByteBuffer.allocateDirect(
                      texcoords.length * 4);
              ee.order(ByteOrder.nativeOrder());
              FloatBuffer texBuffer = ee.asFloatBuffer();
              texBuffer.put(texcoords);
              texBuffer.position(0);
  
              // initialize vertex byte buffer for shape coordinates
              ByteBuffer ee2 = ByteBuffer.allocateDirect(
                      texcoords2.length * 4);
              ee2.order(ByteOrder.nativeOrder());
              FloatBuffer texBuffer2 = ee2.asFloatBuffer();
              texBuffer2.put(texcoords2);
              texBuffer2.position(0);
  
              // initialize byte buffer for the draw list
              ByteBuffer dlb = ByteBuffer.allocateDirect(
                      // (# of coordinate values * 2 bytes per short)
                      indices.length * 2);
              dlb.order(ByteOrder.nativeOrder());
              ShortBuffer indexBuffer = dlb.asShortBuffer();
              indexBuffer.put(indices);
              indexBuffer.position(0);
  
              object3D.setIndicesBuffer(indexBuffer);
              object3D.setTexCoordinateBuffer(0,texBuffer);
              object3D.setTexCoordinateBuffer(1,texBuffer2);
              object3D.setVerticesBuffer(0,vertexBuffer);
              object3D.setVerticesBuffer(1,vertexBuffer);
              object3D.setNumIndices(indices.length);
          }
      }
  }
  