commit 33dbf30b0f5179c6127c2c140a9a20eea437fc85
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Jun 26 13:00:55 2023 +0200

    MeshBandedTriangle

diff --git a/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.java b/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.java
new file mode 100644
index 0000000..6be6d70
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.java
@@ -0,0 +1,539 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski  leszek@koltunski.pl                                          //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// This library is free software; you can redistribute it and/or                                 //
+// modify it under the terms of the GNU Lesser General Public                                    //
+// License as published by the Free Software Foundation; either                                  //
+// version 2.1 of the License, or (at your option) any later version.                            //
+//                                                                                               //
+// This library is distributed in the hope that it will be useful,                               //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                             //
+// Lesser General Public License for more details.                                               //
+//                                                                                               //
+// You should have received a copy of the GNU Lesser General Public                              //
+// License along with this library; if not, write to the Free Software                           //
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.mesh;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.library.main.DistortedLibrary;
+
+/**
+ * Creator a triangular mesh divided into 'bands' i.e. strips parallel to one of the sides and
+ * and of different elevations.
+ * <p>
+ * This is a building block for MeshMultigon.
+ */
+public class MeshBandedTriangle extends MeshBase
+  {
+  private static final int NUM_CACHE = 20;
+  private static final int MODE_UP   = 0;
+  private static final int MODE_DOWN = 1;
+  private static final int MODE_FLAT = 2;
+
+  private float mLeftX, mLeftY;
+  private float mRightX, mRightY;
+  private float mTopX, mTopY;
+
+  private float[] mNormL, mNormR;
+  private int mMode;
+
+  private float[] mBands;
+  private int mNumBands;
+
+  private int remainingVert;
+  private int numVertices;
+  private int extraIndex, extraVertices;
+
+  private float[] mCurveCache;
+  private float mVecX, mVecY;
+  private int[] mEdgeShape;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// mNumBands>=2
+
+  private void computeNumberOfVertices()
+     {
+     /*
+     if( mNumBands==2 && extraIndex>0 )
+       {
+       numVertices = 1 + 2*mNumPolygonVertices*(1+extraIndex+2*extraVertices);
+       }
+     else
+       {
+       numVertices = (mNumPolygonVertices*mNumPolygonBands+2)*(mNumPolygonBands-1) - 1;
+       numVertices+= 2*mNumPolygonVertices*(2*extraIndex*extraVertices);
+       }
+
+     remainingVert = numVertices;
+
+      */
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeCache()
+    {
+    /*
+    mCurveCache = new float[NUM_CACHE];
+    float[] tmpD = new float[mNumPolygonBands+1];
+    float[] tmpX = new float[mNumPolygonBands+1];
+
+    for(int i=1; i<mNumPolygonBands; i++)
+      {
+      tmpD[i] = (mPolygonBands[2*i-1]-mPolygonBands[2*i+1]) / (mPolygonBands[2*i]-mPolygonBands[2*i-2]);
+      tmpX[i] = 1.0f - (mPolygonBands[2*i]+mPolygonBands[2*i-2])/2;
+      }
+
+    tmpD[0] = tmpD[1];
+    tmpD[mNumPolygonBands] = tmpD[mNumPolygonBands-1];
+    tmpX[0] = 0.0f;
+    tmpX[mNumPolygonBands] = 1.0f;
+
+    int prev = 0;
+    int next = 0;
+
+    for(int i=0; i<NUM_CACHE-1; i++)
+      {
+      float x = i/(NUM_CACHE-1.0f);
+
+      if( x>=tmpX[next] )
+        {
+        prev = next;
+        while( next<=mNumPolygonBands && x>=tmpX[next] ) next++;
+        }
+
+      if( next>prev )
+        {
+        float t = (x-tmpX[prev]) / (tmpX[next]-tmpX[prev]);
+        mCurveCache[i] = t*(tmpD[next]-tmpD[prev]) + tmpD[prev];
+        }
+      else
+        {
+        mCurveCache[i] = tmpD[next];
+        }
+      }
+
+    mCurveCache[NUM_CACHE-1] = tmpD[mNumPolygonBands];
+
+     */
+    }
+/*
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float getSpecialQuot(int index)
+    {
+    int num = 1 + extraIndex + 2*extraVertices;
+    int change1 = extraVertices+1;
+    int change2 = num-change1;
+    float quot = 1.0f/(extraIndex+1);
+
+    if( index<change1 )      return index*quot/change1;
+    else if( index>change2 ) return 1-quot + (index-change2)*quot/change1;
+    else                     return (index-change1+1)*quot;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float getQuot(int index, int band, boolean isExtra)
+    {
+    int num = mNumPolygonBands-1-band;
+
+    if( num>0 )
+      {
+      if( isExtra )
+        {
+        int extra = extraIndex-band+extraVertices;
+
+        if( index < extra )
+          {
+          float quot = ((float)extraIndex-band)/(extra*num);
+          return index*quot;
+          }
+        else if( index > num+2*extraVertices-extra )
+          {
+          float quot = ((float)extraIndex-band)/(extra*num);
+          return (1.0f-((float)extraIndex-band)/num) + (index-num-2*extraVertices+extra)*quot;
+          }
+        else
+          {
+          return ((float)(index-extraVertices))/num;
+          }
+        }
+
+      return (float)index/num;
+      }
+
+    return 1.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float computeZEdge(float quot)
+    {
+    if( quot>=1.0f ) return 0.0f;
+
+    for(int band=1; band<mNumPolygonBands; band++)
+      {
+      float curr = mPolygonBands[2*band];
+
+      if( curr<=quot )
+        {
+        float prev = mPolygonBands[2*band-2];
+        float prevH= mPolygonBands[2*band-1];
+        float currH= mPolygonBands[2*band+1];
+
+        float A = (prev-quot)/(prev-curr);
+
+        return A*currH + (1-A)*prevH;
+        }
+      }
+
+    return 0.0f;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float derivative(float x)
+    {
+    if( x>=1.0f )
+      {
+      return 0.0f;
+      }
+    else
+      {
+      float tmp = x*(NUM_CACHE-1);
+      int i1 = (int)tmp;
+      int i2 = i1+1;
+
+      // why 0.5? Arbitrarily; this way the cubit faces of Twisty Puzzles
+      // [the main and only user of this class] look better.
+      return 0.5f*((tmp-i1)*(mCurveCache[i2]-mCurveCache[i1]) + mCurveCache[i1]);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void figureOutNormalVector2D(int edgeShape, int polyVertex, int polyEndVer, float quot, float xEdge, float yEdge )
+    {
+    if( edgeShape==SHAPE_DD )
+      {
+      if( quot<0.5f )
+        {
+        int p = polyVertex>0 ? polyVertex-1 : mNumPolygonVertices-1;
+        int prvShape = mEdgeShape[p];
+
+        switch(prvShape)
+          {
+          case SHAPE_DD : mVecX = xEdge;
+                          mVecY = yEdge;
+                          break;
+          case SHAPE_UD :
+          case SHAPE_DUD: float a = 2*quot;
+                          float xCurr= mPolygonVertices[2*polyVertex  ];
+                          float yCurr= mPolygonVertices[2*polyVertex+1];
+                          float xPrev= mPolygonVertices[2*p  ];
+                          float yPrev= mPolygonVertices[2*p+1];
+                          float xVec = xCurr-xPrev;
+                          float yVec = yCurr-yPrev;
+
+                          mVecX = a*xEdge + (1-a)*xVec;
+                          mVecY = a*yEdge + (1-a)*yVec;
+
+                          break;
+          default: throw new RuntimeException("figureOutNormalVector2D: impossible1: "+prvShape);
+          }
+        }
+      else
+        {
+        int n = polyEndVer==mNumPolygonVertices-1 ? 0 : polyEndVer+1;
+        int nxtShape = mEdgeShape[polyEndVer];
+
+        switch(nxtShape)
+          {
+          case SHAPE_DD : mVecX = xEdge;
+                          mVecY = yEdge;
+                          break;
+          case SHAPE_DU :
+          case SHAPE_DUD: float a = 2-2*quot;
+                          float xCurr= mPolygonVertices[2*polyEndVer  ];
+                          float yCurr= mPolygonVertices[2*polyEndVer+1];
+                          float xNext= mPolygonVertices[2*n  ];
+                          float yNext= mPolygonVertices[2*n+1];
+                          float xVec = xCurr-xNext;
+                          float yVec = yCurr-yNext;
+
+                          mVecX = a*xEdge + (1-a)*xVec;
+                          mVecY = a*yEdge + (1-a)*yVec;
+                          break;
+          default: throw new RuntimeException("figureOutNormalVector2D: impossible2: "+nxtShape);
+          }
+        }
+      }
+    else if( edgeShape==SHAPE_UU || (quot>=0.5f && edgeShape==SHAPE_DU) || (quot<0.5f && edgeShape==SHAPE_UD) )
+      {
+      mVecX = 1;
+      mVecY = 0;
+      }
+    else
+      {
+      float dx = mPolygonVertices[2*polyVertex  ] - mPolygonVertices[2*polyEndVer  ];
+      float dy = mPolygonVertices[2*polyVertex+1] - mPolygonVertices[2*polyEndVer+1];
+
+      if( quot<0.5 )
+        {
+        mVecX = dx;
+        mVecY = dy;
+        }
+      else
+        {
+        mVecX = -dx;
+        mVecY = -dy;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float figureOutDerivative(int edgeShape, int polyBand, float quot)
+    {
+    if( edgeShape==SHAPE_DD )
+      {
+      return derivative(1-mPolygonBands[2*polyBand]);
+      }
+    if( edgeShape==SHAPE_UU || (quot>=0.5f && edgeShape==SHAPE_DU) || (quot<0.5f && edgeShape==SHAPE_UD) )
+      {
+      return 0;
+      }
+
+    float x = 1-mPolygonBands[2*polyBand];
+    float q = quot>=0.5f ? 2-2*quot : 2*quot;
+    return derivative((1-x)*q+x);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int addVertex(int vertex, int polyBand, int polyVertex, int polyEndVer, float quot,
+                        float[] attribs1, float[] attribs2)
+    {
+    remainingVert--;
+
+    float Xfirst= mPolygonVertices[2*polyVertex  ];
+    float Yfirst= mPolygonVertices[2*polyVertex+1];
+    float Xlast = mPolygonVertices[2*polyEndVer  ];
+    float Ylast = mPolygonVertices[2*polyEndVer+1];
+
+    float xEdge = Xfirst + quot*(Xlast-Xfirst);
+    float yEdge = Yfirst + quot*(Ylast-Yfirst);
+    float zEdge;
+
+    float q = mPolygonBands[2*polyBand];
+    float o = mPolygonBands[2*polyBand+1];
+    float t = mPolygonBands[2*mNumPolygonBands-1];
+
+    int shape = mEdgeShape[polyVertex];
+
+    switch(shape)
+      {
+      case SHAPE_DD : zEdge = 0.0f; break;
+      case SHAPE_UU : zEdge = t;    break;
+      case SHAPE_DU : zEdge = quot>=0.5f ? t : computeZEdge(1-2*quot); break;
+      case SHAPE_UD : zEdge = quot<=0.5f ? t : computeZEdge(2*quot-1); break;
+      default       : zEdge = quot<=0.5f ? computeZEdge(1-2*quot) : computeZEdge(2*quot-1); break;
+      }
+
+    float x = q*xEdge;
+    float y = q*yEdge;
+    float z = o + (t-o)*(zEdge/t);
+
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x;
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y;
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = z;
+
+    figureOutNormalVector2D(shape, polyVertex, polyEndVer, quot, xEdge, yEdge);
+    float d = figureOutDerivative(shape,polyBand,quot);
+
+    float vx = d*mVecX;
+    float vy = d*mVecY;
+    float vz = mVecX*mVecX + mVecY*mVecY;
+    float len = (float)Math.sqrt(vx*vx + vy*vy + vz*vz);
+
+    int index = VERT1_ATTRIBS*vertex + NOR_ATTRIB;
+    attribs1[index  ] = vx/len;
+    attribs1[index+1] = vy/len;
+    attribs1[index+2] = vz/len;
+
+    attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = x+0.5f;
+    attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = y+0.5f;
+
+    return vertex+1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int createBandStrip(int vertex, int polyBand, int polyVertex, float[] attribs1, float[] attribs2)
+    {
+    if( polyVertex==0 )
+      {
+      vertex = addVertex(vertex,polyBand,0,1,0,attribs1,attribs2);
+      if( polyBand>0 ) vertex = addVertex(vertex,polyBand,0,1,0,attribs1,attribs2);
+      }
+
+    boolean specialCase = mNumPolygonBands==2 && polyBand==0 && extraIndex>0;
+    boolean isExtra = polyBand<extraIndex;
+    int numPairs = specialCase ? extraIndex+1 : mNumPolygonBands-1-polyBand;
+    if( isExtra ) numPairs += 2*extraVertices;
+
+    int polyEndVer = polyVertex==mNumPolygonVertices-1 ? 0 : polyVertex+1;
+    float quot1, quot2;
+
+    for(int index=0; index<numPairs; index++)
+      {
+      if( specialCase )
+        {
+        quot1 = 1.0f;
+        quot2 = getSpecialQuot(index+1);
+        }
+      else
+        {
+        quot1 = getQuot(index  ,polyBand+1, isExtra);
+        quot2 = getQuot(index+1,polyBand  , isExtra);
+        }
+
+      vertex = addVertex(vertex,polyBand+1,polyVertex,polyEndVer,quot1,attribs1,attribs2);
+      vertex = addVertex(vertex,polyBand  ,polyVertex,polyEndVer,quot2,attribs1,attribs2);
+      }
+
+    return vertex;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeEdgeShape(int curr)
+    {
+    if( mEdgeUp==null || !mEdgeUp[curr] ) return SHAPE_DD;
+
+    int prev = (curr==0 ? mNumPolygonVertices-1 : curr-1);
+    int next = (curr==mNumPolygonVertices-1 ? 0 : curr+1);
+
+    boolean rd = ( !mEdgeUp[next] || mVertUp==null || !mVertUp[next] );
+    boolean ld = ( !mEdgeUp[prev] || mVertUp==null || !mVertUp[curr] );
+
+    return rd ? (ld ? SHAPE_DUD : SHAPE_UD) : (ld ? SHAPE_DU : SHAPE_UU);
+    }
+*/
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildGrid(float[] attribs1, float[] attribs2)
+    {
+    /*
+    int vertex=0;
+
+    mEdgeShape = new int[mNumPolygonVertices];
+
+    for(int polyVertex=0; polyVertex<mNumPolygonVertices; polyVertex++)
+      mEdgeShape[polyVertex] = computeEdgeShape(polyVertex);
+
+    for(int polyBand=0; polyBand<mNumPolygonBands-1; polyBand++)
+      for(int polyVertex=0; polyVertex<mNumPolygonVertices; polyVertex++)
+        vertex = createBandStrip(vertex,polyBand,polyVertex,attribs1,attribs2);
+
+     */
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a triangular mesh split into 'bands' - i.e. strips of triangles - which are parallel to
+ * a given edge.
+ *
+ * @param vL         A pair of floats (x,y) which defines the 'left' vertex of the triangle.
+ * @param vR         A pair of floats (x,y) which defines the 'right' vertex of the triangle.
+ * @param vT         A pair of floats (x,y) which defines the 'top' vertex of the triangle.
+ * @param bands      2K floats; K pairs of two floats each describing a single band.
+ *                   From (1.0,Z[0]) (the 'left-right' edge, its Z elevation) to (0.0,Z[K])
+ *                   (the 'top' vertex, its elevation). The polygon is split into such strips.
+ *                   Must be band[2*i] > band[2*(i+1)] !
+ *                   The way the bands get interpreted also depends on the 'mode' param.
+ * @param normL      A pair of floats which define a 2D vector - which is the normal vector of each
+ *                   mesh vertex which lies on the 'left-top' edge casted to the XY plane.
+ * @param normR      Same as above but about the vertices which belong to the 'right-top' triangle
+ *                   edge. This and the previous param define the vector field of normals, as seen
+ *                   from above, of the whole mesh.
+ * @param mode       MODE_UP, MODE_DOWN or MODE_FLAT.
+ *                   This defines how we interpret the bands.
+ *                   If UP, we interpret them the same as in MeshPolygon - i.e. the first band is
+ *                   about the 'left-right' edge, then progressively towards the 'top'.
+ *                   If DOWN, we turn the interpretation of the bands upside-down: it is the last
+ *                   band which defines the strip aong the 'left-right' edge.
+ *                   If FLAT, everything is flat anyway, so we disregard the bands and return a mesh
+ *                   with 3 vertices.
+ * @param exIndex    This and the next parameter describe how to make the mesh denser at the
+ *                   'left' and 'right' vertices. If e.g. exIndex=3 and exVertices=2, then 3 triangles
+ *                   of the outermost band (and 2 triangles of the next band, and 1 triangle of the
+ *                   third band) get denser - the 3 triangles become 3+2 = 5.
+ * @param exVertices See above.
+ */
+  public MeshBandedTriangle(float[] vL, float[] vR, float[] vT, float[] bands, float[] normL, float[] normR, int mode, int exIndex, int exVertices)
+    {
+    super();
+
+    mLeftX   = vL[0];
+    mLeftY   = vL[1];
+    mRightX  = vR[0];
+    mRightY  = vR[1];
+    mTopX    = vT[0];
+    mTopY    = vT[1];
+
+    mMode = mode;
+    mNormL= normL;
+    mNormR= normR;
+
+    mBands        = bands;
+    mNumBands     = mBands.length /2;
+    extraIndex    = exIndex;
+    extraVertices = exVertices;
+
+    computeNumberOfVertices();
+    computeCache();
+
+    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+    buildGrid(attribs1,attribs2);
+
+    if( remainingVert!=0 )
+      DistortedLibrary.logMessage("MeshBandedTriangle: remainingVert " +remainingVert );
+
+    setAttribs(attribs1,attribs2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshBandedTriangle(MeshBandedTriangle mesh, boolean deep)
+    {
+    super(mesh,deep);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy the Mesh.
+ *
+ * @param deep If to be a deep or shallow copy of mVertAttribs1, i.e. the array holding vertices,
+ *             normals and inflates (the rest, in particular the mVertAttribs2 containing texture
+ *             coordinates and effect associations, is always deep copied)
+ */
+  public MeshBandedTriangle copy(boolean deep)
+    {
+    return new MeshBandedTriangle(this,deep);
+    }
+ }
\ No newline at end of file
