commit b72b8a3be149c69c739a5e198bbfb8c242456754
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Dec 18 16:42:06 2018 +0000

    Fix (hopefully the last!) probelm with texturing the Sphere: the seam in the back.

diff --git a/src/main/java/org/distorted/library/mesh/MeshSphere.java b/src/main/java/org/distorted/library/mesh/MeshSphere.java
index ac8ced9..0f2fe5c 100644
--- a/src/main/java/org/distorted/library/mesh/MeshSphere.java
+++ b/src/main/java/org/distorted/library/mesh/MeshSphere.java
@@ -75,76 +75,46 @@ public class MeshSphere extends MeshBase
 // (level*level +4*level) because there are level*level little triangles, each requiring new vertex,
 // plus 2 extra vertices to start off a row and 2 to move to the next row (or the next face in case
 // of the last row) and there are 'level' rows.
+//
+// Now to this we need to add 6*(level-1) vertices for the internal seams in the three triangles
+// in the back ( 2,7,16 in the list above ): 3 triangles, level-1 rows of more than 1 triangle in
+// each, 2 extra seam vertices in each row.
 
   private void computeNumberOfVertices(int level)
     {
-    numVertices = NUMFACES*level*(level+4) -2;
+    numVertices = NUMFACES*level*(level+4) -2  + 6*(level-1);
     currentVert = 0;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// (longitude,latitude) - spherical coordinates of a point on a unit sphere.
-// Cartesian (0,0,1) - i.e. the point of the sphere closest to the camera - is spherical (0,0).
 
-  private void addVertex( double longitude, double latitude, float[] attribs)
+  private int convertAng(double radian)
     {
-    double sinLON = Math.sin(longitude);
-    double cosLON = Math.cos(longitude);
-    double sinLAT = Math.sin(latitude);
-    double cosLAT = Math.cos(latitude);
-
-    float x = (float)(cosLAT*sinLON / sqrt2);
-    float y = (float)(sinLAT        / sqrt2);
-    float z = (float)(cosLAT*cosLON / sqrt2);
-
-    double texX = 0.5 + longitude/(2*P);
-    if( texX>=1.0 ) texX-=1.0;
-
-    //android.util.Log.e("tex", "longitude = "+((int)(180.0*longitude/P))+" texX="+texX );
-
-    double texY = 0.5 + latitude/P;
-
-    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
-    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
-    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
-                                                           //  In case of this Mesh so it happens that
-    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = x;  //  the vertex coords, normal vector, and
-    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = y;  //  inflate vector have identical (x,y,z).
-    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = z;  //
-                                                           //  TODO: think about some more efficient
-    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = x;  //  representation.
-    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = y;  //
-    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = z;  //
-
-    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
-    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = (float)texY;
-
-    currentVert++;
+    return (int)(radian*180.0/P);
     }
 
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void repeatLast(float[] attribs)
+  private void repeatVertex(float[] attribs, int diff)
     {
     //android.util.Log.e("sphere", "repeat last!");
 
     if( currentVert>0 )
       {
-      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB  ];
-      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+1];
-      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + POS_ATTRIB+2];
+      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-diff) + POS_ATTRIB  ];
+      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + POS_ATTRIB+1];
+      attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-diff) + POS_ATTRIB+2];
 
-      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB  ];
-      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+1];
-      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + NOR_ATTRIB+2];
+      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-diff) + NOR_ATTRIB  ];
+      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + NOR_ATTRIB+1];
+      attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-diff) + NOR_ATTRIB+2];
 
-      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB  ];
-      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+1];
-      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-1) + INF_ATTRIB+2];
+      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-diff) + INF_ATTRIB  ];
+      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + INF_ATTRIB+1];
+      attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = attribs[VERT_ATTRIBS*(currentVert-diff) + INF_ATTRIB+2];
 
-      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB  ];
-      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB+1];
+      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = attribs[VERT_ATTRIBS*(currentVert-diff) + TEX_ATTRIB  ];
+      attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs[VERT_ATTRIBS*(currentVert-diff) + TEX_ATTRIB+1];
 
       currentVert++;
       }
@@ -201,8 +171,8 @@ public class MeshSphere extends MeshBase
 // (      0, level-1, level) -> (lonV3,latV3 )
 // (level-1,       0, level) -> (lonV2,latV12)
 
-  private void newVertex(float[] attribs, int column, int row, int level,
-                         double lonV1, double lonV2, double latV12, double latV3)
+  private void addVertex(float[] attribs, int column, int row, int level,
+                         double lonV1, double lonV2, double latV12, double latV3, boolean lastInRow)
     {
     double quotX = (double)column/level;
     double quotY = (double)row   /level;
@@ -217,12 +187,123 @@ public class MeshSphere extends MeshBase
       quotZ = (quotY==1.0 ? 0.0 : quotX / (1.0-quotY));
       }
 
-    double lonPoint = midLongitude(lonV1, lonV2, quotZ );
-    double latPoint = midLatitude(latV12, latV3, quotY );
+    double longitude = midLongitude(lonV1, lonV2, quotZ );
+    double latitude  = midLatitude(latV12, latV3, quotY );
 
     //android.util.Log.e("sphere", "newVertex: long:"+lonPoint+" lat:"+latPoint+" column="+column+" row="+row);
 
