// Earth
// by Mike 'Flux' Chang (cleaned up by Aaron Koblin). 
// Based on code by Toxi. 
// Android port by Andres Colubri
//
// This example shows the shape recording functionality in A3D,
// where an entire geometry drawn with the regular API can be
// stored in a PShape3D object for later use. This greatly
// improves the performance of the application.
// Tap on the screen to enable/disable drawing with the recorded
// shape.

PShape globe;
PImage texmap;

int sDetail = 32;  // Sphere detail setting
float rotationY = 0;
float globeRadius = 450;
float pushBack = 0;

float[] cx, cz, sphereX, sphereY, sphereZ;
float sinLUT[];
float cosLUT[];
float SINCOS_PRECISION = 0.5f;
int SINCOS_LENGTH = (int)(360.0f / SINCOS_PRECISION);  

boolean usingPShape = false;

public void setup() {
  size(480, 800, A3D);
  orientation(PORTRAIT);

  PFont font = createFont(PFont.list()[0], 24);
  textFont(font, 24);

  texmap = loadImage("world32k.jpg");    
  initializeSphere(sDetail);
  
  autoNormal(false);  
  noStroke();
  
  // Everything that is drawn between beginRecord/endRecord
  // is saved into the PShape3D returned by beginRecord.
  // Drawing the PShape3D object is much faster than redrawing
  // all the geometry again in the draw() function.
  globe = beginRecord();
  texturedSphere(globeRadius, texmap);
  endRecord();
}

public void draw() {
  background(0);            
  renderGlobe();
  
  fill(255);
  if (usingPShape) {
    text("With PShape3D. FPS: " + frameRate, 10, height - 30);  
  } else {
    text("Without PShape3D. FPS: " + frameRate, 10, height - 30);  
  }
}

void mousePressed() {
  usingPShape = !usingPShape;
}

public void renderGlobe() {
  pushMatrix();
  translate(width/2.0f, height/2.0f, pushBack);
  lights();    
  
  pushMatrix();
  
  rotateY(rotationY);  
  
  if (usingPShape) {
    shape(globe);
  } else {
    texturedSphere(globeRadius, texmap);
  }
  
  popMatrix();  
  popMatrix();

  rotationY += 0.01;
}

public void initializeSphere(int res) {
  sinLUT = new float[SINCOS_LENGTH];
  cosLUT = new float[SINCOS_LENGTH];

  for (int i = 0; i < SINCOS_LENGTH; i++) {
    sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION);
    cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION);
  }

  float delta = (float)SINCOS_LENGTH/res;
  float[] cx = new float[res];
  float[] cz = new float[res];

  // Calc unit circle in XZ plane
  for (int i = 0; i < res; i++) {
    cx[i] = -cosLUT[(int) (i*delta) % SINCOS_LENGTH];
    cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH];
  }

  // Computing vertexlist vertexlist starts at south pole
  int vertCount = res * (res-1) + 2;
  int currVert = 0;

  // Re-init arrays to store vertices
  sphereX = new float[vertCount];
  sphereY = new float[vertCount];
  sphereZ = new float[vertCount];
  float angle_step = (SINCOS_LENGTH*0.5f)/res;
  float angle = angle_step;

  // Step along Y axis
  for (int i = 1; i < res; i++) {
    float curradius = sinLUT[(int) angle % SINCOS_LENGTH];
    float currY = -cosLUT[(int) angle % SINCOS_LENGTH];
    for (int j = 0; j < res; j++) {
      sphereX[currVert] = cx[j] * curradius;
      sphereY[currVert] = currY;
      sphereZ[currVert++] = cz[j] * curradius;
    }
    angle += angle_step;
  }
  sDetail = res;
}

// Generic routine to draw textured sphere
void texturedSphere(float r, PImage t) {
  int v1,v11,v2;
  r = (r + 240 ) * 0.33;
  beginShape(TRIANGLE_STRIP);
  texture(t);
  float iu=(float)(t.width-1)/(sDetail);
  float iv=(float)(t.height-1)/(sDetail);
  float u=0,v=iv;
  for (int i = 0; i < sDetail; i++) {
    normal(0, -1, 0);
    vertex(0, -r, 0,u,0);
    normal(sphereX[i], sphereY[i], sphereZ[i]);
    vertex(sphereX[i]*r, sphereY[i]*r, sphereZ[i]*r, u, v);
    u+=iu;
  }
  vertex(0, -r, 0,u,0);
  normal(sphereX[0], sphereY[0], sphereZ[0]);
  vertex(sphereX[0]*r, sphereY[0]*r, sphereZ[0]*r, u, v);
  endShape();   
  
  // Middle rings
  int voff = 0;
  for(int i = 2; i < sDetail; i++) {
    v1=v11=voff;
    voff += sDetail;
    v2=voff;
    u=0;
    beginShape(TRIANGLE_STRIP);
    texture(t);
    for (int j = 0; j < sDetail; j++) {
      normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
      vertex(sphereX[v1]*r, sphereY[v1]*r, sphereZ[v1++]*r, u, v);
      normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
      vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2++]*r, u, v+iv);
      u+=iu;
    }
  
    // Close each ring
    v1=v11;
    v2=voff;
    normal(sphereX[v1], sphereY[v1], sphereZ[v1]);
    vertex(sphereX[v1]*r, sphereY[v1]*r, sphereZ[v1]*r, u, v);
    normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
    vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2]*r, u, v+iv);
    endShape();
    v+=iv;
  }
  u=0;
  
  // Add the northern cap
  beginShape(TRIANGLE_STRIP);
  texture(t);
  for (int i = 0; i < sDetail; i++) {
    v2 = voff + i;
    normal(sphereX[v2], sphereY[v2], sphereZ[v2]);
    vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2]*r, u, v);
    normal(0, 1, 0);
    vertex(0, r, 0, u, v+iv); 
    u+=iu;
  }
  normal(sphereX[voff], sphereY[voff], sphereZ[voff]);
  vertex(sphereX[voff]*r, sphereY[voff]*r, sphereZ[voff]*r, u, v);

  endShape(); 
}