-    addVertex(lonPoint,latPoint,attribs);
+    double sinLON = Math.sin(longitude);
+    double cosLON = Math.cos(longitude);
+    double sinLAT = Math.sin(latitude);
+    double cosLAT = Math.cos(latitude);
+
+    float x = (float)(cosLAT*sinLON / sqrt2);
+    float y = (float)(sinLAT        / sqrt2);
+    float z = (float)(cosLAT*cosLON / sqrt2);
+
+    double texX = 0.5 + longitude/(2*P);
+    if( texX>=1.0 ) texX-=1.0;
+
+    //android.util.Log.e("tex", "longitude = "+((int)(180.0*longitude/P))+" texX="+texX );
+
+    double texY = 0.5 + latitude/P;
+
+    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
+    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
+    attribs[VERT_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
+                                                           //  In case of this Mesh so it happens that
+    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB  ] = x;  //  the vertex coords, normal vector, and
+    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+1] = y;  //  inflate vector have identical (x,y,z).
+    attribs[VERT_ATTRIBS*currentVert + NOR_ATTRIB+2] = z;  //
+                                                           //  TODO: think about some more efficient
+    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB  ] = x;  //  representation.
+    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+1] = y;  //
+    attribs[VERT_ATTRIBS*currentVert + INF_ATTRIB+2] = z;  //
+
+    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
+    attribs[VERT_ATTRIBS*currentVert + TEX_ATTRIB+1] = (float)texY;
+
+    currentVert++;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // Problem: on the 'change of date' line in the back of the sphere, some triangles see texX
+    // coords suddenly jump from 1-epsilon to 0+epsilon, which looks like a seam with a narrow copy
+    // of the whole texture there.
+    // Solution: each such 'jump' triangle, if it is the last in a row, should have the texX of its
+    // last (and maybe forelast as well) vertex remapped to 1.0.
+    // If such triangle is not the last in its row, we need to add two extra vertices between it and
+    // the next one, remap the old ones' texX to 1.0, and set the two new ones' texX to 0.0.
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    if( currentVert>=3 )
+      {
+      double tex1 = attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB];
+      double tex2 = attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB];
+      double y1   = attribs[VERT_ATTRIBS*(currentVert-2) + INF_ATTRIB + 1];
+      double y2   = attribs[VERT_ATTRIBS*(currentVert-3) + INF_ATTRIB + 1];
+
+      if( tex1!=tex2 || y1!=y2 )  // if the triangle is not degenerate
+        {
+        double diff1 = Math.abs(texX-tex1);
+        double diff2 = Math.abs(texX-tex2);
+
+        if( diff1>0.5 || diff2>0.5 )    // a jump in texture coords which can only happen along the
+          {                             // 'date change' line. Have to correct!
+          if( lastInRow )
+            {
+                            attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f;
+            if( tex1<tex2 ) attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB] = 1.0f;
+            }
+          else
+            {
+            if( latitude> -A )  // top two triangles being corrected
+              {
+              if (2*column+row-1 == level)  // last such triangle in a row; have to introduce extra 2 vertices.
+                {
+                /*
+                android.util.Log.e("sphere", "LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
+                android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
+                android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
+                */
+                repeatVertex(attribs, 2);
+                repeatVertex(attribs, 2);
+
+                attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 0.0f;
+                attribs[VERT_ATTRIBS*(currentVert-2) + TEX_ATTRIB] = 0.0f;
+                attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB] = 1.0f;
+
+                if (tex1 < tex2) attribs[VERT_ATTRIBS*(currentVert-4) + TEX_ATTRIB] = 1.0f;
+                }
+              if (2*column+row == level)    // not the last; just remap current vertex' texX to 1.0.
+                {
+                /*
+                android.util.Log.e("sphere", "NOT LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
+                android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
+                android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
+                */
+                attribs[VERT_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f;
+                }
+              }
+            else // bottom triangle
+              {
+              /*
+              android.util.Log.e("sphere", "BOTTOM LAST max diff exceeded: " + convertAng(longitude) + " " + convertAng(latitude) + " texX=" + texX);
+              android.util.Log.d("sphere", " tex1=" + tex1 + " tex2=" + tex2 + " y1=" + y1 + " y2=" + y2);
+              android.util.Log.d("sphere", " latV12=" + latV12 + " latV3=" + latV3);
+              */
+              repeatVertex(attribs, 2);
+              repeatVertex(attribs, 2);
+
+              attribs[VERT_ATTRIBS*(currentVert-3) + TEX_ATTRIB] = 1.0f;
+              }
+            }
+          }
+        }
+      }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // End problem
+    ////////////////////////////////////////////////////////////////////////////////////////////////
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -233,13 +314,13 @@ public class MeshSphere extends MeshBase
       {
       for (int column=0; column<level-row; column++)
         {
-        newVertex(attribs, column, row  , level, lonV1, lonV2, latV12, latV3);
-        if (column==0 && !(face==0 && row==0 ) ) repeatLast(attribs);
-        newVertex(attribs, column, row+1, level, lonV1, lonV2, latV12, latV3);
+        addVertex(attribs, column, row  , level, lonV1, lonV2, latV12, latV3, false);
+        if (column==0 && !(face==0 && row==0 ) ) repeatVertex(attribs,1);
+        addVertex(attribs, column, row+1, level, lonV1, lonV2, latV12, latV3, false);
         }
 
-      newVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3);
-      if( row!=level-1 || face!=NUMFACES-1 ) repeatLast(attribs);
+      addVertex(attribs, level-row, row , level, lonV1, lonV2, latV12, latV3, true);
+      if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs,1);
       }
     }
 
