commit 94690c11728a3516b048e4228e030e8d232f67f5
Author: LeszekKoltunski <leszek@koltunski.pl>
Date:   Mon Apr 28 16:01:30 2025 +0200

    Rename .java to .kt

diff --git a/src/main/java/org/distorted/library/mesh/DeferredJobs.java b/src/main/java/org/distorted/library/mesh/DeferredJobs.java
deleted file mode 100644
index 5d4e65a..0000000
--- a/src/main/java/org/distorted/library/mesh/DeferredJobs.java
+++ /dev/null
@@ -1,435 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.effect.MatrixEffect;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.effectqueue.EffectQueueVertex;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.library.type.Static4D;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be cleaned from the main package)
- *
- * @y.exclude
- */
-public class DeferredJobs
-  {
-  private static final int JOB_TYPE_VERTEX       = 0;
-  private static final int JOB_TYPE_MATRIX       = 1;
-  private static final int JOB_TYPE_MERGE_TEX    = 2;
-  private static final int JOB_TYPE_MERGE_EFF    = 3;
-  private static final int JOB_TYPE_JOIN         = 4;
-  private static final int JOB_TYPE_COPY         = 5;
-  private static final int JOB_TYPE_TEXTURE      = 6;
-  private static final int JOB_TYPE_ASSOC        = 7;
-  private static final int JOB_TYPE_CENTER       = 8;
-  private static final int JOB_TYPE_ADD_EMPTY_TEX= 9;
-  private static final int JOB_TYPE_NOT_AFFECTED = 10;
-
-  private static final ArrayList<JobNode> mJobs = new ArrayList<>();
-
-  //////////////////////////////////////////////////////////////////////////
-
-  private static class Job
-    {
-    private final int mType;
-    private final MeshBase mTarget;
-    private final MeshBase[] mSource;
-    private final MatrixEffect mMatrixEffect;
-    private final Static4D[] mMaps;
-    private final int mComp, mAndAssoc, mEquAssoc;
-    private final float mX,mY,mZ;
-    private final int[] mComps;
-
-    private EffectQueueVertex mVertexEffects;
-
-    Job(int type, MeshBase target, MeshBase[] source, VertexEffect vEff, MatrixEffect mEff,
-        Static4D[] maps, int comp, int and, int equ, float x, float y, float z, int[] comps)
-      {
-      mType     = type;
-      mTarget   = target;
-      mSource   = source;
-      mMaps     = maps;
-      mComp     = comp;
-      mAndAssoc = and;
-      mEquAssoc = equ;
-      mX        = x;
-      mY        = y;
-      mZ        = z;
-      mComps    = comps;
-
-      if( vEff!=null )
-        {
-        mVertexEffects= new EffectQueueVertex();
-        mVertexEffects.add(vEff);
-        }
-
-      mMatrixEffect = mEff;
-      }
-
-    void addEffect(VertexEffect effect)
-      {
-      mVertexEffects.add(effect);
-      }
-
-    void execute()
-      {
-      switch(mType)
-        {
-        case JOB_TYPE_VERTEX       : DistortedLibrary.adjustVertices(mTarget, mVertexEffects);
-                                     break;
-        case JOB_TYPE_MATRIX       : mTarget.applyMatrix(mMatrixEffect,mAndAssoc,mEquAssoc);
-                                     break;
-        case JOB_TYPE_MERGE_TEX    : mTarget.mergeTexComponentsNow();
-                                     break;
-        case JOB_TYPE_MERGE_EFF    : mTarget.mergeEffComponentsNow();
-                                     break;
-        case JOB_TYPE_JOIN         : mTarget.joinAttribs(mSource);
-                                     break;
-        case JOB_TYPE_COPY         : mTarget.copy(mSource[0]);
-                                     break;
-        case JOB_TYPE_TEXTURE      : mTarget.textureMap(mMaps,mComp);
-                                     break;
-        case JOB_TYPE_ASSOC        : mTarget.setEffectAssociationNow(mComp,mAndAssoc,mEquAssoc);
-                                     break;
-        case JOB_TYPE_CENTER       : mTarget.setComponentCenterNow(mComp,mX,mY,mZ);
-                                     break;
-        case JOB_TYPE_ADD_EMPTY_TEX: mTarget.addEmptyTexComponentNow();
-                                     break;
-        case JOB_TYPE_NOT_AFFECTED:  mTarget.setNotAffectedComponentsNow(mComps);
-        }
-      }
-
-    void clear()
-      {
-      if( mVertexEffects!=null ) mVertexEffects.removeAll(false);
-      }
-
-    String print()
-      {
-      switch(mType)
-        {
-        case JOB_TYPE_VERTEX       : return "VERTEX";
-        case JOB_TYPE_MATRIX       : return "MATRIX";
-        case JOB_TYPE_MERGE_TEX    : return "MERGE_TEX";
-        case JOB_TYPE_MERGE_EFF    : return "MERGE_EFF";
-        case JOB_TYPE_JOIN         : return "JOIN";
-        case JOB_TYPE_COPY         : return "COPY";
-        case JOB_TYPE_TEXTURE      : return "TEXTURE";
-        case JOB_TYPE_ASSOC        : return "ASSOC";
-        case JOB_TYPE_CENTER       : return "CENTER";
-        case JOB_TYPE_ADD_EMPTY_TEX: return "ADD_EMPTY_TEX";
-        case JOB_TYPE_NOT_AFFECTED : return "POSTPROC COMPS";
-        }
-
-      return null;
-      }
-    }
-
-  //////////////////////////////////////////////////////////////////////////
-
-  static class JobNode
-    {
-    private ArrayList<JobNode> mPrevJobs;
-    private ArrayList<JobNode> mNextJobs;
-    private Job mJob;
-
-    JobNode(Job job)
-      {
-      mPrevJobs = new ArrayList<>();
-      mNextJobs = new ArrayList<>();
-      mJob      = job;
-      }
-
-    synchronized void execute()
-      {
-      if( mPrevJobs!=null )
-        {
-        JobNode node;
-        int numPrev = mPrevJobs.size();
-
-        for(int i=0; i<numPrev; i++)
-          {
-          node = mPrevJobs.get(0);  // removeNode() rips the executed job out, thus the 0
-          node.execute();
-          }
-
-        removeNode(this);
-        mJob.execute();
-        }
-      }
-
-    synchronized void clear()
-      {
-      mPrevJobs.clear();
-      mPrevJobs = null;
-      mNextJobs.clear();
-      mNextJobs = null;
-
-      mJob.clear();
-      mJob = null;
-      }
-
-    void print(int level)
-      {
-      int numPrev = mPrevJobs.size();
-      int numNext = mNextJobs.size();
-
-      String str = "";
-      for(int i=0; i<level; i++) str+=" ";
-
-      str += mJob.print();
-
-      str += (" next: "+numNext+" prev: "+numPrev);
-
-      DistortedLibrary.logMessage("DeferredJobs: "+str);
-
-      for(int i=0; i<numPrev; i++)
-        {
-        JobNode node = mPrevJobs.get(i);
-        node.print(level+1);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void removeNode(JobNode node)
-    {
-    mJobs.remove(node);
-    JobNode jn;
-
-    int numPrev = node.mPrevJobs.size();
-
-    for(int i=0; i<numPrev; i++)
-      {
-      jn = node.mPrevJobs.get(i);
-      jn.mNextJobs.remove(node);
-      }
-
-    int numNext = node.mNextJobs.size();
-
-    for(int i=0; i<numNext; i++)
-      {
-      jn = node.mNextJobs.get(i);
-      jn.mPrevJobs.remove(node);
-      }
-
-    node.mJob.mTarget.mJobNode[0] = null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode vertex(MeshBase target, VertexEffect effect)
-    {
-    JobNode jn = target.mJobNode[0];
-
-    if( jn==null )
-      {
-      Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null,null,0,0,0,0,0,0,null);
-      JobNode node = new JobNode(job);
-      mJobs.add(node);
-      return node;
-      }
-    else
-      {
-      if( jn.mJob.mType==JOB_TYPE_VERTEX )
-        {
-        jn.mJob.addEffect(effect);
-        return jn;
-        }
-      else
-        {
-        Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null,null,0,0,0,0,0,0,null);
-        JobNode node = new JobNode(job);
-        node.mPrevJobs.add(jn);
-        jn.mNextJobs.add(node);
-        mJobs.add(node);
-        return node;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode matrix(MeshBase target, MatrixEffect effect, int andAssoc, int ecuAssoc)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_MATRIX,target,null,null,effect,null,0,andAssoc,ecuAssoc,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode mergeTex(MeshBase target)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_MERGE_TEX,target,null,null,null,null,0,0,0,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode addEmptyTex(MeshBase target)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_ADD_EMPTY_TEX,target,null,null,null,null,0,0,0,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode mergeEff(MeshBase target)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_MERGE_EFF,target,null,null,null,null,0,0,0,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode join(MeshBase target, MeshBase[] meshes)
-    {
-    JobNode jn;
-
-    Job job = new Job(JOB_TYPE_JOIN,target,meshes,null,null,null,0,0,0,0,0,0,null);
-    JobNode node = new JobNode(job);
-
-    for (MeshBase mesh : meshes)
-      {
-      jn = mesh.mJobNode[0];
-
-      if( jn!=null )
-        {
-        node.mPrevJobs.add(jn);
-        jn.mNextJobs.add(node);
-        }
-      }
-
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode copy(MeshBase target, MeshBase mesh)
-    {
-    JobNode jn = mesh.mJobNode[0];
-    MeshBase[] meshes = new MeshBase[1];
-    meshes[0] = mesh;
-    Job job = new Job(JOB_TYPE_COPY,target,meshes,null,null,null,0,0,0,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode textureMap(MeshBase target, Static4D[] maps, int comp)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_TEXTURE,target,null,null,null,maps,comp,0,0,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode effectAssoc(MeshBase target, int comp, int andAssoc, int equAssoc)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_ASSOC,target,null,null,null,null,comp,andAssoc,equAssoc,0,0,0,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode componentCenter(MeshBase target, int comp, float x, float y, float z)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_CENTER,target,null,null,null,null,comp,0,0,x,y,z,null);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static JobNode setNotAffected(MeshBase target, int[] comps)
-    {
-    JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_NOT_AFFECTED,target,null,null,null,null,0,0,0,0,0,0,comps);
-    JobNode node = new JobNode(job);
-    node.mPrevJobs.add(jn);
-    jn.mNextJobs.add(node);
-    mJobs.add(node);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Only for use by the library itself.
- *
- * @y.exclude
- */
-  public static void onPause()
-    {
-    int num = mJobs.size();
-
-    for(int i=0; i<num; i++)
-      {
-      mJobs.get(i).clear();
-      }
-
-    mJobs.clear();
-    }
-  }
diff --git a/src/main/java/org/distorted/library/mesh/DeferredJobs.kt b/src/main/java/org/distorted/library/mesh/DeferredJobs.kt
new file mode 100644
index 0000000..5d4e65a
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/DeferredJobs.kt
@@ -0,0 +1,435 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.effect.MatrixEffect;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.effectqueue.EffectQueueVertex;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.type.Static4D;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be cleaned from the main package)
+ *
+ * @y.exclude
+ */
+public class DeferredJobs
+  {
+  private static final int JOB_TYPE_VERTEX       = 0;
+  private static final int JOB_TYPE_MATRIX       = 1;
+  private static final int JOB_TYPE_MERGE_TEX    = 2;
+  private static final int JOB_TYPE_MERGE_EFF    = 3;
+  private static final int JOB_TYPE_JOIN         = 4;
+  private static final int JOB_TYPE_COPY         = 5;
+  private static final int JOB_TYPE_TEXTURE      = 6;
+  private static final int JOB_TYPE_ASSOC        = 7;
+  private static final int JOB_TYPE_CENTER       = 8;
+  private static final int JOB_TYPE_ADD_EMPTY_TEX= 9;
+  private static final int JOB_TYPE_NOT_AFFECTED = 10;
+
+  private static final ArrayList<JobNode> mJobs = new ArrayList<>();
+
+  //////////////////////////////////////////////////////////////////////////
+
+  private static class Job
+    {
+    private final int mType;
+    private final MeshBase mTarget;
+    private final MeshBase[] mSource;
+    private final MatrixEffect mMatrixEffect;
+    private final Static4D[] mMaps;
+    private final int mComp, mAndAssoc, mEquAssoc;
+    private final float mX,mY,mZ;
+    private final int[] mComps;
+
+    private EffectQueueVertex mVertexEffects;
+
+    Job(int type, MeshBase target, MeshBase[] source, VertexEffect vEff, MatrixEffect mEff,
+        Static4D[] maps, int comp, int and, int equ, float x, float y, float z, int[] comps)
+      {
+      mType     = type;
+      mTarget   = target;
+      mSource   = source;
+      mMaps     = maps;
+      mComp     = comp;
+      mAndAssoc = and;
+      mEquAssoc = equ;
+      mX        = x;
+      mY        = y;
+      mZ        = z;
+      mComps    = comps;
+
+      if( vEff!=null )
+        {
+        mVertexEffects= new EffectQueueVertex();
+        mVertexEffects.add(vEff);
+        }
+
+      mMatrixEffect = mEff;
+      }
+
+    void addEffect(VertexEffect effect)
+      {
+      mVertexEffects.add(effect);
+      }
+
+    void execute()
+      {
+      switch(mType)
+        {
+        case JOB_TYPE_VERTEX       : DistortedLibrary.adjustVertices(mTarget, mVertexEffects);
+                                     break;
+        case JOB_TYPE_MATRIX       : mTarget.applyMatrix(mMatrixEffect,mAndAssoc,mEquAssoc);
+                                     break;
+        case JOB_TYPE_MERGE_TEX    : mTarget.mergeTexComponentsNow();
+                                     break;
+        case JOB_TYPE_MERGE_EFF    : mTarget.mergeEffComponentsNow();
+                                     break;
+        case JOB_TYPE_JOIN         : mTarget.joinAttribs(mSource);
+                                     break;
+        case JOB_TYPE_COPY         : mTarget.copy(mSource[0]);
+                                     break;
+        case JOB_TYPE_TEXTURE      : mTarget.textureMap(mMaps,mComp);
+                                     break;
+        case JOB_TYPE_ASSOC        : mTarget.setEffectAssociationNow(mComp,mAndAssoc,mEquAssoc);
+                                     break;
+        case JOB_TYPE_CENTER       : mTarget.setComponentCenterNow(mComp,mX,mY,mZ);
+                                     break;
+        case JOB_TYPE_ADD_EMPTY_TEX: mTarget.addEmptyTexComponentNow();
+                                     break;
+        case JOB_TYPE_NOT_AFFECTED:  mTarget.setNotAffectedComponentsNow(mComps);
+        }
+      }
+
+    void clear()
+      {
+      if( mVertexEffects!=null ) mVertexEffects.removeAll(false);
+      }
+
+    String print()
+      {
+      switch(mType)
+        {
+        case JOB_TYPE_VERTEX       : return "VERTEX";
+        case JOB_TYPE_MATRIX       : return "MATRIX";
+        case JOB_TYPE_MERGE_TEX    : return "MERGE_TEX";
+        case JOB_TYPE_MERGE_EFF    : return "MERGE_EFF";
+        case JOB_TYPE_JOIN         : return "JOIN";
+        case JOB_TYPE_COPY         : return "COPY";
+        case JOB_TYPE_TEXTURE      : return "TEXTURE";
+        case JOB_TYPE_ASSOC        : return "ASSOC";
+        case JOB_TYPE_CENTER       : return "CENTER";
+        case JOB_TYPE_ADD_EMPTY_TEX: return "ADD_EMPTY_TEX";
+        case JOB_TYPE_NOT_AFFECTED : return "POSTPROC COMPS";
+        }
+
+      return null;
+      }
+    }
+
+  //////////////////////////////////////////////////////////////////////////
+
+  static class JobNode
+    {
+    private ArrayList<JobNode> mPrevJobs;
+    private ArrayList<JobNode> mNextJobs;
+    private Job mJob;
+
+    JobNode(Job job)
+      {
+      mPrevJobs = new ArrayList<>();
+      mNextJobs = new ArrayList<>();
+      mJob      = job;
+      }
+
+    synchronized void execute()
+      {
+      if( mPrevJobs!=null )
+        {
+        JobNode node;
+        int numPrev = mPrevJobs.size();
+
+        for(int i=0; i<numPrev; i++)
+          {
+          node = mPrevJobs.get(0);  // removeNode() rips the executed job out, thus the 0
+          node.execute();
+          }
+
+        removeNode(this);
+        mJob.execute();
+        }
+      }
+
+    synchronized void clear()
+      {
+      mPrevJobs.clear();
+      mPrevJobs = null;
+      mNextJobs.clear();
+      mNextJobs = null;
+
+      mJob.clear();
+      mJob = null;
+      }
+
+    void print(int level)
+      {
+      int numPrev = mPrevJobs.size();
+      int numNext = mNextJobs.size();
+
+      String str = "";
+      for(int i=0; i<level; i++) str+=" ";
+
+      str += mJob.print();
+
+      str += (" next: "+numNext+" prev: "+numPrev);
+
+      DistortedLibrary.logMessage("DeferredJobs: "+str);
+
+      for(int i=0; i<numPrev; i++)
+        {
+        JobNode node = mPrevJobs.get(i);
+        node.print(level+1);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void removeNode(JobNode node)
+    {
+    mJobs.remove(node);
+    JobNode jn;
+
+    int numPrev = node.mPrevJobs.size();
+
+    for(int i=0; i<numPrev; i++)
+      {
+      jn = node.mPrevJobs.get(i);
+      jn.mNextJobs.remove(node);
+      }
+
+    int numNext = node.mNextJobs.size();
+
+    for(int i=0; i<numNext; i++)
+      {
+      jn = node.mNextJobs.get(i);
+      jn.mPrevJobs.remove(node);
+      }
+
+    node.mJob.mTarget.mJobNode[0] = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode vertex(MeshBase target, VertexEffect effect)
+    {
+    JobNode jn = target.mJobNode[0];
+
+    if( jn==null )
+      {
+      Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null,null,0,0,0,0,0,0,null);
+      JobNode node = new JobNode(job);
+      mJobs.add(node);
+      return node;
+      }
+    else
+      {
+      if( jn.mJob.mType==JOB_TYPE_VERTEX )
+        {
+        jn.mJob.addEffect(effect);
+        return jn;
+        }
+      else
+        {
+        Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null,null,0,0,0,0,0,0,null);
+        JobNode node = new JobNode(job);
+        node.mPrevJobs.add(jn);
+        jn.mNextJobs.add(node);
+        mJobs.add(node);
+        return node;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode matrix(MeshBase target, MatrixEffect effect, int andAssoc, int ecuAssoc)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_MATRIX,target,null,null,effect,null,0,andAssoc,ecuAssoc,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode mergeTex(MeshBase target)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_MERGE_TEX,target,null,null,null,null,0,0,0,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode addEmptyTex(MeshBase target)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_ADD_EMPTY_TEX,target,null,null,null,null,0,0,0,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode mergeEff(MeshBase target)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_MERGE_EFF,target,null,null,null,null,0,0,0,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode join(MeshBase target, MeshBase[] meshes)
+    {
+    JobNode jn;
+
+    Job job = new Job(JOB_TYPE_JOIN,target,meshes,null,null,null,0,0,0,0,0,0,null);
+    JobNode node = new JobNode(job);
+
+    for (MeshBase mesh : meshes)
+      {
+      jn = mesh.mJobNode[0];
+
+      if( jn!=null )
+        {
+        node.mPrevJobs.add(jn);
+        jn.mNextJobs.add(node);
+        }
+      }
+
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode copy(MeshBase target, MeshBase mesh)
+    {
+    JobNode jn = mesh.mJobNode[0];
+    MeshBase[] meshes = new MeshBase[1];
+    meshes[0] = mesh;
+    Job job = new Job(JOB_TYPE_COPY,target,meshes,null,null,null,0,0,0,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode textureMap(MeshBase target, Static4D[] maps, int comp)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_TEXTURE,target,null,null,null,maps,comp,0,0,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode effectAssoc(MeshBase target, int comp, int andAssoc, int equAssoc)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_ASSOC,target,null,null,null,null,comp,andAssoc,equAssoc,0,0,0,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode componentCenter(MeshBase target, int comp, float x, float y, float z)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_CENTER,target,null,null,null,null,comp,0,0,x,y,z,null);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static JobNode setNotAffected(MeshBase target, int[] comps)
+    {
+    JobNode jn = target.mJobNode[0];
+    Job job = new Job(JOB_TYPE_NOT_AFFECTED,target,null,null,null,null,0,0,0,0,0,0,comps);
+    JobNode node = new JobNode(job);
+    node.mPrevJobs.add(jn);
+    jn.mNextJobs.add(node);
+    mJobs.add(node);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Only for use by the library itself.
+ *
+ * @y.exclude
+ */
+  public static void onPause()
+    {
+    int num = mJobs.size();
+
+    for(int i=0; i<num; i++)
+      {
+      mJobs.get(i).clear();
+      }
+
+    mJobs.clear();
+    }
+  }
diff --git a/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.java b/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.java
deleted file mode 100644
index 85405da..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.java
+++ /dev/null
@@ -1,376 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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
-  {
-  public static final int MODE_NORMAL  = 0;
-  public static final int MODE_INVERTED= 1;
-  public static final int MODE_FLAT    = 2;
-
-  private static final int NUM_CACHE = 20;
-
-  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 extraBands, extraVertices;
-
-  private float[] mCurveCache;
-  private float mVecX, mVecY;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeNumberOfVertices()
-    {
-    if( mMode==MODE_FLAT )
-      {
-      numVertices = 3;
-      }
-    else if( mMode==MODE_NORMAL )
-      {
-      numVertices = mNumBands*(mNumBands+2) + 4*extraVertices*extraBands;
-      }
-    else
-      {
-      numVertices = mNumBands*(mNumBands+2);
-      }
-
-    remainingVert = numVertices;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeCache()
-    {
-    mCurveCache = new float[NUM_CACHE];
-    float[] tmpD = new float[mNumBands+1];
-    float[] tmpX = new float[mNumBands+1];
-
-    for(int i=1; i<mNumBands; i++)
-      {
-      tmpD[i] = (mBands[2*i-1]-mBands[2*i+1]) / (mBands[2*i]-mBands[2*i-2]);
-      tmpX[i] = 1.0f - (mBands[2*i]+mBands[2*i-2])/2;
-      }
-
-    tmpD[0] = tmpD[1];
-    tmpD[mNumBands] = tmpD[mNumBands-1];
-    tmpX[0] = 0.0f;
-    tmpX[mNumBands] = 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<=mNumBands && 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[mNumBands];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  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(float qx)
-    {
-    mVecX = mNormL[0] + qx*(mNormR[0]-mNormL[0]);
-    mVecY = mNormL[1] + qx*(mNormR[1]-mNormL[1]);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float figureOutDerivative(float qy)
-    {
-    switch(mMode)
-      {
-      case MODE_NORMAL  : return derivative(1-qy);
-      case MODE_INVERTED: return -derivative(qy);
-      default           : return 0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float computeElevation(int band)
-    {
-    switch(mMode)
-      {
-      case MODE_NORMAL  : return mBands[2*band+1];
-      case MODE_INVERTED: return mBands[2*(mNumBands-band)+1];
-      default           : return mBands[2*mNumBands+1];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int addStripVertex(int vertex, float qx, float qy, int band, float[] attribs1, float[] attribs2)
-    {
-    remainingVert--;
-
-    float bx = mLeftX + qx*(mRightX-mLeftX);
-    float by = mLeftY + qx*(mRightY-mLeftY);
-
-    float q = 1-qy;
-    float x = bx + q*(mTopX-bx);
-    float y = by + q*(mTopY-by);
-    float z = computeElevation(band);
-
-    figureOutNormalVector2D(band<mNumBands ? qx : 0.5f);
-    float d = figureOutDerivative(qy);
-
-    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x;
-    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y;
-    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = z;
-
-    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 createBand(int vertex, int band, float[] attribs1, float[] attribs2)
-    {
-    boolean fromLeft = (band%2 == 0);
-    int extra = ((band<extraBands && mMode==MODE_NORMAL) ? extraVertices : 0);
-    int n = mNumBands-band-1;
-
-    float b = 1.0f/(mNumBands-band);
-    float dxb = fromLeft ? b : -b;
-    float sdx = dxb/(extra+1);
-
-    float qx = fromLeft ? 0.0f : 1.0f;
-
-    int bb = mMode==MODE_NORMAL ? band   : mNumBands-band;
-    int bt = mMode==MODE_NORMAL ? band+1 : mNumBands-(band+1);
-
-    float qyb = mBands[2*bb];
-    float qyt = mBands[2*bt];
-
-    if( mMode!=MODE_NORMAL )
-      {
-      qyb = 1-qyb;
-      qyt = 1-qyt;
-      }
-
-    vertex = addStripVertex(vertex, qx, qyb, band, attribs1,attribs2);
-
-    for(int v=0; v<extra; v++)
-      {
-      vertex = addStripVertex(vertex, qx          , qyt, band+1, attribs1,attribs2);
-      vertex = addStripVertex(vertex, qx+(v+1)*sdx, qyb, band  , attribs1,attribs2);
-      }
-
-    if( n>0 )
-      {
-      float t = 1.0f/n;
-      float dxt = fromLeft ? t : -t;
-
-      for(int v=0; v<n; v++)
-        {
-        vertex=addStripVertex(vertex, qx+ v   *dxt, qyt, band+1, attribs1, attribs2);
-        vertex=addStripVertex(vertex, qx+(v+1)*dxb, qyb, band  , attribs1, attribs2);
-        }
-      }
-
-    qx = 1-qx;
-
-    for(int v=0; v<=extra; v++)
-      {
-      vertex = addStripVertex(vertex, qx              , qyt, band+1, attribs1,attribs2);
-      vertex = addStripVertex(vertex, qx-dxb+(v+1)*sdx, qyb, band  , attribs1,attribs2);
-      }
-
-    return vertex;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildGrid(float[] attribs1, float[] attribs2)
-    {
-    if( mMode==MODE_FLAT )
-      {
-      addStripVertex(0, 0, 1, 0, attribs1,attribs2);
-      addStripVertex(1, 0, 0, 0, attribs1,attribs2);
-      addStripVertex(2, 1, 1, 0, attribs1,attribs2);
-      }
-    else
-      {
-      int vertex=0;
-      for(int b=0; b<mNumBands; b++) vertex=createBand(vertex, b, 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 exBands    This and the next parameter describe how to make the mesh denser at the
- *                   'left' and 'right' vertices. If e.g. exBands=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 exBands, int exVertices)
-    {
-    super();
-
-    mLeftX   = vL[0];
-    mLeftY   = vL[1];
-    mRightX  = vR[0];
-    mRightY  = vR[1];
-    mTopX    = vT[0];
-    mTopY    = vT[1];
-
-    mMode = mode;
-    mNormL= new float[] { normL[0],normL[1] };
-    mNormR= new float[] { normR[0],normR[1] };
-
-    mBands        = bands;
-    mNumBands     = mBands.length/2 -1;
-    extraBands    = exBands;
-    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
diff --git a/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.kt b/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.kt
new file mode 100644
index 0000000..85405da
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshBandedTriangle.kt
@@ -0,0 +1,376 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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
+  {
+  public static final int MODE_NORMAL  = 0;
+  public static final int MODE_INVERTED= 1;
+  public static final int MODE_FLAT    = 2;
+
+  private static final int NUM_CACHE = 20;
+
+  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 extraBands, extraVertices;
+
+  private float[] mCurveCache;
+  private float mVecX, mVecY;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeNumberOfVertices()
+    {
+    if( mMode==MODE_FLAT )
+      {
+      numVertices = 3;
+      }
+    else if( mMode==MODE_NORMAL )
+      {
+      numVertices = mNumBands*(mNumBands+2) + 4*extraVertices*extraBands;
+      }
+    else
+      {
+      numVertices = mNumBands*(mNumBands+2);
+      }
+
+    remainingVert = numVertices;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeCache()
+    {
+    mCurveCache = new float[NUM_CACHE];
+    float[] tmpD = new float[mNumBands+1];
+    float[] tmpX = new float[mNumBands+1];
+
+    for(int i=1; i<mNumBands; i++)
+      {
+      tmpD[i] = (mBands[2*i-1]-mBands[2*i+1]) / (mBands[2*i]-mBands[2*i-2]);
+      tmpX[i] = 1.0f - (mBands[2*i]+mBands[2*i-2])/2;
+      }
+
+    tmpD[0] = tmpD[1];
+    tmpD[mNumBands] = tmpD[mNumBands-1];
+    tmpX[0] = 0.0f;
+    tmpX[mNumBands] = 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<=mNumBands && 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[mNumBands];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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(float qx)
+    {
+    mVecX = mNormL[0] + qx*(mNormR[0]-mNormL[0]);
+    mVecY = mNormL[1] + qx*(mNormR[1]-mNormL[1]);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float figureOutDerivative(float qy)
+    {
+    switch(mMode)
+      {
+      case MODE_NORMAL  : return derivative(1-qy);
+      case MODE_INVERTED: return -derivative(qy);
+      default           : return 0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float computeElevation(int band)
+    {
+    switch(mMode)
+      {
+      case MODE_NORMAL  : return mBands[2*band+1];
+      case MODE_INVERTED: return mBands[2*(mNumBands-band)+1];
+      default           : return mBands[2*mNumBands+1];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int addStripVertex(int vertex, float qx, float qy, int band, float[] attribs1, float[] attribs2)
+    {
+    remainingVert--;
+
+    float bx = mLeftX + qx*(mRightX-mLeftX);
+    float by = mLeftY + qx*(mRightY-mLeftY);
+
+    float q = 1-qy;
+    float x = bx + q*(mTopX-bx);
+    float y = by + q*(mTopY-by);
+    float z = computeElevation(band);
+
+    figureOutNormalVector2D(band<mNumBands ? qx : 0.5f);
+    float d = figureOutDerivative(qy);
+
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x;
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y;
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = z;
+
+    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 createBand(int vertex, int band, float[] attribs1, float[] attribs2)
+    {
+    boolean fromLeft = (band%2 == 0);
+    int extra = ((band<extraBands && mMode==MODE_NORMAL) ? extraVertices : 0);
+    int n = mNumBands-band-1;
+
+    float b = 1.0f/(mNumBands-band);
+    float dxb = fromLeft ? b : -b;
+    float sdx = dxb/(extra+1);
+
+    float qx = fromLeft ? 0.0f : 1.0f;
+
+    int bb = mMode==MODE_NORMAL ? band   : mNumBands-band;
+    int bt = mMode==MODE_NORMAL ? band+1 : mNumBands-(band+1);
+
+    float qyb = mBands[2*bb];
+    float qyt = mBands[2*bt];
+
+    if( mMode!=MODE_NORMAL )
+      {
+      qyb = 1-qyb;
+      qyt = 1-qyt;
+      }
+
+    vertex = addStripVertex(vertex, qx, qyb, band, attribs1,attribs2);
+
+    for(int v=0; v<extra; v++)
+      {
+      vertex = addStripVertex(vertex, qx          , qyt, band+1, attribs1,attribs2);
+      vertex = addStripVertex(vertex, qx+(v+1)*sdx, qyb, band  , attribs1,attribs2);
+      }
+
+    if( n>0 )
+      {
+      float t = 1.0f/n;
+      float dxt = fromLeft ? t : -t;
+
+      for(int v=0; v<n; v++)
+        {
+        vertex=addStripVertex(vertex, qx+ v   *dxt, qyt, band+1, attribs1, attribs2);
+        vertex=addStripVertex(vertex, qx+(v+1)*dxb, qyb, band  , attribs1, attribs2);
+        }
+      }
+
+    qx = 1-qx;
+
+    for(int v=0; v<=extra; v++)
+      {
+      vertex = addStripVertex(vertex, qx              , qyt, band+1, attribs1,attribs2);
+      vertex = addStripVertex(vertex, qx-dxb+(v+1)*sdx, qyb, band  , attribs1,attribs2);
+      }
+
+    return vertex;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildGrid(float[] attribs1, float[] attribs2)
+    {
+    if( mMode==MODE_FLAT )
+      {
+      addStripVertex(0, 0, 1, 0, attribs1,attribs2);
+      addStripVertex(1, 0, 0, 0, attribs1,attribs2);
+      addStripVertex(2, 1, 1, 0, attribs1,attribs2);
+      }
+    else
+      {
+      int vertex=0;
+      for(int b=0; b<mNumBands; b++) vertex=createBand(vertex, b, 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 exBands    This and the next parameter describe how to make the mesh denser at the
+ *                   'left' and 'right' vertices. If e.g. exBands=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 exBands, int exVertices)
+    {
+    super();
+
+    mLeftX   = vL[0];
+    mLeftY   = vL[1];
+    mRightX  = vR[0];
+    mRightY  = vR[1];
+    mTopX    = vT[0];
+    mTopY    = vT[1];
+
+    mMode = mode;
+    mNormL= new float[] { normL[0],normL[1] };
+    mNormR= new float[] { normR[0],normR[1] };
+
+    mBands        = bands;
+    mNumBands     = mBands.length/2 -1;
+    extraBands    = exBands;
+    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
diff --git a/src/main/java/org/distorted/library/mesh/MeshBase.java b/src/main/java/org/distorted/library/mesh/MeshBase.java
deleted file mode 100644
index 3e065e7..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshBase.java
+++ /dev/null
@@ -1,1475 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 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 android.opengl.GLES30;
-
-import org.distorted.library.effect.MatrixEffect;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.effectqueue.EffectQueue;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.library.main.InternalBuffer;
-import org.distorted.library.program.DistortedProgram;
-import org.distorted.library.type.Static4D;
-import org.distorted.library.uniformblock.UniformBlockAssociation;
-import org.distorted.library.uniformblock.UniformBlockCenter;
-
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.DataInputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Abstract class which represents a Mesh, ie an array of vertices (rendered as a TRIANGLE_STRIP).
- * <p>
- * If you want to render to a particular shape, extend from here, construct a float array
- * containing per-vertex attributes, and call back setAttribs().
- */
-public abstract class MeshBase
-   {
-   private static final int ASSOC_UBO_BINDING  = 3;
-   private static final int CENTER_UBO_BINDING = 4;
-
-   // sizes of attributes of an individual vertex.
-   private static final int POS_DATA_SIZE= 3; // vertex coordinates: x,y,z
-   private static final int NOR_DATA_SIZE= 3; // normal vector: x,y,z
-   private static final int TEX_DATA_SIZE= 2; // texture coordinates: s,t
-   private static final int COM_DATA_SIZE= 1; // component number, a single float
-
-   static final int POS_ATTRIB   = 0;
-   static final int NOR_ATTRIB   = POS_DATA_SIZE;
-   static final int TEX_ATTRIB   = 0;
-   static final int COM_ATTRIB   = TEX_DATA_SIZE;
-
-   static final int VERT1_ATTRIBS= POS_DATA_SIZE + NOR_DATA_SIZE;  // number of attributes of a vertex (the part changed by preapply)
-   static final int VERT2_ATTRIBS= TEX_DATA_SIZE + COM_DATA_SIZE;  // number of attributes of a vertex (the 'preapply invariant' part)
-   static final int TRAN_ATTRIBS = POS_DATA_SIZE + NOR_DATA_SIZE;  // number of attributes of a transform feedback vertex
-
-   private static final int BYTES_PER_FLOAT = 4;
-
-   private static final int OFFSET_POS = POS_ATTRIB*BYTES_PER_FLOAT;
-   private static final int OFFSET_NOR = NOR_ATTRIB*BYTES_PER_FLOAT;
-   private static final int OFFSET_TEX = TEX_ATTRIB*BYTES_PER_FLOAT;
-   private static final int OFFSET_COM = COM_ATTRIB*BYTES_PER_FLOAT;
-
-   private static final int TRAN_SIZE  = TRAN_ATTRIBS*BYTES_PER_FLOAT;
-   private static final int VERT1_SIZE = VERT1_ATTRIBS*BYTES_PER_FLOAT;
-   private static final int VERT2_SIZE = VERT2_ATTRIBS*BYTES_PER_FLOAT;
-
-   private static final int[] mCenterBlockIndex = new int[EffectQueue.MAIN_VARIANTS];
-   private static final int[] mAssocBlockIndex  = new int[EffectQueue.MAIN_VARIANTS];
-
-   private static final int TEX_COMP_SIZE = 5; // 5 four-byte entities inside the component
-
-   private static boolean mUseCenters;
-   private static int mStride;
-   private static int mMaxComponents = 100;
-
-   private boolean mShowNormals;              // when rendering this mesh, draw normal vectors?
-   private InternalBuffer mVBO1, mVBO2, mTFO; // main vertex buffer and transform feedback buffer
-   private int mNumVertices;
-   private float[] mVertAttribs1;             // packed: PosX,PosY,PosZ, NorX,NorY,NorZ
-   private float[] mVertAttribs2;             // packed: TexS,TexT, Component
-   private float mInflate;
-   private final UniformBlockAssociation mUBA;
-   private UniformBlockCenter mUBC;
-   private boolean mStrideCorrected;
-
-   DeferredJobs.JobNode[] mJobNode;
-
-   private static class TexComponent
-     {
-     private int mEndIndex;
-     private final Static4D mTextureMap;
-
-     TexComponent(int end)
-       {
-       mEndIndex  = end;
-       mTextureMap= new Static4D(0,0,1,1);
-       }
-     TexComponent(TexComponent original)
-       {
-       mEndIndex = original.mEndIndex;
-
-       float x = original.mTextureMap.get0();
-       float y = original.mTextureMap.get1();
-       float z = original.mTextureMap.get2();
-       float w = original.mTextureMap.get3();
-       mTextureMap = new Static4D(x,y,z,w);
-       }
-
-     void setMap(Static4D map)
-       {
-       mTextureMap.set(map.get0(),map.get1(),map.get2(),map.get3());
-       }
-     }
-
-   private ArrayList<TexComponent> mTexComponent;
-   private ArrayList<Integer> mEffComponent;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   MeshBase()
-     {
-     mShowNormals  = false;
-     mInflate      = 0.0f;
-     mTexComponent = new ArrayList<>();
-     mEffComponent = new ArrayList<>();
-
-     mJobNode = new DeferredJobs.JobNode[1];
-
-     mUBA = new UniformBlockAssociation();
-
-     if( mUseCenters ) mUBC = new UniformBlockCenter();
-
-     mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_DRAW);
-     mVBO2= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_DRAW);
-     mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// copy constructor
-
-   MeshBase(MeshBase original, boolean deep)
-     {
-     mShowNormals= original.mShowNormals;
-     mInflate    = original.mInflate;
-     mNumVertices= original.mNumVertices;
-
-     mUBA = new UniformBlockAssociation(original.mUBA);
-
-     if( mUseCenters ) mUBC = new UniformBlockCenter(original.mUBC);
-
-     if( deep )
-       {
-       mJobNode = new DeferredJobs.JobNode[1];
-       if( original.mJobNode[0]==null ) copy(original);
-       else mJobNode[0] = DeferredJobs.copy(this,original);
-       }
-     else
-       {
-       mJobNode      = original.mJobNode;
-       mVBO1         = original.mVBO1;
-       mVertAttribs1 = original.mVertAttribs1;
-       shallowCopy(original);
-       }
-
-     mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void copy(MeshBase original)
-     {
-     shallowCopy(original);
-
-     mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER, GLES30.GL_STATIC_DRAW);
-     mVertAttribs1= new float[mNumVertices*VERT1_ATTRIBS];
-     System.arraycopy(original.mVertAttribs1,0,mVertAttribs1,0,mNumVertices*VERT1_ATTRIBS);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void shallowCopy(MeshBase original)
-     {
-     int texComSize = original.mTexComponent.size();
-     mTexComponent = new ArrayList<>();
-
-     for(int i=0; i<texComSize; i++)
-       {
-       TexComponent comp = new TexComponent(original.mTexComponent.get(i));
-       mTexComponent.add(comp);
-       }
-
-     mEffComponent = new ArrayList<>();
-     mEffComponent.addAll(original.mEffComponent);
-
-     mVBO2= new InternalBuffer(GLES30.GL_ARRAY_BUFFER, GLES30.GL_STATIC_DRAW);
-     mVertAttribs2= new float[mNumVertices*VERT2_ATTRIBS];
-     System.arraycopy(original.mVertAttribs2,0,mVertAttribs2,0,mNumVertices*VERT2_ATTRIBS);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void mergeTexComponentsNow()
-     {
-     int num = mTexComponent.size();
-
-     if( num>1 )
-       {
-       mTexComponent.clear();
-       mTexComponent.add(new TexComponent(mNumVertices-1));
-
-       mVBO2.invalidate();
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void addEmptyTexComponentNow()
-     {
-     mTexComponent.add(new TexComponent(mNumVertices-1));
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void mergeEffComponentsNow()
-     {
-     int num = mEffComponent.size();
-
-     if( num>1 )
-       {
-       mEffComponent.clear();
-       mEffComponent.add(mNumVertices-1);
-
-       for(int index=0; index<mNumVertices; index++)
-         {
-         mVertAttribs2[VERT2_ATTRIBS*index+COM_ATTRIB] = 0;
-         }
-
-       mVBO2.invalidate();
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void applyMatrix(MatrixEffect effect, int andAssoc, int equAssoc)
-     {
-     float[] matrixP  = new float[16];
-     float[] matrixV  = new float[16];
-     float[] uniforms = new float[7];
-     int start, end=-1, numComp = mEffComponent.size();
-
-     matrixP[0] = matrixP[5] = matrixP[10] = matrixP[15] = 1;
-     matrixV[0] = matrixV[5] = matrixV[10] = matrixV[15] = 1;
-
-     effect.compute(uniforms,0,0,0);
-     effect.apply(matrixP, matrixV, uniforms, 0);
-
-     for(int comp=0; comp<numComp; comp++)
-       {
-       start = end+1;
-       end   = mEffComponent.get(comp);
-
-       if( mUBA.matchesAssociation(comp, andAssoc, equAssoc) )
-         {
-         applyMatrixToComponent(matrixP, matrixV, start, end);
-         }
-       }
-
-     mVBO1.invalidate();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void applyMatrixToComponent(float[] matrixP, float[] matrixV, int start, int end)
-     {
-     float x,y,z;
-
-     for(int index=start*VERT1_ATTRIBS; index<=end*VERT1_ATTRIBS; index+=VERT1_ATTRIBS )
-       {
-       x = mVertAttribs1[index+POS_ATTRIB  ];
-       y = mVertAttribs1[index+POS_ATTRIB+1];
-       z = mVertAttribs1[index+POS_ATTRIB+2];
-
-       mVertAttribs1[index+POS_ATTRIB  ] = matrixP[0]*x + matrixP[4]*y + matrixP[ 8]*z + matrixP[12];
-       mVertAttribs1[index+POS_ATTRIB+1] = matrixP[1]*x + matrixP[5]*y + matrixP[ 9]*z + matrixP[13];
-       mVertAttribs1[index+POS_ATTRIB+2] = matrixP[2]*x + matrixP[6]*y + matrixP[10]*z + matrixP[14];
-
-       x = mVertAttribs1[index+NOR_ATTRIB  ];
-       y = mVertAttribs1[index+NOR_ATTRIB+1];
-       z = mVertAttribs1[index+NOR_ATTRIB+2];
-
-       mVertAttribs1[index+NOR_ATTRIB  ] = matrixV[0]*x + matrixV[4]*y + matrixV[ 8]*z;
-       mVertAttribs1[index+NOR_ATTRIB+1] = matrixV[1]*x + matrixV[5]*y + matrixV[ 9]*z;
-       mVertAttribs1[index+NOR_ATTRIB+2] = matrixV[2]*x + matrixV[6]*y + matrixV[10]*z;
-
-       x = mVertAttribs1[index+NOR_ATTRIB  ];
-       y = mVertAttribs1[index+NOR_ATTRIB+1];
-       z = mVertAttribs1[index+NOR_ATTRIB+2];
-
-       float len1 = (float)Math.sqrt(x*x + y*y + z*z);
-
-       if( len1>0.0f )
-         {
-         mVertAttribs1[index+NOR_ATTRIB  ] /= len1;
-         mVertAttribs1[index+NOR_ATTRIB+1] /= len1;
-         mVertAttribs1[index+NOR_ATTRIB+2] /= len1;
-         }
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void setEffectAssociationNow(int component, int andAssociation, int equAssociation)
-     {
-     mUBA.setEffectAssociationNow(component, andAssociation, equAssociation);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void setNotAffectedComponentsNow(int[] components)
-     {
-     mUBA.setNotAffectedComponentsNow(components);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void setComponentCenterNow(int component, float x, float y, float z)
-     {
-     if( mUBC!=null )
-       {
-       mUBC.setEffectCenterNow(component, x, y, z);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// when a derived class is done computing its mesh, it has to call this method.
-
-   void setAttribs(float[] vert1Attribs, float[] vert2Attribs)
-     {
-     mNumVertices = vert1Attribs.length/VERT1_ATTRIBS;
-     mVertAttribs1= vert1Attribs;
-     mVertAttribs2= vert2Attribs;
-
-     mTexComponent.add(new TexComponent(mNumVertices-1));
-     mEffComponent.add(mNumVertices-1);
-
-     mVBO1.invalidate();
-     mVBO2.invalidate();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void joinAttribs(MeshBase[] meshes)
-     {
-     MeshBase mesh;
-     TexComponent comp;
-     int numMeshes = meshes.length;
-     int numVertices,origVertices = mNumVertices;
-     int origTexComponents,numTexComponents;
-     int origEffComponents=0,numEffComponents;
-
-     if( origVertices>0 )
-       {
-       origTexComponents = mTexComponent.size();
-       mNumVertices+= ( mNumVertices%2==1 ? 2:1 );
-       mTexComponent.get(origTexComponents-1).mEndIndex = mNumVertices-1;
-       origEffComponents = mEffComponent.size();
-       mEffComponent.set(origEffComponents-1,mNumVertices-1);
-       }
-
-     for(int i=0; i<numMeshes; i++)
-       {
-       mesh = meshes[i];
-       numTexComponents = mesh.mTexComponent.size();
-       numEffComponents = mesh.mEffComponent.size();
-       numVertices = mesh.mNumVertices;
-
-       int extraVerticesBefore = mNumVertices==0 ? 0:1;
-       int extraVerticesAfter  = (i==numMeshes-1) ? 0 : (numVertices%2==1 ? 2:1);
-
-       for(int j=0; j<numTexComponents; j++)
-         {
-         comp = new TexComponent(mesh.mTexComponent.get(j));
-         comp.mEndIndex += (extraVerticesBefore+mNumVertices);
-         if( j==numTexComponents-1) comp.mEndIndex += extraVerticesAfter;
-         mTexComponent.add(comp);
-         }
-
-       for(int j=0; j<numEffComponents; j++)
-         {
-         int index = mesh.mEffComponent.get(j);
-         index += (extraVerticesBefore+mNumVertices);
-         if( j==numEffComponents-1 ) index += extraVerticesAfter;
-         mEffComponent.add(index);
-
-         if( origEffComponents<mMaxComponents )
-           {
-           mUBA.copy(origEffComponents, mesh.mUBA, j);
-           if( mUseCenters ) mUBC.copy(origEffComponents, mesh.mUBC, j);
-           origEffComponents++;
-           }
-         }
-
-       mNumVertices += (extraVerticesBefore+numVertices+extraVerticesAfter);
-       }
-
-     float[] newAttribs1 = new float[VERT1_ATTRIBS*mNumVertices];
-     float[] newAttribs2 = new float[VERT2_ATTRIBS*mNumVertices];
-     numVertices = origVertices;
-
-     if( origVertices>0 )
-       {
-       System.arraycopy(mVertAttribs1,                              0, newAttribs1,                          0, VERT1_ATTRIBS*numVertices);
-       System.arraycopy(mVertAttribs1, VERT1_ATTRIBS*(origVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS            );
-       System.arraycopy(mVertAttribs2,                              0, newAttribs2,                          0, VERT2_ATTRIBS*numVertices);
-       System.arraycopy(mVertAttribs2, VERT2_ATTRIBS*(origVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS            );
-       origVertices++;
-
-       if( numVertices%2==1 )
-         {
-         System.arraycopy(mVertAttribs1, VERT1_ATTRIBS*(origVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
-         System.arraycopy(mVertAttribs2, VERT2_ATTRIBS*(origVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
-         origVertices++;
-         }
-       }
-
-     for(int i=0; i<numMeshes; i++)
-       {
-       mesh = meshes[i];
-       numVertices = mesh.mNumVertices;
-
-       if( origVertices>0 )
-         {
-         System.arraycopy(mesh.mVertAttribs1, 0, newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
-         System.arraycopy(mesh.mVertAttribs2, 0, newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
-         origVertices++;
-         }
-       System.arraycopy(mesh.mVertAttribs1, 0, newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS*numVertices);
-       System.arraycopy(mesh.mVertAttribs2, 0, newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS*numVertices);
-       origVertices+=numVertices;
-
-       if( i<numMeshes-1 && numVertices>0 )
-         {
-         System.arraycopy(mesh.mVertAttribs1, VERT1_ATTRIBS*(numVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
-         System.arraycopy(mesh.mVertAttribs2, VERT2_ATTRIBS*(numVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
-         origVertices++;
-
-         if( numVertices%2==1 )
-           {
-           System.arraycopy(mesh.mVertAttribs1, VERT1_ATTRIBS*(numVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
-           System.arraycopy(mesh.mVertAttribs2, VERT2_ATTRIBS*(numVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
-           origVertices++;
-           }
-         }
-       }
-
-     if( origVertices!=mNumVertices )
-       {
-       DistortedLibrary.logMessage("MeshBase: join: origVertices: "+origVertices+" numVertices: "+mNumVertices);
-       }
-
-     int endIndex, index=0, numEffComp = mEffComponent.size();
-
-     for(int component=0; component<numEffComp; component++)
-       {
-       endIndex = mEffComponent.get(component);
-
-       for( ; index<=endIndex; index++) newAttribs2[VERT2_ATTRIBS*index+COM_ATTRIB] = component;
-       }
-
-     mVertAttribs1 = newAttribs1;
-     mVertAttribs2 = newAttribs2;
-     mVBO1.invalidate();
-     mVBO2.invalidate();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// called from MeshJoined
-
-   void join(MeshBase[] meshes)
-     {
-     boolean immediateJoin = true;
-
-     for (MeshBase mesh : meshes)
-       {
-       if (mesh.mJobNode[0] != null)
-         {
-         immediateJoin = false;
-         break;
-         }
-       }
-
-     if( immediateJoin ) joinAttribs(meshes);
-     else                mJobNode[0] = DeferredJobs.join(this,meshes);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void textureMap(Static4D[] maps, int startComponent)
-     {
-     int num_comp = mTexComponent.size();
-     if( startComponent>num_comp ) return;
-
-     int num_maps = maps.length;
-     int min = Math.min(num_comp-startComponent, num_maps);
-     int vertex = startComponent>0 ? mTexComponent.get(startComponent-1).mEndIndex+1 : 0;
-     Static4D newMap, oldMap;
-     TexComponent comp;
-     float newW, newH, ratW, ratH, movX, movY;
-
-     for(int i=0; i<min; i++)
-       {
-       newMap = maps[i];
-       comp = mTexComponent.get(i+startComponent);
-
-       if( newMap!=null )
-         {
-         newW = newMap.get2();
-         newH = newMap.get3();
-
-         if( newW!=0.0f && newH!=0.0f )
-           {
-           oldMap = comp.mTextureMap;
-           ratW = newW/oldMap.get2();
-           ratH = newH/oldMap.get3();
-           movX = newMap.get0() - ratW*oldMap.get0();
-           movY = newMap.get1() - ratH*oldMap.get1();
-
-           for( int index=vertex*VERT2_ATTRIBS+TEX_ATTRIB ; vertex<=comp.mEndIndex; vertex++, index+=VERT2_ATTRIBS)
-             {
-             mVertAttribs2[index  ] = ratW*mVertAttribs2[index  ] + movX;
-             mVertAttribs2[index+1] = ratH*mVertAttribs2[index+1] + movY;
-             }
-           comp.setMap(newMap);
-           }
-         }
-
-       vertex= comp.mEndIndex+1;
-       }
-
-     mVBO2.invalidate();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Are we going to need per-component centers (needed for correct postprocessing of concave meshes)
- * Switching this on allocates 16*MAX_EFFECT_COMPONENTS bytes for uniforms in the vertex shader.
- */
-   public static void setUseCenters()
-     {
-     mUseCenters = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Are we using per-component centers?
- */
-   public static boolean getUseCenters()
-     {
-     return mUseCenters;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public static int getMaxEffComponents()
-     {
-     return mMaxComponents;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * How many mesh components are we going to need? Call before compilation of the shaders.
- */
-   public static void setMaxEffComponents(int max)
-     {
-     mMaxComponents = max;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public static void getUniforms(int programH, int variant)
-     {
-     mCenterBlockIndex[variant]= GLES30.glGetUniformBlockIndex(programH, "componentCenter");
-     mAssocBlockIndex[variant] = GLES30.glGetUniformBlockIndex(programH, "componentAssociation");
-
-     if( mStride==0 )
-       {
-       String[] uniformNames = { "vComAssoc" };
-       int[] indices = new int[1];
-       int[] params  = new int[1];
-
-       GLES30.glGetUniformIndices(programH, uniformNames, indices, 0);
-       GLES30.glGetActiveUniformsiv( programH, 1, indices, 0, GLES30.GL_UNIFORM_ARRAY_STRIDE, params,0 );
-
-       mStride = params[0]/4;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void copyTransformToVertex()
-     {
-     ByteBuffer buffer = (ByteBuffer)GLES30.glMapBufferRange( GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0,
-                                                              TRAN_SIZE*mNumVertices, GLES30.GL_MAP_READ_BIT);
-     if( buffer!=null )
-       {
-       FloatBuffer feedback = buffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
-       feedback.get(mVertAttribs1,0,VERT1_ATTRIBS*mNumVertices);
-       mVBO1.updateFloat(mVertAttribs1);
-       }
-     else
-       {
-       int error = GLES30.glGetError();
-       DistortedLibrary.logMessage("MeshBase: failed to map tf buffer, error="+error);
-       }
-
-     GLES30.glUnmapBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void debugJobs()
-     {
-     if( mJobNode[0]!=null )
-       {
-       mJobNode[0].print(0);
-       }
-     else
-       {
-       DistortedLibrary.logMessage("MeshBase: JobNode null");
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void printTexComponent(int comp)
-     {
-     if( comp>=0 && comp<getNumTexComponents() )
-       {
-       int beg = comp>0 ? mTexComponent.get(comp-1).mEndIndex+1 : 0;
-       int end = mTexComponent.get(comp).mEndIndex;
-       StringBuilder sb = new StringBuilder();
-
-       for( int vert=beg; vert<=end; vert++)
-         {
-         sb.append('(');
-         sb.append(mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB  ]);
-         sb.append(',');
-         sb.append(mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+1]);
-         sb.append(',');
-         sb.append(mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+2]);
-         sb.append(") ");
-         }
-
-       DistortedLibrary.logMessage("MeshBase: "+sb);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void printVertex(int v)
-     {
-     String pos = "\n pos: (" + mVertAttribs1[VERT1_ATTRIBS*v +POS_ATTRIB  ] + ","
-                              + mVertAttribs1[VERT1_ATTRIBS*v +POS_ATTRIB+1] + ","
-                              + mVertAttribs1[VERT1_ATTRIBS*v +POS_ATTRIB+2];
-
-     String nor = "\n nor: (" + mVertAttribs1[VERT1_ATTRIBS*v +NOR_ATTRIB  ] + ","
-                              + mVertAttribs1[VERT1_ATTRIBS*v +NOR_ATTRIB+1] + ","
-                              + mVertAttribs1[VERT1_ATTRIBS*v +NOR_ATTRIB+2];
-
-     String tex = "\n tex: (" + mVertAttribs2[VERT2_ATTRIBS*v +TEX_ATTRIB  ] + ","
-                              + mVertAttribs2[VERT2_ATTRIBS*v +TEX_ATTRIB+1];
-
-     DistortedLibrary.logMessage("MeshBase: first vertex:"+pos+nor+tex);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void printPos()
-     {
-     StringBuilder sb = new StringBuilder();
-
-     for(int i=0; i<mNumVertices; i++)
-       {
-       sb.append('(');
-       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+POS_ATTRIB  ]);
-       sb.append(',');
-       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+POS_ATTRIB+1]);
-       sb.append(',');
-       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+POS_ATTRIB+2]);
-       sb.append(")\n");
-       }
-     DistortedLibrary.logMessage("MeshBase: vertices: \n"+sb);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   /**
-    * Not part of public API, do not document (public only because has to be used from the main package)
-    *
-    * @y.exclude
-    */
-   public void printNor()
-     {
-     StringBuilder sb = new StringBuilder();
-
-     for(int i=0; i<mNumVertices; i++)
-       {
-       sb.append('(');
-       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+NOR_ATTRIB  ]);
-       sb.append(',');
-       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+NOR_ATTRIB+1]);
-       sb.append(',');
-       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+NOR_ATTRIB+2]);
-       sb.append(")\n");
-       }
-     DistortedLibrary.logMessage("MeshBase: normals:\n"+sb);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void printCom()
-     {
-     StringBuilder sb = new StringBuilder();
-
-     for(int i=0; i<mNumVertices; i++)
-       {
-       sb.append(mVertAttribs2[VERT2_ATTRIBS*i+COM_ATTRIB]);
-       sb.append(' ');
-       }
-
-     DistortedLibrary.logMessage("MeshBase: "+sb);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void printTex()
-     {
-     int vert=0;
-     int num = getNumTexComponents();
-     StringBuilder sb = new StringBuilder();
-     sb.append("tex components: ").append(num);
-
-     for(int i=0; i<num; i++)
-       {
-       int end = mTexComponent.get(i).mEndIndex;
-       sb.append("\n");
-
-       for( ; vert<=end; vert++)
-         {
-         sb.append(' ');
-         sb.append('(');
-         sb.append(mVertAttribs2[VERT2_ATTRIBS*vert+TEX_ATTRIB]);
-         sb.append(',');
-         sb.append(mVertAttribs2[VERT2_ATTRIBS*vert+TEX_ATTRIB+1]);
-         sb.append(')');
-         }
-       }
-
-     DistortedLibrary.logMessage("MeshBase: "+sb);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public int getTFO()
-     {
-     return mTFO.createImmediatelyFloat(mNumVertices*TRAN_SIZE, null);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public int getNumVertices()
-     {
-     return mNumVertices;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void send(int programH, int variant)
-     {
-     if( !mStrideCorrected )
-       {
-       mStrideCorrected = true;
-       mUBA.correctStride(mStride);
-       }
-
-     int indexA = mUBA.getIndex();
-     GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, ASSOC_UBO_BINDING, indexA);
-     GLES30.glUniformBlockBinding(programH, mAssocBlockIndex[variant], ASSOC_UBO_BINDING);
-
-     if( mUseCenters )
-       {
-       int indexC = mUBC.getIndex();
-       GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, CENTER_UBO_BINDING, indexC);
-       GLES30.glUniformBlockBinding(programH, mCenterBlockIndex[variant], CENTER_UBO_BINDING);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void bindVertexAttribs(DistortedProgram program)
-     {
-     if( mJobNode[0]!=null )
-       {
-       mJobNode[0].execute();  // this will set itself to null
-       }
-
-     int index1 = mVBO1.createImmediatelyFloat(mNumVertices*VERT1_SIZE, mVertAttribs1);
-     int index2 = mVBO2.createImmediatelyFloat(mNumVertices*VERT2_SIZE, mVertAttribs2);
-     int[] attr = program.mAttribute;
-
-     switch(program.mAttributeLayout )
-       {
-       case DistortedProgram.ATTR_LAYOUT_PNTC:
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
-               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
-               GLES30.glVertexAttribPointer(attr[1], NOR_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_NOR);
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
-               GLES30.glVertexAttribPointer(attr[2], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
-               GLES30.glVertexAttribPointer(attr[3], COM_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_COM);
-               break;
-       case DistortedProgram.ATTR_LAYOUT_PNT:
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
-               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
-               GLES30.glVertexAttribPointer(attr[1], NOR_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_NOR);
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
-               GLES30.glVertexAttribPointer(attr[2], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
-               break;
-       case DistortedProgram.ATTR_LAYOUT_PNC:
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
-               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
-               GLES30.glVertexAttribPointer(attr[1], NOR_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_NOR);
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
-               GLES30.glVertexAttribPointer(attr[2], COM_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_COM);
-               break;
-       case DistortedProgram.ATTR_LAYOUT_PTC:
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
-               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
-               GLES30.glVertexAttribPointer(attr[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
-               GLES30.glVertexAttribPointer(attr[2], COM_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_COM);
-               break;
-       case DistortedProgram.ATTR_LAYOUT_PT:
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
-               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
-               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
-               GLES30.glVertexAttribPointer(attr[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
-               break;
-       case DistortedProgram.ATTR_LAYOUT_UNK:
-               DistortedLibrary.logMessage("Unknown attribute layout: "+program.mAttributeLayout);
-       }
-
-     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void bindTransformAttribs(DistortedProgram program)
-     {
-     int index = mTFO.createImmediatelyFloat(mNumVertices*TRAN_SIZE, null);
-
-     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index );
-     GLES30.glVertexAttribPointer(program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, 0);
-     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public void setInflate(float inflate)
-     {
-     mInflate = inflate;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used from the main package)
- *
- * @y.exclude
- */
-   public float getInflate()
-     {
-     return mInflate;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Return the number of bytes read.
-
-   int read(DataInputStream stream)
-     {
-     byte[] buffer = new byte[BYTES_PER_FLOAT*4];
-     int version, numEff, numTex;
-
-     //////////////////////////////////////////////////////////////////////////////////////////
-     // read version, number of vertices, number of tex components, number of effect components
-     //////////////////////////////////////////////////////////////////////////////////////////
-
-     try
-       {
-       stream.read(buffer);
-       ByteBuffer byteBuf = ByteBuffer.wrap(buffer);
-
-       version = byteBuf.getInt(0);
-
-       if( version==1 || version==2 )
-         {
-         mNumVertices= byteBuf.getInt(4);
-         numTex      = byteBuf.getInt(8);
-         numEff      = byteBuf.getInt(12);
-         }
-       else
-         {
-         DistortedLibrary.logMessage("MeshBase: Error: unknown mesh file version "+String.format("0x%08X", version));
-         return 0;
-         }
-       }
-     catch(Exception e)
-       {
-       DistortedLibrary.logMessage("MeshBase: Exception1 trying to read file: "+ e);
-       return 0;
-       }
-
-     //////////////////////////////////////////////////////////////////////////////////////////
-     // read contents of all texture and effect components
-     //////////////////////////////////////////////////////////////////////////////////////////
-
-     if( mNumVertices>0 && numEff>0 && numTex>0 )
-       {
-       int size = numEff+TEX_COMP_SIZE*numTex;
-       float[] tmp = new float[size];
-       mVertAttribs1 = new float[VERT1_ATTRIBS*mNumVertices];
-       mVertAttribs2 = new float[VERT2_ATTRIBS*mNumVertices];
-
-       // version 1 had extra 3 floats (the 'inflate' vector) in its vert1 array
-       int vert1InFile = version==2 ? VERT1_ATTRIBS : VERT1_ATTRIBS+3;
-       // ... and there was no center.
-       int centerSize  = version==2 ? 3*numEff : 0;
-
-       buffer = new byte[BYTES_PER_FLOAT*(size + centerSize + mNumVertices*(vert1InFile+VERT2_ATTRIBS))];
-
-       try
-         {
-         stream.read(buffer);
-         }
-       catch(Exception e)
-         {
-         DistortedLibrary.logMessage("MeshBase: Exception2 trying to read file: "+ e);
-         return 0;
-         }
-
-       ByteBuffer byteBuf = ByteBuffer.wrap(buffer);
-       FloatBuffer floatBuf = byteBuf.asFloatBuffer();
-
-       floatBuf.get(tmp,0,size);
-
-       TexComponent tex;
-       int index, texComp;
-       float x, y, z, w;
-
-       for(texComp=0; texComp<numTex; texComp++)
-         {
-         index = (int)tmp[TEX_COMP_SIZE*texComp];
-
-         x= tmp[TEX_COMP_SIZE*texComp+1];
-         y= tmp[TEX_COMP_SIZE*texComp+2];
-         z= tmp[TEX_COMP_SIZE*texComp+3];
-         w= tmp[TEX_COMP_SIZE*texComp+4];
-
-         tex = new TexComponent(index);
-         tex.setMap(new Static4D(x,y,z,w));
-
-         mTexComponent.add(tex);
-         }
-
-       for(int effComp=0; effComp<numEff; effComp++)
-         {
-         index = (int)tmp[TEX_COMP_SIZE*texComp + effComp ];
-         mEffComponent.add(index);
-         }
-
-       //////////////////////////////////////////////////////////////////////////////////////////
-       // read vert1 array, vert2 array and (if version>1) the component centers.
-       //////////////////////////////////////////////////////////////////////////////////////////
-
-       switch(version)
-         {
-         case 1 : index = floatBuf.position();
-
-                  for(int vert=0; vert<mNumVertices; vert++)
-                    {
-                    mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB  ] = floatBuf.get(index+POS_ATTRIB  );
-                    mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+1] = floatBuf.get(index+POS_ATTRIB+1);
-                    mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+2] = floatBuf.get(index+POS_ATTRIB+2);
-                    mVertAttribs1[VERT1_ATTRIBS*vert+NOR_ATTRIB  ] = floatBuf.get(index+NOR_ATTRIB  );
-                    mVertAttribs1[VERT1_ATTRIBS*vert+NOR_ATTRIB+1] = floatBuf.get(index+NOR_ATTRIB+1);
-                    mVertAttribs1[VERT1_ATTRIBS*vert+NOR_ATTRIB+2] = floatBuf.get(index+NOR_ATTRIB+2);
-                    index+=vert1InFile;
-                    }
-                  floatBuf.position(index);
-                  break;
-         case 2 : float[] centers = new float[3*numEff];
-                  floatBuf.get(centers,0,3*numEff);
-
-                  if( mUseCenters )
-                    {
-                    for(int eff=0; eff<numEff; eff++)
-                      {
-                      mUBC.setEffectCenterNow(eff, centers[3*eff], centers[3*eff+1], centers[3*eff+2]);
-                      }
-                    }
-
-                  floatBuf.get(mVertAttribs1, 0, VERT1_ATTRIBS*mNumVertices);
-                  break;
-         default: DistortedLibrary.logMessage("MeshBase: Error: unknown mesh file version "+String.format("0x%08X", version));
-                  return 0;
-         }
-
-       floatBuf.get(mVertAttribs2, 0, VERT2_ATTRIBS*mNumVertices);
-       }
-
-     return BYTES_PER_FLOAT*( 4 + numEff+TEX_COMP_SIZE*numTex + VERT1_ATTRIBS*mNumVertices + VERT2_ATTRIBS*mNumVertices );
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * @y.exclude
- */
-   public int getLastVertexEff(int effComponent)
-     {
-     if( effComponent>=0 && effComponent<mTexComponent.size() )
-       {
-       return mEffComponent.get(effComponent);
-       }
-
-     return 0;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * @y.exclude
- */
-   public int getLastVertexTex(int texComponent)
-     {
-     if( texComponent>=0 && texComponent<mTexComponent.size() )
-       {
-       return mTexComponent.get(texComponent).mEndIndex;
-       }
-
-     return 0;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Write the Mesh to a File.
- */
-   public void write(DataOutputStream stream)
-     {
-     TexComponent tex;
-
-     int numTex = mTexComponent.size();
-     int numEff = mEffComponent.size();
-
-     try
-       {
-       stream.writeInt(2);  // version
-       stream.writeInt(mNumVertices);
-       stream.writeInt(numTex);
-       stream.writeInt(numEff);
-
-       for(int i=0; i<numTex; i++)
-         {
-         tex = mTexComponent.get(i);
-
-         stream.writeFloat((float)tex.mEndIndex);
-         stream.writeFloat(tex.mTextureMap.get0());
-         stream.writeFloat(tex.mTextureMap.get1());
-         stream.writeFloat(tex.mTextureMap.get2());
-         stream.writeFloat(tex.mTextureMap.get3());
-         }
-
-       for(int i=0; i<numEff; i++)
-         {
-         stream.writeFloat((float)mEffComponent.get(i));
-         }
-
-       float[] centers = mUseCenters ? mUBC.getBackingArray() : new float[4*numEff];
-
-       for(int i=0; i<numEff; i++)
-         {
-         stream.writeFloat(centers[4*i  ]);
-         stream.writeFloat(centers[4*i+1]);
-         stream.writeFloat(centers[4*i+2]);
-         }
-
-       ByteBuffer vertBuf1 = ByteBuffer.allocate(VERT1_SIZE*mNumVertices);
-       vertBuf1.asFloatBuffer().put(mVertAttribs1);
-       ByteBuffer vertBuf2 = ByteBuffer.allocate(VERT2_SIZE*mNumVertices);
-       vertBuf2.asFloatBuffer().put(mVertAttribs2);
-
-       stream.write(vertBuf1.array());
-       stream.write(vertBuf2.array());
-       }
-     catch(IOException ex)
-       {
-       DistortedLibrary.logMessage("MeshBase: IOException trying to write a mesh: "+ ex);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Mesh, do we want to render the Normal vectors as well?
- * <p>
- * Will work only on OpenGL ES >= 3.0 devices.
- *
- * @param show Controls if we render the Normal vectors or not.
- */
-   public void setShowNormals(boolean show)
-     {
-     mShowNormals = show;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this mesh, should we also draw the normal vectors?
- *
- * @return <i>true</i> if we do render normal vectors
- */
-   public boolean getShowNormals()
-     {
-     return mShowNormals;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Merge all texture components of this Mesh into a single one.
- */
-   public void mergeTexComponents()
-     {
-     if( mJobNode[0]==null )
-       {
-       mergeTexComponentsNow();
-       }
-     else
-       {
-       mJobNode[0] = DeferredJobs.mergeTex(this);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Merge all effect components of this Mesh into a single one.
- */
-   public void mergeEffComponents()
-     {
-     if( mJobNode[0]==null )
-       {
-       mergeEffComponentsNow();
-       }
-     else
-       {
-       mJobNode[0] = DeferredJobs.mergeEff(this);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Release all internal resources.
- */
-   public void markForDeletion()
-     {
-     mVertAttribs1 = null;
-     mVertAttribs2 = null;
-
-     mVBO1.markForDeletion();
-     mVBO2.markForDeletion();
-     mTFO.markForDeletion();
-     mUBA.markForDeletion();
-
-     if( mUBC!=null )
-       {
-       mUBC.markForDeletion();
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Apply a Matrix Effect to the components which match the (addAssoc,equAssoc) association.
- * <p>
- * This is a static, permanent modification of the vertices contained in this Mesh. If the effect
- * contains any Dynamics, they will be evaluated at 0.
- *
- * @param effect List of Matrix Effects to apply to the Mesh.
- * @param andAssoc 'Logical AND' association which defines which components will be affected.
- * @param equAssoc 'equality' association which defines which components will be affected.
- */
-   public void apply(MatrixEffect effect, int andAssoc, int equAssoc)
-     {
-     if( mJobNode[0]==null )
-       {
-       applyMatrix(effect,andAssoc,equAssoc);
-       }
-     else
-       {
-       mJobNode[0] = DeferredJobs.matrix(this,effect,andAssoc,equAssoc);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Apply a Vertex Effect to the vertex mesh.
- * <p>
- * This is a static, permanent modification of the vertices contained in this Mesh. If the effects
- * contain any Dynamics, the Dynamics will be evaluated at 0.
- * We would call this several times building up a list of Effects to do. This list of effects gets
- * lazily executed only when the Mesh is used for rendering for the first time.
- *
- * @param effect Vertex Effect to apply to the Mesh.
- */
-   public void apply(VertexEffect effect)
-     {
-     mJobNode[0] = DeferredJobs.vertex(this,effect);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets texture maps for (some of) the components of this mesh.
- * <p>
- * Format: ( x of lower-left corner, y of lower-left corner, width, height ).
- * For example maps[0] = new Static4D( 0.0, 0.5, 0.5, 0.5 ) sets the 0th component texture map to the
- * upper-left quadrant of the texture.
- * <p>
- * Probably the most common user case would be sending as many maps as there are components in this
- * Mesh. One can also send less, or more (the extraneous ones will be ignored) and set some of them
- * to null (those will be ignored as well). So if there are 5 components, and we want to set the map
- * of the 2nd and 4rd one, call this with
- * maps = new Static4D[4]
- * maps[0] = null
- * maps[1] = the map for the 2nd component
- * maps[2] = null
- * maps[3] = the map for the 4th component
- * A map's width and height have to be non-zero (but can be negative!)
- *
- * @param maps            List of texture maps to apply to the texture's components.
- * @param startComponent  the component the first of the maps refers to.
- */
-   public void setTextureMap(Static4D[] maps, int startComponent)
-     {
-     if( mJobNode[0]==null )
-       {
-       textureMap(maps,startComponent);
-       }
-     else
-       {
-       mJobNode[0] = DeferredJobs.textureMap(this,maps,startComponent);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the texture map of one of the components.
- *
- * @param component The component number whose texture map we want to retrieve.
- */
-   public Static4D getTextureMap(int component)
-     {
-     return (component>=0 && component<mTexComponent.size()) ? mTexComponent.get(component).mTextureMap : null;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set Effect association.
- * This creates an association between a Component of this Mesh and a Vertex Effect.
- * One can set two types of associations - an 'logical and' and a 'equal' associations and the Effect
- * will only be active on vertices of Components such that
- * (effect andAssoc) & (component andAssoc) != 0 || (effect equAssoc) == (mesh equAssoc)
- * (see main_vertex_shader)
- * The point: this way we can configure the system so that each Vertex Effect acts only on a certain
- * subset of a Mesh, thus potentially significantly reducing the number of render calls.
- * <p>
- * Only the bottom 31 bits of the 'andAssociation' are possible, the top one is taken by Postprocessing
- * Association [i.e. marking which components are disabled when we postprocess]
- */
-   public void setEffectAssociation(int component, int andAssociation, int equAssociation)
-     {
-     if( component>=0 && component<mMaxComponents )
-       {
-       if( mJobNode[0]==null )
-         {
-         setEffectAssociationNow(component, andAssociation, equAssociation);
-         }
-       else
-         {
-         mJobNode[0] = DeferredJobs.effectAssoc(this,component,andAssociation,equAssociation);
-         }
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set certain components to be not affected by any subsequent postprocessing.
- * Calling this the second time resets the list. Calling this with null makes all components
- * affected again.
- *
- * @param components list of components which will not be not affected by any Postprocessing.
- */
-  public void setComponentsNotAffectedByPostprocessing(int[] components)
-    {
-    if( mJobNode[0]==null )
-      {
-      setNotAffectedComponentsNow(components);
-      }
-    else
-      {
-      mJobNode[0] = DeferredJobs.setNotAffected(this,components);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set center of a Component.
- * A 'center' of a (effect) component is a 3D point in space. The array of centers gets sent to
- * the vertex shader as a Uniform Buffer Object; in the vertex shader we can then use it to compute
- * the 'Inflation' of the whole Mesh per-component. 'Inflation' is needed by postprocess effects to
- * add the 'halo' around an object.
- * This is all 'per-component' so that a user has a chance to make the halo look right in case of
- * non-convex meshes: then we need to ensure that each component of such a mesh is a convex
- * sub-mesh with its center being more-or-less the center of gravity of the sub-mesh.
- */
-   public void setComponentCenter(int component, float centerX, float centerY, float centerZ)
-     {
-     if( component>=0 && component<mMaxComponents )
-       {
-       if( mJobNode[0]==null )
-         {
-         setComponentCenterNow(component, centerX, centerY, centerZ);
-         }
-       else
-         {
-         mJobNode[0] = DeferredJobs.componentCenter(this,component,centerX, centerY, centerZ);
-         }
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set the centers of a all components to the same value in one go.
- */
-   public void setAllComponentCenters(float centerX, float centerY, float centerZ)
-     {
-     if( mJobNode[0]==null )
-       {
-       setComponentCenterNow(-1, centerX, centerY, centerZ);
-       }
-     else
-       {
-       mJobNode[0] = DeferredJobs.componentCenter(this,-1,centerX, centerY, centerZ);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Adds an empty (no vertices) texture component to the end of the text component list.
- * Sometimes we want to do this to have several Meshes with equal number of tex components each.
- */
-   public void addEmptyTexComponent()
-     {
-      if( mJobNode[0]==null )
-       {
-       addEmptyTexComponentNow();
-       }
-     else
-       {
-       mJobNode[0] = DeferredJobs.addEmptyTex(this);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the number of texture components, i.e. individual subsets of the whole set of vertices
- * which can be independently textured.
- *
- * @return The number of Texture Components of this Mesh.
- */
-   public int getNumTexComponents()
-     {
-     return mTexComponent.size();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the number of 'effect' components, i.e. individual subsets of the whole set of vertices
- * to which a VertexEffect can be addressed, independently of other vertices.
- *
- * @return The number of Effect Components of this Mesh.
- */
-   public int getNumEffComponents()
-     {
-     return mEffComponent.size();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy the Mesh.
- *
- * @param deep If to be a deep or shallow copy of mVertAttribs1, i.e. the array holding vertices,
- *             and normals (the rest, in particular the mVertAttribs2 containing texture
- *             coordinates and effect associations, is always deep copied)
- */
-   public abstract MeshBase copy(boolean deep);
-   }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshBase.kt b/src/main/java/org/distorted/library/mesh/MeshBase.kt
new file mode 100644
index 0000000..3e065e7
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshBase.kt
@@ -0,0 +1,1475 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 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 android.opengl.GLES30;
+
+import org.distorted.library.effect.MatrixEffect;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.effectqueue.EffectQueue;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.InternalBuffer;
+import org.distorted.library.program.DistortedProgram;
+import org.distorted.library.type.Static4D;
+import org.distorted.library.uniformblock.UniformBlockAssociation;
+import org.distorted.library.uniformblock.UniformBlockCenter;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Abstract class which represents a Mesh, ie an array of vertices (rendered as a TRIANGLE_STRIP).
+ * <p>
+ * If you want to render to a particular shape, extend from here, construct a float array
+ * containing per-vertex attributes, and call back setAttribs().
+ */
+public abstract class MeshBase
+   {
+   private static final int ASSOC_UBO_BINDING  = 3;
+   private static final int CENTER_UBO_BINDING = 4;
+
+   // sizes of attributes of an individual vertex.
+   private static final int POS_DATA_SIZE= 3; // vertex coordinates: x,y,z
+   private static final int NOR_DATA_SIZE= 3; // normal vector: x,y,z
+   private static final int TEX_DATA_SIZE= 2; // texture coordinates: s,t
+   private static final int COM_DATA_SIZE= 1; // component number, a single float
+
+   static final int POS_ATTRIB   = 0;
+   static final int NOR_ATTRIB   = POS_DATA_SIZE;
+   static final int TEX_ATTRIB   = 0;
+   static final int COM_ATTRIB   = TEX_DATA_SIZE;
+
+   static final int VERT1_ATTRIBS= POS_DATA_SIZE + NOR_DATA_SIZE;  // number of attributes of a vertex (the part changed by preapply)
+   static final int VERT2_ATTRIBS= TEX_DATA_SIZE + COM_DATA_SIZE;  // number of attributes of a vertex (the 'preapply invariant' part)
+   static final int TRAN_ATTRIBS = POS_DATA_SIZE + NOR_DATA_SIZE;  // number of attributes of a transform feedback vertex
+
+   private static final int BYTES_PER_FLOAT = 4;
+
+   private static final int OFFSET_POS = POS_ATTRIB*BYTES_PER_FLOAT;
+   private static final int OFFSET_NOR = NOR_ATTRIB*BYTES_PER_FLOAT;
+   private static final int OFFSET_TEX = TEX_ATTRIB*BYTES_PER_FLOAT;
+   private static final int OFFSET_COM = COM_ATTRIB*BYTES_PER_FLOAT;
+
+   private static final int TRAN_SIZE  = TRAN_ATTRIBS*BYTES_PER_FLOAT;
+   private static final int VERT1_SIZE = VERT1_ATTRIBS*BYTES_PER_FLOAT;
+   private static final int VERT2_SIZE = VERT2_ATTRIBS*BYTES_PER_FLOAT;
+
+   private static final int[] mCenterBlockIndex = new int[EffectQueue.MAIN_VARIANTS];
+   private static final int[] mAssocBlockIndex  = new int[EffectQueue.MAIN_VARIANTS];
+
+   private static final int TEX_COMP_SIZE = 5; // 5 four-byte entities inside the component
+
+   private static boolean mUseCenters;
+   private static int mStride;
+   private static int mMaxComponents = 100;
+
+   private boolean mShowNormals;              // when rendering this mesh, draw normal vectors?
+   private InternalBuffer mVBO1, mVBO2, mTFO; // main vertex buffer and transform feedback buffer
+   private int mNumVertices;
+   private float[] mVertAttribs1;             // packed: PosX,PosY,PosZ, NorX,NorY,NorZ
+   private float[] mVertAttribs2;             // packed: TexS,TexT, Component
+   private float mInflate;
+   private final UniformBlockAssociation mUBA;
+   private UniformBlockCenter mUBC;
+   private boolean mStrideCorrected;
+
+   DeferredJobs.JobNode[] mJobNode;
+
+   private static class TexComponent
+     {
+     private int mEndIndex;
+     private final Static4D mTextureMap;
+
+     TexComponent(int end)
+       {
+       mEndIndex  = end;
+       mTextureMap= new Static4D(0,0,1,1);
+       }
+     TexComponent(TexComponent original)
+       {
+       mEndIndex = original.mEndIndex;
+
+       float x = original.mTextureMap.get0();
+       float y = original.mTextureMap.get1();
+       float z = original.mTextureMap.get2();
+       float w = original.mTextureMap.get3();
+       mTextureMap = new Static4D(x,y,z,w);
+       }
+
+     void setMap(Static4D map)
+       {
+       mTextureMap.set(map.get0(),map.get1(),map.get2(),map.get3());
+       }
+     }
+
+   private ArrayList<TexComponent> mTexComponent;
+   private ArrayList<Integer> mEffComponent;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   MeshBase()
+     {
+     mShowNormals  = false;
+     mInflate      = 0.0f;
+     mTexComponent = new ArrayList<>();
+     mEffComponent = new ArrayList<>();
+
+     mJobNode = new DeferredJobs.JobNode[1];
+
+     mUBA = new UniformBlockAssociation();
+
+     if( mUseCenters ) mUBC = new UniformBlockCenter();
+
+     mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_DRAW);
+     mVBO2= new InternalBuffer(GLES30.GL_ARRAY_BUFFER             , GLES30.GL_STATIC_DRAW);
+     mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// copy constructor
+
+   MeshBase(MeshBase original, boolean deep)
+     {
+     mShowNormals= original.mShowNormals;
+     mInflate    = original.mInflate;
+     mNumVertices= original.mNumVertices;
+
+     mUBA = new UniformBlockAssociation(original.mUBA);
+
+     if( mUseCenters ) mUBC = new UniformBlockCenter(original.mUBC);
+
+     if( deep )
+       {
+       mJobNode = new DeferredJobs.JobNode[1];
+       if( original.mJobNode[0]==null ) copy(original);
+       else mJobNode[0] = DeferredJobs.copy(this,original);
+       }
+     else
+       {
+       mJobNode      = original.mJobNode;
+       mVBO1         = original.mVBO1;
+       mVertAttribs1 = original.mVertAttribs1;
+       shallowCopy(original);
+       }
+
+     mTFO = new InternalBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, GLES30.GL_STATIC_READ);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void copy(MeshBase original)
+     {
+     shallowCopy(original);
+
+     mVBO1= new InternalBuffer(GLES30.GL_ARRAY_BUFFER, GLES30.GL_STATIC_DRAW);
+     mVertAttribs1= new float[mNumVertices*VERT1_ATTRIBS];
+     System.arraycopy(original.mVertAttribs1,0,mVertAttribs1,0,mNumVertices*VERT1_ATTRIBS);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void shallowCopy(MeshBase original)
+     {
+     int texComSize = original.mTexComponent.size();
+     mTexComponent = new ArrayList<>();
+
+     for(int i=0; i<texComSize; i++)
+       {
+       TexComponent comp = new TexComponent(original.mTexComponent.get(i));
+       mTexComponent.add(comp);
+       }
+
+     mEffComponent = new ArrayList<>();
+     mEffComponent.addAll(original.mEffComponent);
+
+     mVBO2= new InternalBuffer(GLES30.GL_ARRAY_BUFFER, GLES30.GL_STATIC_DRAW);
+     mVertAttribs2= new float[mNumVertices*VERT2_ATTRIBS];
+     System.arraycopy(original.mVertAttribs2,0,mVertAttribs2,0,mNumVertices*VERT2_ATTRIBS);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void mergeTexComponentsNow()
+     {
+     int num = mTexComponent.size();
+
+     if( num>1 )
+       {
+       mTexComponent.clear();
+       mTexComponent.add(new TexComponent(mNumVertices-1));
+
+       mVBO2.invalidate();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void addEmptyTexComponentNow()
+     {
+     mTexComponent.add(new TexComponent(mNumVertices-1));
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void mergeEffComponentsNow()
+     {
+     int num = mEffComponent.size();
+
+     if( num>1 )
+       {
+       mEffComponent.clear();
+       mEffComponent.add(mNumVertices-1);
+
+       for(int index=0; index<mNumVertices; index++)
+         {
+         mVertAttribs2[VERT2_ATTRIBS*index+COM_ATTRIB] = 0;
+         }
+
+       mVBO2.invalidate();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void applyMatrix(MatrixEffect effect, int andAssoc, int equAssoc)
+     {
+     float[] matrixP  = new float[16];
+     float[] matrixV  = new float[16];
+     float[] uniforms = new float[7];
+     int start, end=-1, numComp = mEffComponent.size();
+
+     matrixP[0] = matrixP[5] = matrixP[10] = matrixP[15] = 1;
+     matrixV[0] = matrixV[5] = matrixV[10] = matrixV[15] = 1;
+
+     effect.compute(uniforms,0,0,0);
+     effect.apply(matrixP, matrixV, uniforms, 0);
+
+     for(int comp=0; comp<numComp; comp++)
+       {
+       start = end+1;
+       end   = mEffComponent.get(comp);
+
+       if( mUBA.matchesAssociation(comp, andAssoc, equAssoc) )
+         {
+         applyMatrixToComponent(matrixP, matrixV, start, end);
+         }
+       }
+
+     mVBO1.invalidate();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void applyMatrixToComponent(float[] matrixP, float[] matrixV, int start, int end)
+     {
+     float x,y,z;
+
+     for(int index=start*VERT1_ATTRIBS; index<=end*VERT1_ATTRIBS; index+=VERT1_ATTRIBS )
+       {
+       x = mVertAttribs1[index+POS_ATTRIB  ];
+       y = mVertAttribs1[index+POS_ATTRIB+1];
+       z = mVertAttribs1[index+POS_ATTRIB+2];
+
+       mVertAttribs1[index+POS_ATTRIB  ] = matrixP[0]*x + matrixP[4]*y + matrixP[ 8]*z + matrixP[12];
+       mVertAttribs1[index+POS_ATTRIB+1] = matrixP[1]*x + matrixP[5]*y + matrixP[ 9]*z + matrixP[13];
+       mVertAttribs1[index+POS_ATTRIB+2] = matrixP[2]*x + matrixP[6]*y + matrixP[10]*z + matrixP[14];
+
+       x = mVertAttribs1[index+NOR_ATTRIB  ];
+       y = mVertAttribs1[index+NOR_ATTRIB+1];
+       z = mVertAttribs1[index+NOR_ATTRIB+2];
+
+       mVertAttribs1[index+NOR_ATTRIB  ] = matrixV[0]*x + matrixV[4]*y + matrixV[ 8]*z;
+       mVertAttribs1[index+NOR_ATTRIB+1] = matrixV[1]*x + matrixV[5]*y + matrixV[ 9]*z;
+       mVertAttribs1[index+NOR_ATTRIB+2] = matrixV[2]*x + matrixV[6]*y + matrixV[10]*z;
+
+       x = mVertAttribs1[index+NOR_ATTRIB  ];
+       y = mVertAttribs1[index+NOR_ATTRIB+1];
+       z = mVertAttribs1[index+NOR_ATTRIB+2];
+
+       float len1 = (float)Math.sqrt(x*x + y*y + z*z);
+
+       if( len1>0.0f )
+         {
+         mVertAttribs1[index+NOR_ATTRIB  ] /= len1;
+         mVertAttribs1[index+NOR_ATTRIB+1] /= len1;
+         mVertAttribs1[index+NOR_ATTRIB+2] /= len1;
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void setEffectAssociationNow(int component, int andAssociation, int equAssociation)
+     {
+     mUBA.setEffectAssociationNow(component, andAssociation, equAssociation);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void setNotAffectedComponentsNow(int[] components)
+     {
+     mUBA.setNotAffectedComponentsNow(components);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void setComponentCenterNow(int component, float x, float y, float z)
+     {
+     if( mUBC!=null )
+       {
+       mUBC.setEffectCenterNow(component, x, y, z);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// when a derived class is done computing its mesh, it has to call this method.
+
+   void setAttribs(float[] vert1Attribs, float[] vert2Attribs)
+     {
+     mNumVertices = vert1Attribs.length/VERT1_ATTRIBS;
+     mVertAttribs1= vert1Attribs;
+     mVertAttribs2= vert2Attribs;
+
+     mTexComponent.add(new TexComponent(mNumVertices-1));
+     mEffComponent.add(mNumVertices-1);
+
+     mVBO1.invalidate();
+     mVBO2.invalidate();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void joinAttribs(MeshBase[] meshes)
+     {
+     MeshBase mesh;
+     TexComponent comp;
+     int numMeshes = meshes.length;
+     int numVertices,origVertices = mNumVertices;
+     int origTexComponents,numTexComponents;
+     int origEffComponents=0,numEffComponents;
+
+     if( origVertices>0 )
+       {
+       origTexComponents = mTexComponent.size();
+       mNumVertices+= ( mNumVertices%2==1 ? 2:1 );
+       mTexComponent.get(origTexComponents-1).mEndIndex = mNumVertices-1;
+       origEffComponents = mEffComponent.size();
+       mEffComponent.set(origEffComponents-1,mNumVertices-1);
+       }
+
+     for(int i=0; i<numMeshes; i++)
+       {
+       mesh = meshes[i];
+       numTexComponents = mesh.mTexComponent.size();
+       numEffComponents = mesh.mEffComponent.size();
+       numVertices = mesh.mNumVertices;
+
+       int extraVerticesBefore = mNumVertices==0 ? 0:1;
+       int extraVerticesAfter  = (i==numMeshes-1) ? 0 : (numVertices%2==1 ? 2:1);
+
+       for(int j=0; j<numTexComponents; j++)
+         {
+         comp = new TexComponent(mesh.mTexComponent.get(j));
+         comp.mEndIndex += (extraVerticesBefore+mNumVertices);
+         if( j==numTexComponents-1) comp.mEndIndex += extraVerticesAfter;
+         mTexComponent.add(comp);
+         }
+
+       for(int j=0; j<numEffComponents; j++)
+         {
+         int index = mesh.mEffComponent.get(j);
+         index += (extraVerticesBefore+mNumVertices);
+         if( j==numEffComponents-1 ) index += extraVerticesAfter;
+         mEffComponent.add(index);
+
+         if( origEffComponents<mMaxComponents )
+           {
+           mUBA.copy(origEffComponents, mesh.mUBA, j);
+           if( mUseCenters ) mUBC.copy(origEffComponents, mesh.mUBC, j);
+           origEffComponents++;
+           }
+         }
+
+       mNumVertices += (extraVerticesBefore+numVertices+extraVerticesAfter);
+       }
+
+     float[] newAttribs1 = new float[VERT1_ATTRIBS*mNumVertices];
+     float[] newAttribs2 = new float[VERT2_ATTRIBS*mNumVertices];
+     numVertices = origVertices;
+
+     if( origVertices>0 )
+       {
+       System.arraycopy(mVertAttribs1,                              0, newAttribs1,                          0, VERT1_ATTRIBS*numVertices);
+       System.arraycopy(mVertAttribs1, VERT1_ATTRIBS*(origVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS            );
+       System.arraycopy(mVertAttribs2,                              0, newAttribs2,                          0, VERT2_ATTRIBS*numVertices);
+       System.arraycopy(mVertAttribs2, VERT2_ATTRIBS*(origVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS            );
+       origVertices++;
+
+       if( numVertices%2==1 )
+         {
+         System.arraycopy(mVertAttribs1, VERT1_ATTRIBS*(origVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
+         System.arraycopy(mVertAttribs2, VERT2_ATTRIBS*(origVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
+         origVertices++;
+         }
+       }
+
+     for(int i=0; i<numMeshes; i++)
+       {
+       mesh = meshes[i];
+       numVertices = mesh.mNumVertices;
+
+       if( origVertices>0 )
+         {
+         System.arraycopy(mesh.mVertAttribs1, 0, newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
+         System.arraycopy(mesh.mVertAttribs2, 0, newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
+         origVertices++;
+         }
+       System.arraycopy(mesh.mVertAttribs1, 0, newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS*numVertices);
+       System.arraycopy(mesh.mVertAttribs2, 0, newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS*numVertices);
+       origVertices+=numVertices;
+
+       if( i<numMeshes-1 && numVertices>0 )
+         {
+         System.arraycopy(mesh.mVertAttribs1, VERT1_ATTRIBS*(numVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
+         System.arraycopy(mesh.mVertAttribs2, VERT2_ATTRIBS*(numVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
+         origVertices++;
+
+         if( numVertices%2==1 )
+           {
+           System.arraycopy(mesh.mVertAttribs1, VERT1_ATTRIBS*(numVertices-1), newAttribs1, VERT1_ATTRIBS*origVertices, VERT1_ATTRIBS);
+           System.arraycopy(mesh.mVertAttribs2, VERT2_ATTRIBS*(numVertices-1), newAttribs2, VERT2_ATTRIBS*origVertices, VERT2_ATTRIBS);
+           origVertices++;
+           }
+         }
+       }
+
+     if( origVertices!=mNumVertices )
+       {
+       DistortedLibrary.logMessage("MeshBase: join: origVertices: "+origVertices+" numVertices: "+mNumVertices);
+       }
+
+     int endIndex, index=0, numEffComp = mEffComponent.size();
+
+     for(int component=0; component<numEffComp; component++)
+       {
+       endIndex = mEffComponent.get(component);
+
+       for( ; index<=endIndex; index++) newAttribs2[VERT2_ATTRIBS*index+COM_ATTRIB] = component;
+       }
+
+     mVertAttribs1 = newAttribs1;
+     mVertAttribs2 = newAttribs2;
+     mVBO1.invalidate();
+     mVBO2.invalidate();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// called from MeshJoined
+
+   void join(MeshBase[] meshes)
+     {
+     boolean immediateJoin = true;
+
+     for (MeshBase mesh : meshes)
+       {
+       if (mesh.mJobNode[0] != null)
+         {
+         immediateJoin = false;
+         break;
+         }
+       }
+
+     if( immediateJoin ) joinAttribs(meshes);
+     else                mJobNode[0] = DeferredJobs.join(this,meshes);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void textureMap(Static4D[] maps, int startComponent)
+     {
+     int num_comp = mTexComponent.size();
+     if( startComponent>num_comp ) return;
+
+     int num_maps = maps.length;
+     int min = Math.min(num_comp-startComponent, num_maps);
+     int vertex = startComponent>0 ? mTexComponent.get(startComponent-1).mEndIndex+1 : 0;
+     Static4D newMap, oldMap;
+     TexComponent comp;
+     float newW, newH, ratW, ratH, movX, movY;
+
+     for(int i=0; i<min; i++)
+       {
+       newMap = maps[i];
+       comp = mTexComponent.get(i+startComponent);
+
+       if( newMap!=null )
+         {
+         newW = newMap.get2();
+         newH = newMap.get3();
+
+         if( newW!=0.0f && newH!=0.0f )
+           {
+           oldMap = comp.mTextureMap;
+           ratW = newW/oldMap.get2();
+           ratH = newH/oldMap.get3();
+           movX = newMap.get0() - ratW*oldMap.get0();
+           movY = newMap.get1() - ratH*oldMap.get1();
+
+           for( int index=vertex*VERT2_ATTRIBS+TEX_ATTRIB ; vertex<=comp.mEndIndex; vertex++, index+=VERT2_ATTRIBS)
+             {
+             mVertAttribs2[index  ] = ratW*mVertAttribs2[index  ] + movX;
+             mVertAttribs2[index+1] = ratH*mVertAttribs2[index+1] + movY;
+             }
+           comp.setMap(newMap);
+           }
+         }
+
+       vertex= comp.mEndIndex+1;
+       }
+
+     mVBO2.invalidate();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Are we going to need per-component centers (needed for correct postprocessing of concave meshes)
+ * Switching this on allocates 16*MAX_EFFECT_COMPONENTS bytes for uniforms in the vertex shader.
+ */
+   public static void setUseCenters()
+     {
+     mUseCenters = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Are we using per-component centers?
+ */
+   public static boolean getUseCenters()
+     {
+     return mUseCenters;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public static int getMaxEffComponents()
+     {
+     return mMaxComponents;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * How many mesh components are we going to need? Call before compilation of the shaders.
+ */
+   public static void setMaxEffComponents(int max)
+     {
+     mMaxComponents = max;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public static void getUniforms(int programH, int variant)
+     {
+     mCenterBlockIndex[variant]= GLES30.glGetUniformBlockIndex(programH, "componentCenter");
+     mAssocBlockIndex[variant] = GLES30.glGetUniformBlockIndex(programH, "componentAssociation");
+
+     if( mStride==0 )
+       {
+       String[] uniformNames = { "vComAssoc" };
+       int[] indices = new int[1];
+       int[] params  = new int[1];
+
+       GLES30.glGetUniformIndices(programH, uniformNames, indices, 0);
+       GLES30.glGetActiveUniformsiv( programH, 1, indices, 0, GLES30.GL_UNIFORM_ARRAY_STRIDE, params,0 );
+
+       mStride = params[0]/4;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void copyTransformToVertex()
+     {
+     ByteBuffer buffer = (ByteBuffer)GLES30.glMapBufferRange( GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0,
+                                                              TRAN_SIZE*mNumVertices, GLES30.GL_MAP_READ_BIT);
+     if( buffer!=null )
+       {
+       FloatBuffer feedback = buffer.order(ByteOrder.nativeOrder()).asFloatBuffer();
+       feedback.get(mVertAttribs1,0,VERT1_ATTRIBS*mNumVertices);
+       mVBO1.updateFloat(mVertAttribs1);
+       }
+     else
+       {
+       int error = GLES30.glGetError();
+       DistortedLibrary.logMessage("MeshBase: failed to map tf buffer, error="+error);
+       }
+
+     GLES30.glUnmapBuffer(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void debugJobs()
+     {
+     if( mJobNode[0]!=null )
+       {
+       mJobNode[0].print(0);
+       }
+     else
+       {
+       DistortedLibrary.logMessage("MeshBase: JobNode null");
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void printTexComponent(int comp)
+     {
+     if( comp>=0 && comp<getNumTexComponents() )
+       {
+       int beg = comp>0 ? mTexComponent.get(comp-1).mEndIndex+1 : 0;
+       int end = mTexComponent.get(comp).mEndIndex;
+       StringBuilder sb = new StringBuilder();
+
+       for( int vert=beg; vert<=end; vert++)
+         {
+         sb.append('(');
+         sb.append(mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB  ]);
+         sb.append(',');
+         sb.append(mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+1]);
+         sb.append(',');
+         sb.append(mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+2]);
+         sb.append(") ");
+         }
+
+       DistortedLibrary.logMessage("MeshBase: "+sb);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void printVertex(int v)
+     {
+     String pos = "\n pos: (" + mVertAttribs1[VERT1_ATTRIBS*v +POS_ATTRIB  ] + ","
+                              + mVertAttribs1[VERT1_ATTRIBS*v +POS_ATTRIB+1] + ","
+                              + mVertAttribs1[VERT1_ATTRIBS*v +POS_ATTRIB+2];
+
+     String nor = "\n nor: (" + mVertAttribs1[VERT1_ATTRIBS*v +NOR_ATTRIB  ] + ","
+                              + mVertAttribs1[VERT1_ATTRIBS*v +NOR_ATTRIB+1] + ","
+                              + mVertAttribs1[VERT1_ATTRIBS*v +NOR_ATTRIB+2];
+
+     String tex = "\n tex: (" + mVertAttribs2[VERT2_ATTRIBS*v +TEX_ATTRIB  ] + ","
+                              + mVertAttribs2[VERT2_ATTRIBS*v +TEX_ATTRIB+1];
+
+     DistortedLibrary.logMessage("MeshBase: first vertex:"+pos+nor+tex);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void printPos()
+     {
+     StringBuilder sb = new StringBuilder();
+
+     for(int i=0; i<mNumVertices; i++)
+       {
+       sb.append('(');
+       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+POS_ATTRIB  ]);
+       sb.append(',');
+       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+POS_ATTRIB+1]);
+       sb.append(',');
+       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+POS_ATTRIB+2]);
+       sb.append(")\n");
+       }
+     DistortedLibrary.logMessage("MeshBase: vertices: \n"+sb);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   /**
+    * Not part of public API, do not document (public only because has to be used from the main package)
+    *
+    * @y.exclude
+    */
+   public void printNor()
+     {
+     StringBuilder sb = new StringBuilder();
+
+     for(int i=0; i<mNumVertices; i++)
+       {
+       sb.append('(');
+       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+NOR_ATTRIB  ]);
+       sb.append(',');
+       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+NOR_ATTRIB+1]);
+       sb.append(',');
+       sb.append(mVertAttribs1[VERT1_ATTRIBS*i+NOR_ATTRIB+2]);
+       sb.append(")\n");
+       }
+     DistortedLibrary.logMessage("MeshBase: normals:\n"+sb);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void printCom()
+     {
+     StringBuilder sb = new StringBuilder();
+
+     for(int i=0; i<mNumVertices; i++)
+       {
+       sb.append(mVertAttribs2[VERT2_ATTRIBS*i+COM_ATTRIB]);
+       sb.append(' ');
+       }
+
+     DistortedLibrary.logMessage("MeshBase: "+sb);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void printTex()
+     {
+     int vert=0;
+     int num = getNumTexComponents();
+     StringBuilder sb = new StringBuilder();
+     sb.append("tex components: ").append(num);
+
+     for(int i=0; i<num; i++)
+       {
+       int end = mTexComponent.get(i).mEndIndex;
+       sb.append("\n");
+
+       for( ; vert<=end; vert++)
+         {
+         sb.append(' ');
+         sb.append('(');
+         sb.append(mVertAttribs2[VERT2_ATTRIBS*vert+TEX_ATTRIB]);
+         sb.append(',');
+         sb.append(mVertAttribs2[VERT2_ATTRIBS*vert+TEX_ATTRIB+1]);
+         sb.append(')');
+         }
+       }
+
+     DistortedLibrary.logMessage("MeshBase: "+sb);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public int getTFO()
+     {
+     return mTFO.createImmediatelyFloat(mNumVertices*TRAN_SIZE, null);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public int getNumVertices()
+     {
+     return mNumVertices;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void send(int programH, int variant)
+     {
+     if( !mStrideCorrected )
+       {
+       mStrideCorrected = true;
+       mUBA.correctStride(mStride);
+       }
+
+     int indexA = mUBA.getIndex();
+     GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, ASSOC_UBO_BINDING, indexA);
+     GLES30.glUniformBlockBinding(programH, mAssocBlockIndex[variant], ASSOC_UBO_BINDING);
+
+     if( mUseCenters )
+       {
+       int indexC = mUBC.getIndex();
+       GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, CENTER_UBO_BINDING, indexC);
+       GLES30.glUniformBlockBinding(programH, mCenterBlockIndex[variant], CENTER_UBO_BINDING);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void bindVertexAttribs(DistortedProgram program)
+     {
+     if( mJobNode[0]!=null )
+       {
+       mJobNode[0].execute();  // this will set itself to null
+       }
+
+     int index1 = mVBO1.createImmediatelyFloat(mNumVertices*VERT1_SIZE, mVertAttribs1);
+     int index2 = mVBO2.createImmediatelyFloat(mNumVertices*VERT2_SIZE, mVertAttribs2);
+     int[] attr = program.mAttribute;
+
+     switch(program.mAttributeLayout )
+       {
+       case DistortedProgram.ATTR_LAYOUT_PNTC:
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
+               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
+               GLES30.glVertexAttribPointer(attr[1], NOR_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_NOR);
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
+               GLES30.glVertexAttribPointer(attr[2], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
+               GLES30.glVertexAttribPointer(attr[3], COM_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_COM);
+               break;
+       case DistortedProgram.ATTR_LAYOUT_PNT:
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
+               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
+               GLES30.glVertexAttribPointer(attr[1], NOR_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_NOR);
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
+               GLES30.glVertexAttribPointer(attr[2], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
+               break;
+       case DistortedProgram.ATTR_LAYOUT_PNC:
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
+               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
+               GLES30.glVertexAttribPointer(attr[1], NOR_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_NOR);
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
+               GLES30.glVertexAttribPointer(attr[2], COM_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_COM);
+               break;
+       case DistortedProgram.ATTR_LAYOUT_PTC:
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
+               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
+               GLES30.glVertexAttribPointer(attr[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
+               GLES30.glVertexAttribPointer(attr[2], COM_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_COM);
+               break;
+       case DistortedProgram.ATTR_LAYOUT_PT:
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index1 );
+               GLES30.glVertexAttribPointer(attr[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, VERT1_SIZE, OFFSET_POS);
+               GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index2 );
+               GLES30.glVertexAttribPointer(attr[1], TEX_DATA_SIZE, GLES30.GL_FLOAT, false, VERT2_SIZE, OFFSET_TEX);
+               break;
+       case DistortedProgram.ATTR_LAYOUT_UNK:
+               DistortedLibrary.logMessage("Unknown attribute layout: "+program.mAttributeLayout);
+       }
+
+     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void bindTransformAttribs(DistortedProgram program)
+     {
+     int index = mTFO.createImmediatelyFloat(mNumVertices*TRAN_SIZE, null);
+
+     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, index );
+     GLES30.glVertexAttribPointer(program.mAttribute[0], POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, 0);
+     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public void setInflate(float inflate)
+     {
+     mInflate = inflate;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used from the main package)
+ *
+ * @y.exclude
+ */
+   public float getInflate()
+     {
+     return mInflate;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Return the number of bytes read.
+
+   int read(DataInputStream stream)
+     {
+     byte[] buffer = new byte[BYTES_PER_FLOAT*4];
+     int version, numEff, numTex;
+
+     //////////////////////////////////////////////////////////////////////////////////////////
+     // read version, number of vertices, number of tex components, number of effect components
+     //////////////////////////////////////////////////////////////////////////////////////////
+
+     try
+       {
+       stream.read(buffer);
+       ByteBuffer byteBuf = ByteBuffer.wrap(buffer);
+
+       version = byteBuf.getInt(0);
+
+       if( version==1 || version==2 )
+         {
+         mNumVertices= byteBuf.getInt(4);
+         numTex      = byteBuf.getInt(8);
+         numEff      = byteBuf.getInt(12);
+         }
+       else
+         {
+         DistortedLibrary.logMessage("MeshBase: Error: unknown mesh file version "+String.format("0x%08X", version));
+         return 0;
+         }
+       }
+     catch(Exception e)
+       {
+       DistortedLibrary.logMessage("MeshBase: Exception1 trying to read file: "+ e);
+       return 0;
+       }
+
+     //////////////////////////////////////////////////////////////////////////////////////////
+     // read contents of all texture and effect components
+     //////////////////////////////////////////////////////////////////////////////////////////
+
+     if( mNumVertices>0 && numEff>0 && numTex>0 )
+       {
+       int size = numEff+TEX_COMP_SIZE*numTex;
+       float[] tmp = new float[size];
+       mVertAttribs1 = new float[VERT1_ATTRIBS*mNumVertices];
+       mVertAttribs2 = new float[VERT2_ATTRIBS*mNumVertices];
+
+       // version 1 had extra 3 floats (the 'inflate' vector) in its vert1 array
+       int vert1InFile = version==2 ? VERT1_ATTRIBS : VERT1_ATTRIBS+3;
+       // ... and there was no center.
+       int centerSize  = version==2 ? 3*numEff : 0;
+
+       buffer = new byte[BYTES_PER_FLOAT*(size + centerSize + mNumVertices*(vert1InFile+VERT2_ATTRIBS))];
+
+       try
+         {
+         stream.read(buffer);
+         }
+       catch(Exception e)
+         {
+         DistortedLibrary.logMessage("MeshBase: Exception2 trying to read file: "+ e);
+         return 0;
+         }
+
+       ByteBuffer byteBuf = ByteBuffer.wrap(buffer);
+       FloatBuffer floatBuf = byteBuf.asFloatBuffer();
+
+       floatBuf.get(tmp,0,size);
+
+       TexComponent tex;
+       int index, texComp;
+       float x, y, z, w;
+
+       for(texComp=0; texComp<numTex; texComp++)
+         {
+         index = (int)tmp[TEX_COMP_SIZE*texComp];
+
+         x= tmp[TEX_COMP_SIZE*texComp+1];
+         y= tmp[TEX_COMP_SIZE*texComp+2];
+         z= tmp[TEX_COMP_SIZE*texComp+3];
+         w= tmp[TEX_COMP_SIZE*texComp+4];
+
+         tex = new TexComponent(index);
+         tex.setMap(new Static4D(x,y,z,w));
+
+         mTexComponent.add(tex);
+         }
+
+       for(int effComp=0; effComp<numEff; effComp++)
+         {
+         index = (int)tmp[TEX_COMP_SIZE*texComp + effComp ];
+         mEffComponent.add(index);
+         }
+
+       //////////////////////////////////////////////////////////////////////////////////////////
+       // read vert1 array, vert2 array and (if version>1) the component centers.
+       //////////////////////////////////////////////////////////////////////////////////////////
+
+       switch(version)
+         {
+         case 1 : index = floatBuf.position();
+
+                  for(int vert=0; vert<mNumVertices; vert++)
+                    {
+                    mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB  ] = floatBuf.get(index+POS_ATTRIB  );
+                    mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+1] = floatBuf.get(index+POS_ATTRIB+1);
+                    mVertAttribs1[VERT1_ATTRIBS*vert+POS_ATTRIB+2] = floatBuf.get(index+POS_ATTRIB+2);
+                    mVertAttribs1[VERT1_ATTRIBS*vert+NOR_ATTRIB  ] = floatBuf.get(index+NOR_ATTRIB  );
+                    mVertAttribs1[VERT1_ATTRIBS*vert+NOR_ATTRIB+1] = floatBuf.get(index+NOR_ATTRIB+1);
+                    mVertAttribs1[VERT1_ATTRIBS*vert+NOR_ATTRIB+2] = floatBuf.get(index+NOR_ATTRIB+2);
+                    index+=vert1InFile;
+                    }
+                  floatBuf.position(index);
+                  break;
+         case 2 : float[] centers = new float[3*numEff];
+                  floatBuf.get(centers,0,3*numEff);
+
+                  if( mUseCenters )
+                    {
+                    for(int eff=0; eff<numEff; eff++)
+                      {
+                      mUBC.setEffectCenterNow(eff, centers[3*eff], centers[3*eff+1], centers[3*eff+2]);
+                      }
+                    }
+
+                  floatBuf.get(mVertAttribs1, 0, VERT1_ATTRIBS*mNumVertices);
+                  break;
+         default: DistortedLibrary.logMessage("MeshBase: Error: unknown mesh file version "+String.format("0x%08X", version));
+                  return 0;
+         }
+
+       floatBuf.get(mVertAttribs2, 0, VERT2_ATTRIBS*mNumVertices);
+       }
+
+     return BYTES_PER_FLOAT*( 4 + numEff+TEX_COMP_SIZE*numTex + VERT1_ATTRIBS*mNumVertices + VERT2_ATTRIBS*mNumVertices );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * @y.exclude
+ */
+   public int getLastVertexEff(int effComponent)
+     {
+     if( effComponent>=0 && effComponent<mTexComponent.size() )
+       {
+       return mEffComponent.get(effComponent);
+       }
+
+     return 0;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * @y.exclude
+ */
+   public int getLastVertexTex(int texComponent)
+     {
+     if( texComponent>=0 && texComponent<mTexComponent.size() )
+       {
+       return mTexComponent.get(texComponent).mEndIndex;
+       }
+
+     return 0;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Write the Mesh to a File.
+ */
+   public void write(DataOutputStream stream)
+     {
+     TexComponent tex;
+
+     int numTex = mTexComponent.size();
+     int numEff = mEffComponent.size();
+
+     try
+       {
+       stream.writeInt(2);  // version
+       stream.writeInt(mNumVertices);
+       stream.writeInt(numTex);
+       stream.writeInt(numEff);
+
+       for(int i=0; i<numTex; i++)
+         {
+         tex = mTexComponent.get(i);
+
+         stream.writeFloat((float)tex.mEndIndex);
+         stream.writeFloat(tex.mTextureMap.get0());
+         stream.writeFloat(tex.mTextureMap.get1());
+         stream.writeFloat(tex.mTextureMap.get2());
+         stream.writeFloat(tex.mTextureMap.get3());
+         }
+
+       for(int i=0; i<numEff; i++)
+         {
+         stream.writeFloat((float)mEffComponent.get(i));
+         }
+
+       float[] centers = mUseCenters ? mUBC.getBackingArray() : new float[4*numEff];
+
+       for(int i=0; i<numEff; i++)
+         {
+         stream.writeFloat(centers[4*i  ]);
+         stream.writeFloat(centers[4*i+1]);
+         stream.writeFloat(centers[4*i+2]);
+         }
+
+       ByteBuffer vertBuf1 = ByteBuffer.allocate(VERT1_SIZE*mNumVertices);
+       vertBuf1.asFloatBuffer().put(mVertAttribs1);
+       ByteBuffer vertBuf2 = ByteBuffer.allocate(VERT2_SIZE*mNumVertices);
+       vertBuf2.asFloatBuffer().put(mVertAttribs2);
+
+       stream.write(vertBuf1.array());
+       stream.write(vertBuf2.array());
+       }
+     catch(IOException ex)
+       {
+       DistortedLibrary.logMessage("MeshBase: IOException trying to write a mesh: "+ ex);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Mesh, do we want to render the Normal vectors as well?
+ * <p>
+ * Will work only on OpenGL ES >= 3.0 devices.
+ *
+ * @param show Controls if we render the Normal vectors or not.
+ */
+   public void setShowNormals(boolean show)
+     {
+     mShowNormals = show;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this mesh, should we also draw the normal vectors?
+ *
+ * @return <i>true</i> if we do render normal vectors
+ */
+   public boolean getShowNormals()
+     {
+     return mShowNormals;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Merge all texture components of this Mesh into a single one.
+ */
+   public void mergeTexComponents()
+     {
+     if( mJobNode[0]==null )
+       {
+       mergeTexComponentsNow();
+       }
+     else
+       {
+       mJobNode[0] = DeferredJobs.mergeTex(this);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Merge all effect components of this Mesh into a single one.
+ */
+   public void mergeEffComponents()
+     {
+     if( mJobNode[0]==null )
+       {
+       mergeEffComponentsNow();
+       }
+     else
+       {
+       mJobNode[0] = DeferredJobs.mergeEff(this);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Release all internal resources.
+ */
+   public void markForDeletion()
+     {
+     mVertAttribs1 = null;
+     mVertAttribs2 = null;
+
+     mVBO1.markForDeletion();
+     mVBO2.markForDeletion();
+     mTFO.markForDeletion();
+     mUBA.markForDeletion();
+
+     if( mUBC!=null )
+       {
+       mUBC.markForDeletion();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Apply a Matrix Effect to the components which match the (addAssoc,equAssoc) association.
+ * <p>
+ * This is a static, permanent modification of the vertices contained in this Mesh. If the effect
+ * contains any Dynamics, they will be evaluated at 0.
+ *
+ * @param effect List of Matrix Effects to apply to the Mesh.
+ * @param andAssoc 'Logical AND' association which defines which components will be affected.
+ * @param equAssoc 'equality' association which defines which components will be affected.
+ */
+   public void apply(MatrixEffect effect, int andAssoc, int equAssoc)
+     {
+     if( mJobNode[0]==null )
+       {
+       applyMatrix(effect,andAssoc,equAssoc);
+       }
+     else
+       {
+       mJobNode[0] = DeferredJobs.matrix(this,effect,andAssoc,equAssoc);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Apply a Vertex Effect to the vertex mesh.
+ * <p>
+ * This is a static, permanent modification of the vertices contained in this Mesh. If the effects
+ * contain any Dynamics, the Dynamics will be evaluated at 0.
+ * We would call this several times building up a list of Effects to do. This list of effects gets
+ * lazily executed only when the Mesh is used for rendering for the first time.
+ *
+ * @param effect Vertex Effect to apply to the Mesh.
+ */
+   public void apply(VertexEffect effect)
+     {
+     mJobNode[0] = DeferredJobs.vertex(this,effect);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets texture maps for (some of) the components of this mesh.
+ * <p>
+ * Format: ( x of lower-left corner, y of lower-left corner, width, height ).
+ * For example maps[0] = new Static4D( 0.0, 0.5, 0.5, 0.5 ) sets the 0th component texture map to the
+ * upper-left quadrant of the texture.
+ * <p>
+ * Probably the most common user case would be sending as many maps as there are components in this
+ * Mesh. One can also send less, or more (the extraneous ones will be ignored) and set some of them
+ * to null (those will be ignored as well). So if there are 5 components, and we want to set the map
+ * of the 2nd and 4rd one, call this with
+ * maps = new Static4D[4]
+ * maps[0] = null
+ * maps[1] = the map for the 2nd component
+ * maps[2] = null
+ * maps[3] = the map for the 4th component
+ * A map's width and height have to be non-zero (but can be negative!)
+ *
+ * @param maps            List of texture maps to apply to the texture's components.
+ * @param startComponent  the component the first of the maps refers to.
+ */
+   public void setTextureMap(Static4D[] maps, int startComponent)
+     {
+     if( mJobNode[0]==null )
+       {
+       textureMap(maps,startComponent);
+       }
+     else
+       {
+       mJobNode[0] = DeferredJobs.textureMap(this,maps,startComponent);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the texture map of one of the components.
+ *
+ * @param component The component number whose texture map we want to retrieve.
+ */
+   public Static4D getTextureMap(int component)
+     {
+     return (component>=0 && component<mTexComponent.size()) ? mTexComponent.get(component).mTextureMap : null;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set Effect association.
+ * This creates an association between a Component of this Mesh and a Vertex Effect.
+ * One can set two types of associations - an 'logical and' and a 'equal' associations and the Effect
+ * will only be active on vertices of Components such that
+ * (effect andAssoc) & (component andAssoc) != 0 || (effect equAssoc) == (mesh equAssoc)
+ * (see main_vertex_shader)
+ * The point: this way we can configure the system so that each Vertex Effect acts only on a certain
+ * subset of a Mesh, thus potentially significantly reducing the number of render calls.
+ * <p>
+ * Only the bottom 31 bits of the 'andAssociation' are possible, the top one is taken by Postprocessing
+ * Association [i.e. marking which components are disabled when we postprocess]
+ */
+   public void setEffectAssociation(int component, int andAssociation, int equAssociation)
+     {
+     if( component>=0 && component<mMaxComponents )
+       {
+       if( mJobNode[0]==null )
+         {
+         setEffectAssociationNow(component, andAssociation, equAssociation);
+         }
+       else
+         {
+         mJobNode[0] = DeferredJobs.effectAssoc(this,component,andAssociation,equAssociation);
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set certain components to be not affected by any subsequent postprocessing.
+ * Calling this the second time resets the list. Calling this with null makes all components
+ * affected again.
+ *
+ * @param components list of components which will not be not affected by any Postprocessing.
+ */
+  public void setComponentsNotAffectedByPostprocessing(int[] components)
+    {
+    if( mJobNode[0]==null )
+      {
+      setNotAffectedComponentsNow(components);
+      }
+    else
+      {
+      mJobNode[0] = DeferredJobs.setNotAffected(this,components);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set center of a Component.
+ * A 'center' of a (effect) component is a 3D point in space. The array of centers gets sent to
+ * the vertex shader as a Uniform Buffer Object; in the vertex shader we can then use it to compute
+ * the 'Inflation' of the whole Mesh per-component. 'Inflation' is needed by postprocess effects to
+ * add the 'halo' around an object.
+ * This is all 'per-component' so that a user has a chance to make the halo look right in case of
+ * non-convex meshes: then we need to ensure that each component of such a mesh is a convex
+ * sub-mesh with its center being more-or-less the center of gravity of the sub-mesh.
+ */
+   public void setComponentCenter(int component, float centerX, float centerY, float centerZ)
+     {
+     if( component>=0 && component<mMaxComponents )
+       {
+       if( mJobNode[0]==null )
+         {
+         setComponentCenterNow(component, centerX, centerY, centerZ);
+         }
+       else
+         {
+         mJobNode[0] = DeferredJobs.componentCenter(this,component,centerX, centerY, centerZ);
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set the centers of a all components to the same value in one go.
+ */
+   public void setAllComponentCenters(float centerX, float centerY, float centerZ)
+     {
+     if( mJobNode[0]==null )
+       {
+       setComponentCenterNow(-1, centerX, centerY, centerZ);
+       }
+     else
+       {
+       mJobNode[0] = DeferredJobs.componentCenter(this,-1,centerX, centerY, centerZ);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds an empty (no vertices) texture component to the end of the text component list.
+ * Sometimes we want to do this to have several Meshes with equal number of tex components each.
+ */
+   public void addEmptyTexComponent()
+     {
+      if( mJobNode[0]==null )
+       {
+       addEmptyTexComponentNow();
+       }
+     else
+       {
+       mJobNode[0] = DeferredJobs.addEmptyTex(this);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the number of texture components, i.e. individual subsets of the whole set of vertices
+ * which can be independently textured.
+ *
+ * @return The number of Texture Components of this Mesh.
+ */
+   public int getNumTexComponents()
+     {
+     return mTexComponent.size();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the number of 'effect' components, i.e. individual subsets of the whole set of vertices
+ * to which a VertexEffect can be addressed, independently of other vertices.
+ *
+ * @return The number of Effect Components of this Mesh.
+ */
+   public int getNumEffComponents()
+     {
+     return mEffComponent.size();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy the Mesh.
+ *
+ * @param deep If to be a deep or shallow copy of mVertAttribs1, i.e. the array holding vertices,
+ *             and normals (the rest, in particular the mVertAttribs2 containing texture
+ *             coordinates and effect associations, is always deep copied)
+ */
+   public abstract MeshBase copy(boolean deep);
+   }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshCubes.java b/src/main/java/org/distorted/library/mesh/MeshCubes.java
deleted file mode 100644
index 7f26711..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshCubes.java
+++ /dev/null
@@ -1,928 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 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;
-import org.distorted.library.type.Static4D;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a 3D grid composed of Cubes.
- * <p>
- * Any subset of a MxNx1 cuboid is possible. (repeated arbitrary number of times into Z-dir)
- */
-public class MeshCubes extends MeshBase
-   {
-   private static final float R = 0.0f;
-
-   private static final int FRONT = 0;
-   private static final int BACK  = 1;
-   private static final int LEFT  = 2;
-   private static final int RIGHT = 3;
-   private static final int TOP   = 4;
-   private static final int BOTTOM= 5;
-
-   private static final int NORTH = 0;
-   private static final int WEST  = 1;
-   private static final int EAST  = 2;
-   private static final int SOUTH = 3;
-
-   private static final float[] mNormalX = new float[4];
-   private static final float[] mNormalY = new float[4];
-   private static final float[] mNormalZ = new float[4];
-
-   private static class Edge
-     {
-     final int side; 
-     final int row;
-     final int col;
-     
-     Edge(int s, int r, int c)
-       {
-       side= s; 
-       row = r;
-       col = c;
-       }
-     }
-
-   private float[] mTexMappingX,mTexMappingY, mTexMappingW, mTexMappingH;
-
-   private int mCols, mRows, mSlices;
-   private int[][] mCubes;
-   private byte[][] mInflateX, mInflateY;
-   private ArrayList<Edge> mEdges = new ArrayList<>();
-
-   private int currVert;
-   private int numVertices;
-   private int mSideBends;
-   private int mEdgeNum;
-   private int mSideWalls;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// a Block is split into two triangles along the NE-SW line iff it is in the top-right
-// or bottom-left quadrant of the grid.
-
-   private boolean isNE(int row,int col)
-     {
-     return ( (2*row<mRows)^(2*col<mCols) );
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// fill per-side texture mappings. Default: all 6 sides map to the whole texture.
-// Each Static4D describes the way its side will map. Format:
-//
-// 1st float: X-coord of the texture point that our top-left corner of the side maps to
-// 2nd float: Y-coord of the texture point that our top-left corner of the side maps to
-// 3rd float: X-coord of the texture point that our bot-right corner of the side maps to
-// 4th float: Y-coord of the texture point that our bot-right corner of the side maps to
-
-   private void fillTexMappings(Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
-     {
-     if( mTexMappingX==null ) mTexMappingX = new float[6];
-     if( mTexMappingY==null ) mTexMappingY = new float[6];
-     if( mTexMappingW==null ) mTexMappingW = new float[6];
-     if( mTexMappingH==null ) mTexMappingH = new float[6];
-
-     mTexMappingX[FRONT]  = front.get0();
-     mTexMappingY[FRONT]  = front.get1();
-     mTexMappingW[FRONT]  = front.get2() - front.get0();
-     mTexMappingH[FRONT]  = front.get3() - front.get1();
-
-     mTexMappingX[BACK]   = back.get0();
-     mTexMappingY[BACK]   = back.get1();
-     mTexMappingW[BACK]   = back.get2() - back.get0();
-     mTexMappingH[BACK]   = back.get3() - back.get1();
-
-     mTexMappingX[LEFT]   = left.get0();
-     mTexMappingY[LEFT]   = left.get1();
-     mTexMappingW[LEFT]   = left.get2() - left.get0();
-     mTexMappingH[LEFT]   = left.get3() - left.get1();
-
-     mTexMappingX[RIGHT]  = right.get0();
-     mTexMappingY[RIGHT]  = right.get1();
-     mTexMappingW[RIGHT]  = right.get2() - right.get0();
-     mTexMappingH[RIGHT]  = right.get3() - right.get1();
-
-     mTexMappingX[TOP]    = top.get0();
-     mTexMappingY[TOP]    = top.get1();
-     mTexMappingW[TOP]    = top.get2() - top.get0();
-     mTexMappingH[TOP]    = top.get3() - top.get1();
-
-     mTexMappingX[BOTTOM] = bottom.get0();
-     mTexMappingY[BOTTOM] = bottom.get1();
-     mTexMappingW[BOTTOM] = bottom.get2() - bottom.get0();
-     mTexMappingH[BOTTOM] = bottom.get3() - bottom.get1();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return the number of vertices our grid will contain
-
-   private int computeDataLength()
-      {
-      int frontWalls=0, frontSegments=0, triangleShifts=0, windingShifts=0;
-      int shiftCol = (mCols-1)/2;
-
-      boolean lastBlockIsNE=false;
-      boolean thisBlockIsNE;        // the block we are currently looking at is split into
-                                    // two triangles along the NE-SW line (rather than NW-SE)
-      for(int row=0; row<mRows; row++)
-        {
-        if( mCols>=2 && (mCubes[row][shiftCol]%2 == 1) && (mCubes[row][shiftCol+1]%2 == 1) ) triangleShifts++;
-
-        for(int col=0; col<mCols; col++)
-          {
-          if( mCubes[row][col]%2 == 1 )  // land
-            {
-            thisBlockIsNE = isNE(row,col);
-            if( thisBlockIsNE^lastBlockIsNE ) windingShifts++;
-            lastBlockIsNE = thisBlockIsNE;
-            frontWalls++;
-            if( col==mCols-1 || mCubes[row][col+1]%2 == 0 ) frontSegments++;
-            }
-          }
-        }
-
-      int frontVert       = 2*( frontWalls + 2*frontSegments - 1) +2*triangleShifts + windingShifts;
-      int sideVertOneSlice= 2*( mSideWalls + mSideBends + mEdgeNum -1);
-      int sideVert        = 2*(mSlices-1) + mSlices*sideVertOneSlice;
-      int firstWinding    = (mSlices>0 && (frontVert+1)%2==1 ) ? 1:0;
-      int dataL           = mSlices==0 ? frontVert : (frontVert+1) +firstWinding+ (1+sideVert+1) + (1+frontVert);
-
-      return dataL<0 ? 0:dataL;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void prepareDataStructures(int cols, String desc, int slices)
-     {
-     mRows       =0;
-     mCols       =0;
-     mSlices     =slices;
-     numVertices =0;
-
-     if( cols>0 && desc.contains("1") )
-       {
-       mCols = cols;
-       mRows = desc.length()/cols;
-
-       mCubes    = new int[mRows][mCols];
-       mInflateX = new byte[mRows+1][mCols+1];
-       mInflateY = new byte[mRows+1][mCols+1];
-
-       for(int col=0; col<mCols; col++)
-         for(int row=0; row<mRows; row++)
-           mCubes[row][col] = (desc.charAt(row * mCols + col) == '1' ? 1 : 0);
-
-       for(int col=0; col<mCols+1; col++)
-         for(int row=0; row<mRows+1; row++)
-           {
-           fillInflate(row,col);
-           }
-
-       markRegions();
-       numVertices = computeDataLength();
-       currVert = 0;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// full grid
-
-   private void prepareDataStructures(int cols, int rows, int slices)
-     {
-     mRows        =rows;
-     mCols        =cols;
-     mSlices      =slices;
-     numVertices  =0;
-
-     if( cols>0 && rows>0 )
-       {
-       mCubes    = new int[mRows][mCols];
-       mInflateX = new byte[mRows+1][mCols+1];
-       mInflateY = new byte[mRows+1][mCols+1];
-
-       for(int col=0; col<mCols; col++)
-         for(int row=0; row<mRows; row++)
-           mCubes[row][col] = 1;
-
-       for(int col=0; col<mCols+1; col++)
-         for(int row=0; row<mRows+1; row++)
-           {
-           fillInflate(row,col);
-           }
-
-       markRegions();
-       numVertices = computeDataLength();
-       currVert = 0;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Mark all the 'regions' of our grid  - i.e. separate pieces of 'land' (connected blocks that will 
-// be rendered) and 'water' (connected holes in between) with integers. Each connected block of land
-// gets a unique odd integer, each connected block of water a unique even integer.
-//
-// Water on the edges of the grid is also considered connected to itself!   
-//   
-// This function also creates a list of 'Edges'. Each Edge is a data structure from which later on we
-// will start building the side walls of each connected block of land (and sides of holes of water
-// inside). Each Edge needs to point from Land to Water (thus the '(SOUTH,row-1,col)' below) - otherwise
-// later on setting up normal vectors wouldn't work.
-   
-  private void markRegions()
-     {
-     int row, col, numWater=1, numLand=0;
-     
-     for(row=0; row<mRows; row++) if( mCubes[    row][      0]==0 ) markRegion((short)2,    row,       0);
-     for(row=0; row<mRows; row++) if( mCubes[    row][mCols-1]==0 ) markRegion((short)2,    row, mCols-1);
-     for(col=0; col<mCols; col++) if( mCubes[0      ][    col]==0 ) markRegion((short)2,      0,     col);
-     for(col=0; col<mCols; col++) if( mCubes[mRows-1][    col]==0 ) markRegion((short)2,mRows-1,     col);
-           
-     for(row=0; row<mRows; row++)
-        for(col=0; col<mCols; col++)
-           {
-           if( mCubes[row][col] == 0 ) { numWater++; markRegion( (short)(2*numWater ),row,col); mEdges.add(new Edge(SOUTH,row-1,col)); }
-           if( mCubes[row][col] == 1 ) { numLand ++; markRegion( (short)(2*numLand+1),row,col); mEdges.add(new Edge(NORTH,row  ,col)); }
-           }
-     
-     // now we potentially need to kick out some Edges . Otherwise the following does not work:
-     //
-     // 0 1 0
-     // 1 0 1
-     // 0 1 0
-     
-     mEdgeNum= mEdges.size();
-     int initCol, initRow, initSide, lastSide;
-     Edge e1,e2;
-     
-     for(int edge=0; edge<mEdgeNum; edge++)
-       {
-       e1 = mEdges.get(edge);
-       initRow= e1.row;
-       initCol= e1.col;
-       initSide=e1.side;
-
-       do
-         {
-         mSideWalls++;
-
-         if( e1.side==NORTH || e1.side==SOUTH )
-           {
-           for(int j=edge+1;j<mEdgeNum;j++)
-             {
-             e2 = mEdges.get(j);
-
-             if( e2.side==e1.side && e2.row==e1.row && e2.col==e1.col )
-               {
-               mEdges.remove(j);
-               mEdgeNum--;
-               j--;
-               }
-             }
-           }
-
-         lastSide = e1.side;
-         e1 = getNextEdge(e1);
-         if( e1.side!=lastSide ) mSideBends++;
-         }
-       while( e1.col!=initCol || e1.row!=initRow || e1.side!=initSide );
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// when calling, make sure that newVal != val
-   
-  private void markRegion(short newVal, int row, int col)
-     {
-     int val = mCubes[row][col];
-     mCubes[row][col] = newVal;
-     
-     if( row>0       && mCubes[row-1][col  ]==val ) markRegion(newVal, row-1, col  );
-     if( row<mRows-1 && mCubes[row+1][col  ]==val ) markRegion(newVal, row+1, col  );
-     if( col>0       && mCubes[row  ][col-1]==val ) markRegion(newVal, row  , col-1);
-     if( col<mCols-1 && mCubes[row  ][col+1]==val ) markRegion(newVal, row  , col+1);
-     }
-   
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  private void createNormals(boolean front, int row, int col)
-     {
-     int td,lr; 
-      
-     int nw = (col>0       && row>0      ) ? (mCubes[row-1][col-1]%2) : 0;
-     int w  = (col>0                     ) ? (mCubes[row  ][col-1]%2) : 0;
-     int n  = (               row>0      ) ? (mCubes[row-1][col  ]%2) : 0;
-     int c  =                                (mCubes[row  ][col  ]%2);
-     int sw = (col>0       && row<mRows-1) ? (mCubes[row+1][col-1]%2) : 0;
-     int s  = (               row<mRows-1) ? (mCubes[row+1][col  ]%2) : 0;
-     int ne = (col<mCols-1 && row>0      ) ? (mCubes[row-1][col+1]%2) : 0;
-     int e  = (col<mCols-1               ) ? (mCubes[row  ][col+1]%2) : 0;
-     int se = (col<mCols-1 && row<mRows-1) ? (mCubes[row+1][col+1]%2) : 0;
-
-     if(front)
-       {
-       mNormalZ[0] = 1.0f;
-       mNormalZ[1] = 1.0f;
-       mNormalZ[2] = 1.0f;
-       mNormalZ[3] = 1.0f;
-       }
-     else
-       {
-       mNormalZ[0] =-1.0f;
-       mNormalZ[1] =-1.0f;
-       mNormalZ[2] =-1.0f;
-       mNormalZ[3] =-1.0f;
-       }
-
-     td = nw+n-w-c;
-     lr = c+n-w-nw;
-     if( td<0 ) td=-1;
-     if( td>0 ) td= 1;
-     if( lr<0 ) lr=-1;
-     if( lr>0 ) lr= 1;
-     mNormalX[0] = lr*R;
-     mNormalY[0] = td*R;
-     
-     td = w+c-sw-s;
-     lr = c+s-w-sw;
-     if( td<0 ) td=-1;
-     if( td>0 ) td= 1;
-     if( lr<0 ) lr=-1;
-     if( lr>0 ) lr= 1;
-     mNormalX[1] = lr*R;
-     mNormalY[1] = td*R;
-     
-     td = n+ne-c-e;
-     lr = e+ne-c-n;
-     if( td<0 ) td=-1;
-     if( td>0 ) td= 1;
-     if( lr<0 ) lr=-1;
-     if( lr>0 ) lr= 1;
-     mNormalX[2] = lr*R;
-     mNormalY[2] = td*R;
-     
-     td = c+e-s-se;
-     lr = e+se-c-s;
-     if( td<0 ) td=-1;
-     if( td>0 ) td= 1;
-     if( lr<0 ) lr=-1;
-     if( lr>0 ) lr= 1;
-     mNormalX[3] = lr*R;
-     mNormalY[3] = td*R;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildFrontBackGrid(boolean front, float[] attribs1, float[] attribs2)
-     {
-     int last, current;
-     boolean seenLand=false;
-     boolean lastBlockIsNE = false;
-     boolean currentBlockIsNE;
-     float vectZ = (front ? 0.5f : -0.5f);
-
-     for(int row=0; row<mRows; row++)
-       {
-       last =0;
-         
-       for(int col=0; col<mCols; col++)
-         {
-         current = mCubes[row][col];
-
-         if( current%2 == 1 )
-           {
-           currentBlockIsNE = isNE(row,col);
-
-           if( !seenLand && !front && ((currVert%2==1)^currentBlockIsNE) )
-             {
-             repeatLast(attribs1,attribs2);
-             }
-
-           createNormals(front,row,col);
-
-           if( currentBlockIsNE )
-             {
-             if( (last!=current) || !lastBlockIsNE )
-               {
-               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
-               addFrontVertex( 0, vectZ, col, row, attribs1,attribs2);
-               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
-               if( !lastBlockIsNE || (!front && !seenLand) ) repeatLast(attribs1,attribs2);
-               addFrontVertex( 1, vectZ, col, row+1, attribs1,attribs2);
-               }
-             addFrontVertex( 2, vectZ, col+1, row  , attribs1,attribs2);
-             addFrontVertex( 3, vectZ, col+1, row+1, attribs1,attribs2);
-             }
-           else
-             {
-             if( (last!=current) || lastBlockIsNE )
-               {
-               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
-               addFrontVertex( 1, vectZ, col, row+1, attribs1,attribs2);
-               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
-               if( lastBlockIsNE || (!front && !seenLand) ) repeatLast(attribs1,attribs2);
-               addFrontVertex( 0, vectZ, col, row, attribs1,attribs2);
-               }
-             addFrontVertex( 3, vectZ, col+1, row+1, attribs1,attribs2);
-             addFrontVertex( 2, vectZ, col+1, row  , attribs1,attribs2);
-             }
-
-           seenLand = true;
-           lastBlockIsNE = currentBlockIsNE;
-           }
-            
-         last = current;
-         }
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSideGrid(float[] attribs1,float[] attribs2)
-     {
-     for(int i=0; i<mEdgeNum; i++)
-       {
-       buildIthSide(mEdges.get(i), attribs1, attribs2);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildIthSide(Edge curr, float[] attribs1, float[] attribs2)
-     {
-     Edge prev, next;
-     int col, row, side;
-
-     if( curr.side==NORTH ) // water outside
-       {
-       prev = new Edge(WEST,curr.row,curr.col);
-       }
-     else                   // land outside; we need to move forward one link because we are
-       {                    // going in opposite direction and we need to start from a bend.
-       prev = curr;
-       curr = new Edge(EAST,curr.row+1,curr.col-1);
-       }
-
-     for(int slice=0; slice<mSlices; slice++)
-       {
-       col = curr.col;
-       row = curr.row;
-       side= curr.side;
-       next = getNextEdge(curr);
-     
-       addSideVertex(curr,true,slice+1,prev.side,attribs1,attribs2);
-
-       do
-         {
-         if( prev.side!=curr.side )
-           {
-           addSideVertex(curr,true,slice+1,prev.side,attribs1,attribs2);
-           addSideVertex(curr,true,slice  ,prev.side,attribs1,attribs2);
-           }
-       
-         addSideVertex(curr,false,slice+1,next.side,attribs1,attribs2);
-         addSideVertex(curr,false,slice  ,next.side,attribs1,attribs2);
-       
-         prev = curr;
-         curr = next;
-         next = getNextEdge(curr);
-         }
-       while( curr.col!=col || curr.row!=row || curr.side!=side );
-     
-       repeatLast(attribs1,attribs2);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private Edge getNextEdge(Edge curr)
-     {
-     int col = curr.col;
-     int row = curr.row;
-
-     switch(curr.side) 
-       {
-       case NORTH: if( col==mCols-1 ) 
-                     return new Edge(EAST,row,col);
-                   if( row>0 && mCubes[row-1][col+1]==mCubes[row][col] )
-                     return new Edge(WEST,row-1,col+1);
-                   if( mCubes[row][col+1]==mCubes[row][col] )
-                     return new Edge(NORTH,row,col+1);
-                   else  
-                     return new Edge(EAST,row,col);
-                   
-       case SOUTH: if( col==0 ) 
-                     return new Edge(WEST,row,col);
-                   if( (row<mRows-1) && mCubes[row+1][col-1]==mCubes[row][col] )
-                     return new Edge(EAST,row+1,col-1); 
-                   if( mCubes[row][col-1]==mCubes[row][col] )
-                     return new Edge(SOUTH,row,col-1);
-                   else
-                     return new Edge(WEST,row,col); 
-                     
-       case EAST : if( row==mRows-1 ) 
-                     return new Edge(SOUTH,row,col);
-                   if( (col<mCols-1) && mCubes[row+1][col+1]==mCubes[row][col] )
-                     return new Edge(NORTH,row+1,col+1);
-                   if( mCubes[row+1][col]==mCubes[row][col] )
-                     return new Edge(EAST,row+1,col);
-                   else 
-                     return new Edge(SOUTH,row,col);
-                   
-       default   : if( row==0 )
-                     return new Edge(NORTH,row,col);
-                   if( col>0 && mCubes[row-1][col-1]==mCubes[row][col] )
-                     return new Edge(SOUTH,row-1,col-1);
-                   if( mCubes[row-1][col]==mCubes[row][col] )
-                     return new Edge(WEST,row-1,col);
-                   else
-                     return new Edge(NORTH,row,col);     
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fillInflate(int row, int col)
-    {
-    int diff;
-
-         if( col==0     ) mInflateX[row][col] = -1;
-    else if( col==mCols ) mInflateX[row][col] = +1;
-    else
-      {
-      if( row==0 )
-        {
-        diff = mCubes[0][col-1]-mCubes[0][col];
-        }
-      else if( row==mRows )
-        {
-        diff = mCubes[mRows-1][col-1]-mCubes[mRows-1][col];
-        }
-      else
-        {
-        diff = (mCubes[row  ][col-1]-mCubes[row  ][col]) +
-               (mCubes[row-1][col-1]-mCubes[row-1][col]) ;
-
-        if( diff==-2 ) diff=-1;
-        if( diff== 2 ) diff= 1;
-        }
-
-      mInflateX[row][col] = (byte)diff;
-      }
-
-         if( row==0     ) mInflateY[row][col] = +1;
-    else if( row==mRows ) mInflateY[row][col] = -1;
-    else
-      {
-      if( col==0 )
-        {
-        diff = mCubes[row][0]-mCubes[row-1][0];
-        }
-      else if( col==mCols )
-        {
-        diff = mCubes[row][mCols-1]-mCubes[row-1][mCols-1];
-        }
-      else
-        {
-        diff = (mCubes[row  ][col-1]+mCubes[row  ][col]) -
-               (mCubes[row-1][col-1]+mCubes[row-1][col]) ;
-
-        if( diff==-2 ) diff=-1;
-        if( diff== 2 ) diff= 1;
-        }
-
-      mInflateY[row][col] = (byte)diff;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addFrontVertex(int index, float vectZ, int col, int row, float[] attribs1, float[] attribs2)
-     {
-     float x = (float)col/mCols;
-     float y = (float)row/mRows;
-
-     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x-0.5f;
-     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = 0.5f-y;
-     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = vectZ;
-
-     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = mNormalX[index];
-     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = mNormalY[index];
-     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = mNormalZ[index];
-
-     if( vectZ>0 )
-       {
-       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[FRONT] +       x  * mTexMappingW[FRONT];
-       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[FRONT] + (1.0f-y) * mTexMappingH[FRONT];
-       }
-     else
-       {
-       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[BACK]  +       x  * mTexMappingW[BACK];
-       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[BACK]  + (1.0f-y) * mTexMappingH[BACK];
-       }
-
-     currVert++;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addSideVertex(Edge curr, boolean back, int slice, int side, float[] attribs1, float[] attribs2)
-     {
-     float x, y, z;
-     int row, col;
-
-     switch(curr.side)
-       {
-       case NORTH: row = curr.row;
-                   col = (back ? (curr.col  ):(curr.col+1));
-                   x = (float)col/mCols;
-                   y = 0.5f - (float)row/mRows;
-                   z = 0.5f - (float)slice/mSlices;
-
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x - 0.5f;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = y;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
-
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = side==NORTH ? 0.0f : (side==WEST?-R:R);
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = 1.0f;
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
-
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[TOP] +       x  * mTexMappingW[TOP];
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[TOP] + (0.5f-z) * mTexMappingH[TOP];
-
-                   break;
-       case SOUTH: row = curr.row+1;
-                   col = (back ? (curr.col+1):(curr.col));
-                   x = (float)col/mCols;
-                   y = 0.5f - (float)row/mRows;
-                   z = 0.5f - (float)slice/mSlices;
-
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x - 0.5f;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = y;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
-
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = side==SOUTH ? 0.0f: (side==EAST?-R:R);
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] =-1.0f;
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
-
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[BOTTOM] +       x  * mTexMappingW[BOTTOM];
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[BOTTOM] + (0.5f-z) * mTexMappingH[BOTTOM];
-
-                   break;
-       case WEST : row = (back  ? (curr.row+1):(curr.row));
-                   col = curr.col;
-                   x = (float)col/mCols -0.5f;
-                   y = (float)row/mRows;
-                   z = 0.5f - (float)slice/mSlices;
-
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = 0.5f - y;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
-
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] =-1.0f;
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = side==WEST ? 0.0f : (side==NORTH?-R:R);
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
-
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[LEFT] + (0.5f-z) * mTexMappingW[LEFT];
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[LEFT] + (1.0f-y) * mTexMappingH[LEFT];
-
-                   break;
-       case EAST : row = (back  ? (curr.row):(curr.row+1));
-                   col = (curr.col+1);
-                   x = (float)col/mCols -0.5f;
-                   y = (float)row/mRows;
-                   z = 0.5f - (float)slice/mSlices;
-
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = 0.5f - y;
-                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
-
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = 1.0f;
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = side==EAST ? 0.0f : (side==SOUTH?-R:R);
-                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
-
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[RIGHT] + (0.5f-z) * mTexMappingW[RIGHT];
-                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[RIGHT] + (1.0f-y) * mTexMappingH[RIGHT];
-
-                   break;
-       }
-
-     currVert++;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void repeatLast(float[] attribs1, float[] attribs2)
-     {
-     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currVert-1) + POS_ATTRIB  ];
-     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currVert-1) + POS_ATTRIB+1];
-     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currVert-1) + POS_ATTRIB+2];
-
-     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currVert-1) + NOR_ATTRIB  ];
-     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currVert-1) + NOR_ATTRIB+1];
-     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currVert-1) + NOR_ATTRIB+2];
-
-     attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = attribs2[VERT2_ATTRIBS*(currVert-1) + TEX_ATTRIB  ];
-     attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = attribs2[VERT2_ATTRIBS*(currVert-1) + TEX_ATTRIB+1];
-
-     currVert++;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void build()
-     {
-     float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
-     float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
-
-     buildFrontBackGrid(true,attribs1,attribs2);
-
-     if( mSlices>0 )
-       {
-       repeatLast(attribs1,attribs2);
-       if( currVert%2==1 ) repeatLast(attribs1,attribs2);
-       buildSideGrid(attribs1,attribs2);
-       buildFrontBackGrid(false,attribs1,attribs2);
-       }
-
-     mEdges.clear();
-     mEdges = null;
-     mCubes = null;
-     mInflateX = null;
-     mInflateY = null;
-
-     if( currVert!=numVertices )
-       DistortedLibrary.logMessage("MeshCubes: currVert " +currVert+" numVertices="+numVertices );
-
-     setAttribs(attribs1,attribs2);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Creates the underlying mesh of vertices, normals, texture coords.
- *    
- * @param cols   Integer helping to parse the next parameter.
- * @param desc   String describing the subset of a MxNx1 cuboid that we want to create.
- *               Its MxN characters - all 0 or 1 - decide of appropriate field is taken or not.
- *               <p></p>
- *               <p>
- *               <pre>
- *               For example, (cols=2, desc="111010", slices=1) describes the following shape:
- *
- *               XX
- *               X
- *               X
- *
- *               whereas (cols=2,desc="110001", slices=1) describes
- *
- *               XX
- *
- *                X
- *               </pre>
- *               </p>
- * @param slices Number of slices, i.e. 'depth' of the Mesh.
- */
- public MeshCubes(int cols, String desc, int slices)
-   {
-   super();
-   Static4D map = new Static4D(0.0f,0.0f,1.0f,1.0f);
-   fillTexMappings(map,map,map,map,map,map);
-   prepareDataStructures(cols,desc,slices);
-   build();
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Creates the underlying mesh of vertices, normals, texture coords with custom texture mappings.
- *
- * The mappings decide what part of the texture appears on a given side. Example:
- * front = (0.0,0.5,0.5,1.0) means that the front side of the grid will be textured with the
- * bottom-left quadrant of the texture.
- *
- * Default is: each of the 6 sides maps to the whole texture, i.e. (0.0,0.0,1.0,1.0)
- *
- * @param cols   Integer helping to parse the next parameter.
- * @param desc   String describing the subset of a MxNx1 cuboid that we want to create.
- *               Its MxN characters - all 0 or 1 - decide of appropriate field is taken or not.
- *               <p></p>
- *               <p>
- *               <pre>
- *               For example, (cols=2, desc="111010", slices=1) describes the following shape:
- *
- *               XX
- *               X
- *               X
- *
- *               whereas (cols=2,desc="110001", slices=1) describes
- *
- *               XX
- *
- *                X
- *               </pre>
- *               </p>
- * @param slices Number of slices, i.e. 'depth' of the Mesh.
- * @param front  Texture mapping the front side maps to.
- * @param back   Texture mapping the back side maps to.
- * @param left   Texture mapping the left side maps to.
- * @param right  Texture mapping the right side maps to.
- * @param top    Texture mapping the top side maps to.
- * @param bottom Texture mapping the bottom side maps to.
- */
- public MeshCubes(int cols, String desc, int slices, Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
-   {
-   super();
-   fillTexMappings(front,back,left,right,top,bottom);
-   prepareDataStructures(cols,desc,slices);
-   build();
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Creates a full, hole-less underlying mesh of vertices, normals, texture coords and colors.
- *
- * @param cols   Number of columns, i.e. 'width' of the Mesh.
- * @param rows   Number of rows, i.e. 'height' of the Mesh.
- * @param slices Number of slices, i.e. 'depth' of the Mesh.
- */
- public MeshCubes(int cols, int rows, int slices)
-   {
-   super();
-   Static4D map = new Static4D(0.0f,0.0f,1.0f,1.0f);
-   fillTexMappings(map,map,map,map,map,map);
-   prepareDataStructures(cols,rows,slices);
-   build();
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Creates a full, hole-less underlying mesh of vertices, normals, texture coords and colors with
- * custom texture mappings.
- *
- * The mappings decide what part of the texture appears on a given side. Example:
- * front = (0.0,0.5,0.5,1.0) means that the front side of the grid will be textured with the
- * bottom-left quadrant of the texture.
- *
- * Default is: each of the 6 sides maps to the whole texture, i.e. (0.0,0.0,1.0,1.0)
- *
- * @param cols   Number of columns, i.e. 'width' of the Mesh.
- * @param rows   Number of rows, i.e. 'height' of the Mesh.
- * @param slices Number of slices, i.e. 'depth' of the Mesh.
- * @param front  Texture mapping the front side maps to.
- * @param back   Texture mapping the back side maps to.
- * @param left   Texture mapping the left side maps to.
- * @param right  Texture mapping the right side maps to.
- * @param top    Texture mapping the top side maps to.
- * @param bottom Texture mapping the bottom side maps to.
- */
- public MeshCubes(int cols, int rows, int slices, Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
-   {
-   super();
-   fillTexMappings(front,back,left,right,top,bottom);
-   prepareDataStructures(cols,rows,slices);
-   build();
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
- public MeshCubes(MeshCubes 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 MeshCubes copy(boolean deep)
-   {
-   return new MeshCubes(this,deep);
-   }
- }
diff --git a/src/main/java/org/distorted/library/mesh/MeshCubes.kt b/src/main/java/org/distorted/library/mesh/MeshCubes.kt
new file mode 100644
index 0000000..7f26711
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshCubes.kt
@@ -0,0 +1,928 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 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;
+import org.distorted.library.type.Static4D;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a 3D grid composed of Cubes.
+ * <p>
+ * Any subset of a MxNx1 cuboid is possible. (repeated arbitrary number of times into Z-dir)
+ */
+public class MeshCubes extends MeshBase
+   {
+   private static final float R = 0.0f;
+
+   private static final int FRONT = 0;
+   private static final int BACK  = 1;
+   private static final int LEFT  = 2;
+   private static final int RIGHT = 3;
+   private static final int TOP   = 4;
+   private static final int BOTTOM= 5;
+
+   private static final int NORTH = 0;
+   private static final int WEST  = 1;
+   private static final int EAST  = 2;
+   private static final int SOUTH = 3;
+
+   private static final float[] mNormalX = new float[4];
+   private static final float[] mNormalY = new float[4];
+   private static final float[] mNormalZ = new float[4];
+
+   private static class Edge
+     {
+     final int side; 
+     final int row;
+     final int col;
+     
+     Edge(int s, int r, int c)
+       {
+       side= s; 
+       row = r;
+       col = c;
+       }
+     }
+
+   private float[] mTexMappingX,mTexMappingY, mTexMappingW, mTexMappingH;
+
+   private int mCols, mRows, mSlices;
+   private int[][] mCubes;
+   private byte[][] mInflateX, mInflateY;
+   private ArrayList<Edge> mEdges = new ArrayList<>();
+
+   private int currVert;
+   private int numVertices;
+   private int mSideBends;
+   private int mEdgeNum;
+   private int mSideWalls;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// a Block is split into two triangles along the NE-SW line iff it is in the top-right
+// or bottom-left quadrant of the grid.
+
+   private boolean isNE(int row,int col)
+     {
+     return ( (2*row<mRows)^(2*col<mCols) );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// fill per-side texture mappings. Default: all 6 sides map to the whole texture.
+// Each Static4D describes the way its side will map. Format:
+//
+// 1st float: X-coord of the texture point that our top-left corner of the side maps to
+// 2nd float: Y-coord of the texture point that our top-left corner of the side maps to
+// 3rd float: X-coord of the texture point that our bot-right corner of the side maps to
+// 4th float: Y-coord of the texture point that our bot-right corner of the side maps to
+
+   private void fillTexMappings(Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
+     {
+     if( mTexMappingX==null ) mTexMappingX = new float[6];
+     if( mTexMappingY==null ) mTexMappingY = new float[6];
+     if( mTexMappingW==null ) mTexMappingW = new float[6];
+     if( mTexMappingH==null ) mTexMappingH = new float[6];
+
+     mTexMappingX[FRONT]  = front.get0();
+     mTexMappingY[FRONT]  = front.get1();
+     mTexMappingW[FRONT]  = front.get2() - front.get0();
+     mTexMappingH[FRONT]  = front.get3() - front.get1();
+
+     mTexMappingX[BACK]   = back.get0();
+     mTexMappingY[BACK]   = back.get1();
+     mTexMappingW[BACK]   = back.get2() - back.get0();
+     mTexMappingH[BACK]   = back.get3() - back.get1();
+
+     mTexMappingX[LEFT]   = left.get0();
+     mTexMappingY[LEFT]   = left.get1();
+     mTexMappingW[LEFT]   = left.get2() - left.get0();
+     mTexMappingH[LEFT]   = left.get3() - left.get1();
+
+     mTexMappingX[RIGHT]  = right.get0();
+     mTexMappingY[RIGHT]  = right.get1();
+     mTexMappingW[RIGHT]  = right.get2() - right.get0();
+     mTexMappingH[RIGHT]  = right.get3() - right.get1();
+
+     mTexMappingX[TOP]    = top.get0();
+     mTexMappingY[TOP]    = top.get1();
+     mTexMappingW[TOP]    = top.get2() - top.get0();
+     mTexMappingH[TOP]    = top.get3() - top.get1();
+
+     mTexMappingX[BOTTOM] = bottom.get0();
+     mTexMappingY[BOTTOM] = bottom.get1();
+     mTexMappingW[BOTTOM] = bottom.get2() - bottom.get0();
+     mTexMappingH[BOTTOM] = bottom.get3() - bottom.get1();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return the number of vertices our grid will contain
+
+   private int computeDataLength()
+      {
+      int frontWalls=0, frontSegments=0, triangleShifts=0, windingShifts=0;
+      int shiftCol = (mCols-1)/2;
+
+      boolean lastBlockIsNE=false;
+      boolean thisBlockIsNE;        // the block we are currently looking at is split into
+                                    // two triangles along the NE-SW line (rather than NW-SE)
+      for(int row=0; row<mRows; row++)
+        {
+        if( mCols>=2 && (mCubes[row][shiftCol]%2 == 1) && (mCubes[row][shiftCol+1]%2 == 1) ) triangleShifts++;
+
+        for(int col=0; col<mCols; col++)
+          {
+          if( mCubes[row][col]%2 == 1 )  // land
+            {
+            thisBlockIsNE = isNE(row,col);
+            if( thisBlockIsNE^lastBlockIsNE ) windingShifts++;
+            lastBlockIsNE = thisBlockIsNE;
+            frontWalls++;
+            if( col==mCols-1 || mCubes[row][col+1]%2 == 0 ) frontSegments++;
+            }
+          }
+        }
+
+      int frontVert       = 2*( frontWalls + 2*frontSegments - 1) +2*triangleShifts + windingShifts;
+      int sideVertOneSlice= 2*( mSideWalls + mSideBends + mEdgeNum -1);
+      int sideVert        = 2*(mSlices-1) + mSlices*sideVertOneSlice;
+      int firstWinding    = (mSlices>0 && (frontVert+1)%2==1 ) ? 1:0;
+      int dataL           = mSlices==0 ? frontVert : (frontVert+1) +firstWinding+ (1+sideVert+1) + (1+frontVert);
+
+      return dataL<0 ? 0:dataL;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void prepareDataStructures(int cols, String desc, int slices)
+     {
+     mRows       =0;
+     mCols       =0;
+     mSlices     =slices;
+     numVertices =0;
+
+     if( cols>0 && desc.contains("1") )
+       {
+       mCols = cols;
+       mRows = desc.length()/cols;
+
+       mCubes    = new int[mRows][mCols];
+       mInflateX = new byte[mRows+1][mCols+1];
+       mInflateY = new byte[mRows+1][mCols+1];
+
+       for(int col=0; col<mCols; col++)
+         for(int row=0; row<mRows; row++)
+           mCubes[row][col] = (desc.charAt(row * mCols + col) == '1' ? 1 : 0);
+
+       for(int col=0; col<mCols+1; col++)
+         for(int row=0; row<mRows+1; row++)
+           {
+           fillInflate(row,col);
+           }
+
+       markRegions();
+       numVertices = computeDataLength();
+       currVert = 0;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// full grid
+
+   private void prepareDataStructures(int cols, int rows, int slices)
+     {
+     mRows        =rows;
+     mCols        =cols;
+     mSlices      =slices;
+     numVertices  =0;
+
+     if( cols>0 && rows>0 )
+       {
+       mCubes    = new int[mRows][mCols];
+       mInflateX = new byte[mRows+1][mCols+1];
+       mInflateY = new byte[mRows+1][mCols+1];
+
+       for(int col=0; col<mCols; col++)
+         for(int row=0; row<mRows; row++)
+           mCubes[row][col] = 1;
+
+       for(int col=0; col<mCols+1; col++)
+         for(int row=0; row<mRows+1; row++)
+           {
+           fillInflate(row,col);
+           }
+
+       markRegions();
+       numVertices = computeDataLength();
+       currVert = 0;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Mark all the 'regions' of our grid  - i.e. separate pieces of 'land' (connected blocks that will 
+// be rendered) and 'water' (connected holes in between) with integers. Each connected block of land
+// gets a unique odd integer, each connected block of water a unique even integer.
+//
+// Water on the edges of the grid is also considered connected to itself!   
+//   
+// This function also creates a list of 'Edges'. Each Edge is a data structure from which later on we
+// will start building the side walls of each connected block of land (and sides of holes of water
+// inside). Each Edge needs to point from Land to Water (thus the '(SOUTH,row-1,col)' below) - otherwise
+// later on setting up normal vectors wouldn't work.
+   
+  private void markRegions()
+     {
+     int row, col, numWater=1, numLand=0;
+     
+     for(row=0; row<mRows; row++) if( mCubes[    row][      0]==0 ) markRegion((short)2,    row,       0);
+     for(row=0; row<mRows; row++) if( mCubes[    row][mCols-1]==0 ) markRegion((short)2,    row, mCols-1);
+     for(col=0; col<mCols; col++) if( mCubes[0      ][    col]==0 ) markRegion((short)2,      0,     col);
+     for(col=0; col<mCols; col++) if( mCubes[mRows-1][    col]==0 ) markRegion((short)2,mRows-1,     col);
+           
+     for(row=0; row<mRows; row++)
+        for(col=0; col<mCols; col++)
+           {
+           if( mCubes[row][col] == 0 ) { numWater++; markRegion( (short)(2*numWater ),row,col); mEdges.add(new Edge(SOUTH,row-1,col)); }
+           if( mCubes[row][col] == 1 ) { numLand ++; markRegion( (short)(2*numLand+1),row,col); mEdges.add(new Edge(NORTH,row  ,col)); }
+           }
+     
+     // now we potentially need to kick out some Edges . Otherwise the following does not work:
+     //
+     // 0 1 0
+     // 1 0 1
+     // 0 1 0
+     
+     mEdgeNum= mEdges.size();
+     int initCol, initRow, initSide, lastSide;
+     Edge e1,e2;
+     
+     for(int edge=0; edge<mEdgeNum; edge++)
+       {
+       e1 = mEdges.get(edge);
+       initRow= e1.row;
+       initCol= e1.col;
+       initSide=e1.side;
+
+       do
+         {
+         mSideWalls++;
+
+         if( e1.side==NORTH || e1.side==SOUTH )
+           {
+           for(int j=edge+1;j<mEdgeNum;j++)
+             {
+             e2 = mEdges.get(j);
+
+             if( e2.side==e1.side && e2.row==e1.row && e2.col==e1.col )
+               {
+               mEdges.remove(j);
+               mEdgeNum--;
+               j--;
+               }
+             }
+           }
+
+         lastSide = e1.side;
+         e1 = getNextEdge(e1);
+         if( e1.side!=lastSide ) mSideBends++;
+         }
+       while( e1.col!=initCol || e1.row!=initRow || e1.side!=initSide );
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// when calling, make sure that newVal != val
+   
+  private void markRegion(short newVal, int row, int col)
+     {
+     int val = mCubes[row][col];
+     mCubes[row][col] = newVal;
+     
+     if( row>0       && mCubes[row-1][col  ]==val ) markRegion(newVal, row-1, col  );
+     if( row<mRows-1 && mCubes[row+1][col  ]==val ) markRegion(newVal, row+1, col  );
+     if( col>0       && mCubes[row  ][col-1]==val ) markRegion(newVal, row  , col-1);
+     if( col<mCols-1 && mCubes[row  ][col+1]==val ) markRegion(newVal, row  , col+1);
+     }
+   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  private void createNormals(boolean front, int row, int col)
+     {
+     int td,lr; 
+      
+     int nw = (col>0       && row>0      ) ? (mCubes[row-1][col-1]%2) : 0;
+     int w  = (col>0                     ) ? (mCubes[row  ][col-1]%2) : 0;
+     int n  = (               row>0      ) ? (mCubes[row-1][col  ]%2) : 0;
+     int c  =                                (mCubes[row  ][col  ]%2);
+     int sw = (col>0       && row<mRows-1) ? (mCubes[row+1][col-1]%2) : 0;
+     int s  = (               row<mRows-1) ? (mCubes[row+1][col  ]%2) : 0;
+     int ne = (col<mCols-1 && row>0      ) ? (mCubes[row-1][col+1]%2) : 0;
+     int e  = (col<mCols-1               ) ? (mCubes[row  ][col+1]%2) : 0;
+     int se = (col<mCols-1 && row<mRows-1) ? (mCubes[row+1][col+1]%2) : 0;
+
+     if(front)
+       {
+       mNormalZ[0] = 1.0f;
+       mNormalZ[1] = 1.0f;
+       mNormalZ[2] = 1.0f;
+       mNormalZ[3] = 1.0f;
+       }
+     else
+       {
+       mNormalZ[0] =-1.0f;
+       mNormalZ[1] =-1.0f;
+       mNormalZ[2] =-1.0f;
+       mNormalZ[3] =-1.0f;
+       }
+
+     td = nw+n-w-c;
+     lr = c+n-w-nw;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[0] = lr*R;
+     mNormalY[0] = td*R;
+     
+     td = w+c-sw-s;
+     lr = c+s-w-sw;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[1] = lr*R;
+     mNormalY[1] = td*R;
+     
+     td = n+ne-c-e;
+     lr = e+ne-c-n;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[2] = lr*R;
+     mNormalY[2] = td*R;
+     
+     td = c+e-s-se;
+     lr = e+se-c-s;
+     if( td<0 ) td=-1;
+     if( td>0 ) td= 1;
+     if( lr<0 ) lr=-1;
+     if( lr>0 ) lr= 1;
+     mNormalX[3] = lr*R;
+     mNormalY[3] = td*R;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildFrontBackGrid(boolean front, float[] attribs1, float[] attribs2)
+     {
+     int last, current;
+     boolean seenLand=false;
+     boolean lastBlockIsNE = false;
+     boolean currentBlockIsNE;
+     float vectZ = (front ? 0.5f : -0.5f);
+
+     for(int row=0; row<mRows; row++)
+       {
+       last =0;
+         
+       for(int col=0; col<mCols; col++)
+         {
+         current = mCubes[row][col];
+
+         if( current%2 == 1 )
+           {
+           currentBlockIsNE = isNE(row,col);
+
+           if( !seenLand && !front && ((currVert%2==1)^currentBlockIsNE) )
+             {
+             repeatLast(attribs1,attribs2);
+             }
+
+           createNormals(front,row,col);
+
+           if( currentBlockIsNE )
+             {
+             if( (last!=current) || !lastBlockIsNE )
+               {
+               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
+               addFrontVertex( 0, vectZ, col, row, attribs1,attribs2);
+               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
+               if( !lastBlockIsNE || (!front && !seenLand) ) repeatLast(attribs1,attribs2);
+               addFrontVertex( 1, vectZ, col, row+1, attribs1,attribs2);
+               }
+             addFrontVertex( 2, vectZ, col+1, row  , attribs1,attribs2);
+             addFrontVertex( 3, vectZ, col+1, row+1, attribs1,attribs2);
+             }
+           else
+             {
+             if( (last!=current) || lastBlockIsNE )
+               {
+               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
+               addFrontVertex( 1, vectZ, col, row+1, attribs1,attribs2);
+               if( seenLand  && (last != current) ) repeatLast(attribs1,attribs2);
+               if( lastBlockIsNE || (!front && !seenLand) ) repeatLast(attribs1,attribs2);
+               addFrontVertex( 0, vectZ, col, row, attribs1,attribs2);
+               }
+             addFrontVertex( 3, vectZ, col+1, row+1, attribs1,attribs2);
+             addFrontVertex( 2, vectZ, col+1, row  , attribs1,attribs2);
+             }
+
+           seenLand = true;
+           lastBlockIsNE = currentBlockIsNE;
+           }
+            
+         last = current;
+         }
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildSideGrid(float[] attribs1,float[] attribs2)
+     {
+     for(int i=0; i<mEdgeNum; i++)
+       {
+       buildIthSide(mEdges.get(i), attribs1, attribs2);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildIthSide(Edge curr, float[] attribs1, float[] attribs2)
+     {
+     Edge prev, next;
+     int col, row, side;
+
+     if( curr.side==NORTH ) // water outside
+       {
+       prev = new Edge(WEST,curr.row,curr.col);
+       }
+     else                   // land outside; we need to move forward one link because we are
+       {                    // going in opposite direction and we need to start from a bend.
+       prev = curr;
+       curr = new Edge(EAST,curr.row+1,curr.col-1);
+       }
+
+     for(int slice=0; slice<mSlices; slice++)
+       {
+       col = curr.col;
+       row = curr.row;
+       side= curr.side;
+       next = getNextEdge(curr);
+     
+       addSideVertex(curr,true,slice+1,prev.side,attribs1,attribs2);
+
+       do
+         {
+         if( prev.side!=curr.side )
+           {
+           addSideVertex(curr,true,slice+1,prev.side,attribs1,attribs2);
+           addSideVertex(curr,true,slice  ,prev.side,attribs1,attribs2);
+           }
+       
+         addSideVertex(curr,false,slice+1,next.side,attribs1,attribs2);
+         addSideVertex(curr,false,slice  ,next.side,attribs1,attribs2);
+       
+         prev = curr;
+         curr = next;
+         next = getNextEdge(curr);
+         }
+       while( curr.col!=col || curr.row!=row || curr.side!=side );
+     
+       repeatLast(attribs1,attribs2);
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Edge getNextEdge(Edge curr)
+     {
+     int col = curr.col;
+     int row = curr.row;
+
+     switch(curr.side) 
+       {
+       case NORTH: if( col==mCols-1 ) 
+                     return new Edge(EAST,row,col);
+                   if( row>0 && mCubes[row-1][col+1]==mCubes[row][col] )
+                     return new Edge(WEST,row-1,col+1);
+                   if( mCubes[row][col+1]==mCubes[row][col] )
+                     return new Edge(NORTH,row,col+1);
+                   else  
+                     return new Edge(EAST,row,col);
+                   
+       case SOUTH: if( col==0 ) 
+                     return new Edge(WEST,row,col);
+                   if( (row<mRows-1) && mCubes[row+1][col-1]==mCubes[row][col] )
+                     return new Edge(EAST,row+1,col-1); 
+                   if( mCubes[row][col-1]==mCubes[row][col] )
+                     return new Edge(SOUTH,row,col-1);
+                   else
+                     return new Edge(WEST,row,col); 
+                     
+       case EAST : if( row==mRows-1 ) 
+                     return new Edge(SOUTH,row,col);
+                   if( (col<mCols-1) && mCubes[row+1][col+1]==mCubes[row][col] )
+                     return new Edge(NORTH,row+1,col+1);
+                   if( mCubes[row+1][col]==mCubes[row][col] )
+                     return new Edge(EAST,row+1,col);
+                   else 
+                     return new Edge(SOUTH,row,col);
+                   
+       default   : if( row==0 )
+                     return new Edge(NORTH,row,col);
+                   if( col>0 && mCubes[row-1][col-1]==mCubes[row][col] )
+                     return new Edge(SOUTH,row-1,col-1);
+                   if( mCubes[row-1][col]==mCubes[row][col] )
+                     return new Edge(WEST,row-1,col);
+                   else
+                     return new Edge(NORTH,row,col);     
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void fillInflate(int row, int col)
+    {
+    int diff;
+
+         if( col==0     ) mInflateX[row][col] = -1;
+    else if( col==mCols ) mInflateX[row][col] = +1;
+    else
+      {
+      if( row==0 )
+        {
+        diff = mCubes[0][col-1]-mCubes[0][col];
+        }
+      else if( row==mRows )
+        {
+        diff = mCubes[mRows-1][col-1]-mCubes[mRows-1][col];
+        }
+      else
+        {
+        diff = (mCubes[row  ][col-1]-mCubes[row  ][col]) +
+               (mCubes[row-1][col-1]-mCubes[row-1][col]) ;
+
+        if( diff==-2 ) diff=-1;
+        if( diff== 2 ) diff= 1;
+        }
+
+      mInflateX[row][col] = (byte)diff;
+      }
+
+         if( row==0     ) mInflateY[row][col] = +1;
+    else if( row==mRows ) mInflateY[row][col] = -1;
+    else
+      {
+      if( col==0 )
+        {
+        diff = mCubes[row][0]-mCubes[row-1][0];
+        }
+      else if( col==mCols )
+        {
+        diff = mCubes[row][mCols-1]-mCubes[row-1][mCols-1];
+        }
+      else
+        {
+        diff = (mCubes[row  ][col-1]+mCubes[row  ][col]) -
+               (mCubes[row-1][col-1]+mCubes[row-1][col]) ;
+
+        if( diff==-2 ) diff=-1;
+        if( diff== 2 ) diff= 1;
+        }
+
+      mInflateY[row][col] = (byte)diff;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addFrontVertex(int index, float vectZ, int col, int row, float[] attribs1, float[] attribs2)
+     {
+     float x = (float)col/mCols;
+     float y = (float)row/mRows;
+
+     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x-0.5f;
+     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = 0.5f-y;
+     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = vectZ;
+
+     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = mNormalX[index];
+     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = mNormalY[index];
+     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = mNormalZ[index];
+
+     if( vectZ>0 )
+       {
+       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[FRONT] +       x  * mTexMappingW[FRONT];
+       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[FRONT] + (1.0f-y) * mTexMappingH[FRONT];
+       }
+     else
+       {
+       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[BACK]  +       x  * mTexMappingW[BACK];
+       attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[BACK]  + (1.0f-y) * mTexMappingH[BACK];
+       }
+
+     currVert++;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addSideVertex(Edge curr, boolean back, int slice, int side, float[] attribs1, float[] attribs2)
+     {
+     float x, y, z;
+     int row, col;
+
+     switch(curr.side)
+       {
+       case NORTH: row = curr.row;
+                   col = (back ? (curr.col  ):(curr.col+1));
+                   x = (float)col/mCols;
+                   y = 0.5f - (float)row/mRows;
+                   z = 0.5f - (float)slice/mSlices;
+
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x - 0.5f;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = y;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
+
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = side==NORTH ? 0.0f : (side==WEST?-R:R);
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = 1.0f;
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
+
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[TOP] +       x  * mTexMappingW[TOP];
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[TOP] + (0.5f-z) * mTexMappingH[TOP];
+
+                   break;
+       case SOUTH: row = curr.row+1;
+                   col = (back ? (curr.col+1):(curr.col));
+                   x = (float)col/mCols;
+                   y = 0.5f - (float)row/mRows;
+                   z = 0.5f - (float)slice/mSlices;
+
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x - 0.5f;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = y;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
+
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = side==SOUTH ? 0.0f: (side==EAST?-R:R);
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] =-1.0f;
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
+
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[BOTTOM] +       x  * mTexMappingW[BOTTOM];
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[BOTTOM] + (0.5f-z) * mTexMappingH[BOTTOM];
+
+                   break;
+       case WEST : row = (back  ? (curr.row+1):(curr.row));
+                   col = curr.col;
+                   x = (float)col/mCols -0.5f;
+                   y = (float)row/mRows;
+                   z = 0.5f - (float)slice/mSlices;
+
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = 0.5f - y;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
+
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] =-1.0f;
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = side==WEST ? 0.0f : (side==NORTH?-R:R);
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
+
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[LEFT] + (0.5f-z) * mTexMappingW[LEFT];
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[LEFT] + (1.0f-y) * mTexMappingH[LEFT];
+
+                   break;
+       case EAST : row = (back  ? (curr.row):(curr.row+1));
+                   col = (curr.col+1);
+                   x = (float)col/mCols -0.5f;
+                   y = (float)row/mRows;
+                   z = 0.5f - (float)slice/mSlices;
+
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = x;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = 0.5f - y;
+                   attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = z;
+
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = 1.0f;
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = side==EAST ? 0.0f : (side==SOUTH?-R:R);
+                   attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = (slice==0 ? R : (slice==mSlices ? -R:0) );
+
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = mTexMappingX[RIGHT] + (0.5f-z) * mTexMappingW[RIGHT];
+                   attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = mTexMappingY[RIGHT] + (1.0f-y) * mTexMappingH[RIGHT];
+
+                   break;
+       }
+
+     currVert++;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void repeatLast(float[] attribs1, float[] attribs2)
+     {
+     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currVert-1) + POS_ATTRIB  ];
+     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currVert-1) + POS_ATTRIB+1];
+     attribs1[VERT1_ATTRIBS*currVert + POS_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currVert-1) + POS_ATTRIB+2];
+
+     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currVert-1) + NOR_ATTRIB  ];
+     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currVert-1) + NOR_ATTRIB+1];
+     attribs1[VERT1_ATTRIBS*currVert + NOR_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currVert-1) + NOR_ATTRIB+2];
+
+     attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB  ] = attribs2[VERT2_ATTRIBS*(currVert-1) + TEX_ATTRIB  ];
+     attribs2[VERT2_ATTRIBS*currVert + TEX_ATTRIB+1] = attribs2[VERT2_ATTRIBS*(currVert-1) + TEX_ATTRIB+1];
+
+     currVert++;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void build()
+     {
+     float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+     float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+     buildFrontBackGrid(true,attribs1,attribs2);
+
+     if( mSlices>0 )
+       {
+       repeatLast(attribs1,attribs2);
+       if( currVert%2==1 ) repeatLast(attribs1,attribs2);
+       buildSideGrid(attribs1,attribs2);
+       buildFrontBackGrid(false,attribs1,attribs2);
+       }
+
+     mEdges.clear();
+     mEdges = null;
+     mCubes = null;
+     mInflateX = null;
+     mInflateY = null;
+
+     if( currVert!=numVertices )
+       DistortedLibrary.logMessage("MeshCubes: currVert " +currVert+" numVertices="+numVertices );
+
+     setAttribs(attribs1,attribs2);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates the underlying mesh of vertices, normals, texture coords.
+ *    
+ * @param cols   Integer helping to parse the next parameter.
+ * @param desc   String describing the subset of a MxNx1 cuboid that we want to create.
+ *               Its MxN characters - all 0 or 1 - decide of appropriate field is taken or not.
+ *               <p></p>
+ *               <p>
+ *               <pre>
+ *               For example, (cols=2, desc="111010", slices=1) describes the following shape:
+ *
+ *               XX
+ *               X
+ *               X
+ *
+ *               whereas (cols=2,desc="110001", slices=1) describes
+ *
+ *               XX
+ *
+ *                X
+ *               </pre>
+ *               </p>
+ * @param slices Number of slices, i.e. 'depth' of the Mesh.
+ */
+ public MeshCubes(int cols, String desc, int slices)
+   {
+   super();
+   Static4D map = new Static4D(0.0f,0.0f,1.0f,1.0f);
+   fillTexMappings(map,map,map,map,map,map);
+   prepareDataStructures(cols,desc,slices);
+   build();
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates the underlying mesh of vertices, normals, texture coords with custom texture mappings.
+ *
+ * The mappings decide what part of the texture appears on a given side. Example:
+ * front = (0.0,0.5,0.5,1.0) means that the front side of the grid will be textured with the
+ * bottom-left quadrant of the texture.
+ *
+ * Default is: each of the 6 sides maps to the whole texture, i.e. (0.0,0.0,1.0,1.0)
+ *
+ * @param cols   Integer helping to parse the next parameter.
+ * @param desc   String describing the subset of a MxNx1 cuboid that we want to create.
+ *               Its MxN characters - all 0 or 1 - decide of appropriate field is taken or not.
+ *               <p></p>
+ *               <p>
+ *               <pre>
+ *               For example, (cols=2, desc="111010", slices=1) describes the following shape:
+ *
+ *               XX
+ *               X
+ *               X
+ *
+ *               whereas (cols=2,desc="110001", slices=1) describes
+ *
+ *               XX
+ *
+ *                X
+ *               </pre>
+ *               </p>
+ * @param slices Number of slices, i.e. 'depth' of the Mesh.
+ * @param front  Texture mapping the front side maps to.
+ * @param back   Texture mapping the back side maps to.
+ * @param left   Texture mapping the left side maps to.
+ * @param right  Texture mapping the right side maps to.
+ * @param top    Texture mapping the top side maps to.
+ * @param bottom Texture mapping the bottom side maps to.
+ */
+ public MeshCubes(int cols, String desc, int slices, Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
+   {
+   super();
+   fillTexMappings(front,back,left,right,top,bottom);
+   prepareDataStructures(cols,desc,slices);
+   build();
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates a full, hole-less underlying mesh of vertices, normals, texture coords and colors.
+ *
+ * @param cols   Number of columns, i.e. 'width' of the Mesh.
+ * @param rows   Number of rows, i.e. 'height' of the Mesh.
+ * @param slices Number of slices, i.e. 'depth' of the Mesh.
+ */
+ public MeshCubes(int cols, int rows, int slices)
+   {
+   super();
+   Static4D map = new Static4D(0.0f,0.0f,1.0f,1.0f);
+   fillTexMappings(map,map,map,map,map,map);
+   prepareDataStructures(cols,rows,slices);
+   build();
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates a full, hole-less underlying mesh of vertices, normals, texture coords and colors with
+ * custom texture mappings.
+ *
+ * The mappings decide what part of the texture appears on a given side. Example:
+ * front = (0.0,0.5,0.5,1.0) means that the front side of the grid will be textured with the
+ * bottom-left quadrant of the texture.
+ *
+ * Default is: each of the 6 sides maps to the whole texture, i.e. (0.0,0.0,1.0,1.0)
+ *
+ * @param cols   Number of columns, i.e. 'width' of the Mesh.
+ * @param rows   Number of rows, i.e. 'height' of the Mesh.
+ * @param slices Number of slices, i.e. 'depth' of the Mesh.
+ * @param front  Texture mapping the front side maps to.
+ * @param back   Texture mapping the back side maps to.
+ * @param left   Texture mapping the left side maps to.
+ * @param right  Texture mapping the right side maps to.
+ * @param top    Texture mapping the top side maps to.
+ * @param bottom Texture mapping the bottom side maps to.
+ */
+ public MeshCubes(int cols, int rows, int slices, Static4D front, Static4D back, Static4D left, Static4D right, Static4D top, Static4D bottom)
+   {
+   super();
+   fillTexMappings(front,back,left,right,top,bottom);
+   prepareDataStructures(cols,rows,slices);
+   build();
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+ public MeshCubes(MeshCubes 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 MeshCubes copy(boolean deep)
+   {
+   return new MeshCubes(this,deep);
+   }
+ }
diff --git a/src/main/java/org/distorted/library/mesh/MeshFile.java b/src/main/java/org/distorted/library/mesh/MeshFile.java
deleted file mode 100644
index fe9b719..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshFile.java
+++ /dev/null
@@ -1,78 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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 java.io.DataInputStream;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Read a file in our mesh format, 'dmesh' and create a Mesh from that.
- * <p>
- * Such a file must have been created with help of MeshBase.write().
- */
-public class MeshFile extends MeshBase
-{
-   private int mBytes;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Read mesh description from a file.
- * <p>
- * File format: as written by MeshBase.write().
- */
-  public MeshFile(DataInputStream stream)
-    {
-    super();
-
-    mBytes = read(stream);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshFile(MeshFile 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 MeshFile copy(boolean deep)
-   {
-   return new MeshFile(this,deep);
-   }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return number of bytes read from the mesh file.
- */
-  public int getNumBytes()
-    {
-    return mBytes;
-    }
-}
diff --git a/src/main/java/org/distorted/library/mesh/MeshFile.kt b/src/main/java/org/distorted/library/mesh/MeshFile.kt
new file mode 100644
index 0000000..fe9b719
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshFile.kt
@@ -0,0 +1,78 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 java.io.DataInputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Read a file in our mesh format, 'dmesh' and create a Mesh from that.
+ * <p>
+ * Such a file must have been created with help of MeshBase.write().
+ */
+public class MeshFile extends MeshBase
+{
+   private int mBytes;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Read mesh description from a file.
+ * <p>
+ * File format: as written by MeshBase.write().
+ */
+  public MeshFile(DataInputStream stream)
+    {
+    super();
+
+    mBytes = read(stream);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshFile(MeshFile 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 MeshFile copy(boolean deep)
+   {
+   return new MeshFile(this,deep);
+   }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return number of bytes read from the mesh file.
+ */
+  public int getNumBytes()
+    {
+    return mBytes;
+    }
+}
diff --git a/src/main/java/org/distorted/library/mesh/MeshJoined.java b/src/main/java/org/distorted/library/mesh/MeshJoined.java
deleted file mode 100644
index 06fed17..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshJoined.java
+++ /dev/null
@@ -1,62 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a Mesh which is a union of several simpler Meshes.
- */
-public class MeshJoined extends MeshBase
-{
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Join a list of (probably already changed by Vertex Effects) Meshes into one.
- * <p>
- * You need to try and keep the origin (0,0,0) in the center of gravity of the whole thing.
- */
-  public MeshJoined(MeshBase[] meshes)
-    {
-    super();
-    join(meshes);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshJoined(MeshJoined 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 MeshJoined copy(boolean deep)
-   {
-   return new MeshJoined(this,deep);
-   }
-}
diff --git a/src/main/java/org/distorted/library/mesh/MeshJoined.kt b/src/main/java/org/distorted/library/mesh/MeshJoined.kt
new file mode 100644
index 0000000..06fed17
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshJoined.kt
@@ -0,0 +1,62 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a Mesh which is a union of several simpler Meshes.
+ */
+public class MeshJoined extends MeshBase
+{
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Join a list of (probably already changed by Vertex Effects) Meshes into one.
+ * <p>
+ * You need to try and keep the origin (0,0,0) in the center of gravity of the whole thing.
+ */
+  public MeshJoined(MeshBase[] meshes)
+    {
+    super();
+    join(meshes);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshJoined(MeshJoined 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 MeshJoined copy(boolean deep)
+   {
+   return new MeshJoined(this,deep);
+   }
+}
diff --git a/src/main/java/org/distorted/library/mesh/MeshMultigon.java b/src/main/java/org/distorted/library/mesh/MeshMultigon.java
deleted file mode 100644
index 90af5f5..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshMultigon.java
+++ /dev/null
@@ -1,767 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2023 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 java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Create a 'multigon' mesh - a union of several polygons.
- *
- * <p>
- * Specify several lists of vertices. Each list defines one polygon. (@see MeshPolygon).
- * If two such polygons share an edge (or several edges) then the edge in both of them is
- * marked 'up'.
- */
-public class MeshMultigon extends MeshBase
-  {
-  private static final float MAX_ERROR = 0.000001f;
-  private float[][][] mOuterAndHoleVertices;
-  private float[][][] mEdgeVectors;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean isSame(float dx, float dy)
-    {
-    return (dx*dx + dy*dy < MAX_ERROR);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int isUp(float[][][] vertices, int polygon, int edge)
-    {
-    float[][] p= vertices[polygon];
-    int lenP = p.length;
-    int len  = vertices.length;
-    int next = (edge==lenP-1 ? 0 : edge+1);
-
-    float v1x = p[edge][0];
-    float v1y = p[edge][1];
-    float v2x = p[next][0];
-    float v2y = p[next][1];
-
-    for(int i=0; i<len; i++)
-      if( i!=polygon )
-        {
-        int num = vertices[i].length;
-
-        for(int j=0; j<num; j++)
-          {
-          int n = (j==num-1 ? 0 : j+1);
-          float[][] v = vertices[i];
-          float x1 = v[j][0];
-          float y1 = v[j][1];
-          float x2 = v[n][0];
-          float y2 = v[n][1];
-
-          if( isSame(v2x-x1,v2y-y1) && isSame(v1x-x2,v1y-y2) ) return i;
-          }
-        }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static float[] detectFirstOuterVertex(float[][][] vertices)
-    {
-    float X = -Float.MAX_VALUE;
-    int I=0,J=0, len = vertices.length;
-
-    for(int i=0; i<len; i++ )
-      {
-      float[][] v = vertices[i];
-      int num = v.length;
-
-      for(int j=0; j<num; j++)
-        if( v[j][0]>X )
-          {
-          X = v[j][0];
-          I = i;
-          J = j;
-          }
-      }
-
-    float[] v = vertices[I][J];
-    return new float[] {v[0],v[1]};
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static double computeAngle(float x1,float y1, float x2, float y2)
-    {
-    double diff = Math.atan2(y2,x2)-Math.atan2(y1,x1);
-    return diff<0 ? diff+(2*Math.PI) : diff;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static float[] detectNextOuterVertex(float[][][] vertices, float[] curr, float[] vect, int[][] edgesUp)
-    {
-    double maxAngle = 0;
-    float x=0, y=0;
-    int numC = vertices.length;
-
-    for( int c=0; c<numC; c++ )
-      {
-      float[][] vert = vertices[c];
-      int numV = vert.length;
-
-      for( int v=0; v<numV; v++)
-        {
-        float xc = vert[v][0];
-        float yc = vert[v][1];
-
-        if( edgesUp[c][v]<0 && isSame(xc-curr[0],yc-curr[1]) )
-          {
-          int n = (v==numV-1 ? 0 : v+1);
-          float xn = vert[n][0];
-          float yn = vert[n][1];
-
-          double angle = computeAngle(vect[0], vect[1], xn-xc, yn-yc);
-
-          if (angle > maxAngle)
-            {
-            maxAngle = angle;
-            x = xn;
-            y = yn;
-            }
-
-          break;
-          }
-        }
-      }
-
-    return new float[] {x,y};
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int getNextIndex(float[][][] vertices, int[][] unclaimedEdges, int currIndex, int numHoleVerts)
-    {
-    int[] currEdge = unclaimedEdges[currIndex];
-    int currP = currEdge[0];
-    int currE = currEdge[1];
-    float[][] polygon = vertices[currP];
-    int numV = polygon.length;
-    int nextE= currE<numV-1 ? currE+1 : 0;
-
-    float x = polygon[nextE][0];
-    float y = polygon[nextE][1];
-
-    for(int e=0; e<numHoleVerts; e++)
-      {
-      int[] edge = unclaimedEdges[e];
-
-      if( edge[2]==1 )
-        {
-        int po = edge[0];
-        int ed = edge[1];
-
-        float cx = vertices[po][ed][0];
-        float cy = vertices[po][ed][1];
-
-        if( isSame(cx-x,cy-y) )
-          {
-          edge[2] = 0;
-          return e;
-          }
-        }
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int generateHole(float[][][] vertices, int[][] unclaimedEdges, float[][] output, int[][] edgesUp, int holeNumber, int numHoleVerts)
-    {
-    int firstIndex=-1;
-
-    for(int e=0; e<numHoleVerts; e++)
-      if( unclaimedEdges[e][2]==1 )
-        {
-        firstIndex = e;
-        break;
-        }
-
-    int currIndex = firstIndex;
-    int nextIndex=-1;
-    int numAdded = 0;
-
-    while( nextIndex!=firstIndex )
-      {
-      nextIndex = getNextIndex(vertices,unclaimedEdges,currIndex,numHoleVerts);
-
-      int p = unclaimedEdges[nextIndex][0];
-      int v = unclaimedEdges[nextIndex][1];
-      edgesUp[p][v] = -holeNumber-2;
-      currIndex = nextIndex;
-      int[] e = unclaimedEdges[nextIndex];
-      float[] ve = vertices[e[0]][e[1]];
-      output[numAdded][0] = ve[0];
-      output[numAdded][1] = ve[1];
-      numAdded++;
-      }
-
-    return numAdded;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static boolean doesNotBelongToOuter(float x, float y, float[][] outer)
-    {
-    for(float[] v : outer)
-      if(isSame(x-v[0], y-v[1])) return false;
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static float[][][] computeHoles(float[][][] vertices, int[][] edgesUp, float[][] outer, int numHoleVerts)
-    {
-    int[][] unclaimedEdges = new int[numHoleVerts][];
-    int index = 0;
-    int numPoly = vertices.length;
-
-    for(int p=0; p<numPoly; p++)
-      {
-      float[][] ve = vertices[p];
-      int numEdges = ve.length;
-
-      for(int e=0; e<numEdges; e++)
-        if( edgesUp[p][e]<0 )
-          {
-          float x1 = ve[e][0];
-          float y1 = ve[e][1];
-
-          int n = e<numEdges-1 ? e+1 : 0;
-          float x2 = ve[n][0];
-          float y2 = ve[n][1];
-
-          // yes, we need to check both ends of the edge - otherwise the
-          // following 3x3 situation (x - wall, o - hole ) does not work:
-          // x x x
-          // x o x
-          // o x x
-
-          if( doesNotBelongToOuter(x1,y1,outer) || doesNotBelongToOuter(x2,y2,outer) )
-            {
-            unclaimedEdges[index]=new int[] {p, e, 1};
-            index++;
-            }
-          }
-      }
-
-    int remaining = numHoleVerts;
-    ArrayList<float[][]> holes = new ArrayList<>();
-    float[][] output = new float[numHoleVerts][2];
-    int numHoles = 0;
-
-    while(remaining>0)
-      {
-      int num = generateHole(vertices,unclaimedEdges,output,edgesUp,numHoles,numHoleVerts);
-      remaining -= num;
-      numHoles++;
-
-      float[][] hole = new float[num][2];
-      for(int h=0; h<num; h++)
-        {
-        hole[h][0] = output[h][0];
-        hole[h][1] = output[h][1];
-        }
-
-      holes.add(hole);
-      }
-
-    float[][][] ret = new float[numHoles][][];
-    for(int h=0; h<numHoles; h++) ret[h] = holes.remove(0);
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int countEdgesDown(int[][] edgesUp)
-    {
-    int numEdgesDown = 0;
-
-    for(int[] edgeUp : edgesUp)
-      for(int edge : edgeUp)
-        if( edge<0 ) numEdgesDown++;
-
-    return numEdgesDown;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[] produceNormalVector(float[][] outerAndHole, float xs, float ys)
-    {
-    int vCurr,len = outerAndHole.length;
-
-    for(vCurr=0; vCurr<len; vCurr++)
-      {
-      float[] vs = outerAndHole[vCurr];
-      if( isSame(vs[0]-xs,vs[1]-ys) ) break;
-      }
-
-    if( vCurr==len ) System.out.println("MeshMultigon: ERROR in produceNormalVector!!");
-
-    int vPrev = vCurr==0 ? len-1 : vCurr-1;
-    int vNext = vCurr>=len-1 ? 0 : vCurr+1;
-    float[] vp = outerAndHole[vPrev];
-    float[] vn = outerAndHole[vNext];
-
-    return new float[] { vp[1]-vn[1], vn[0]-vp[0] };
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[] produceNormalVector(float[][] outerAndHole, float xs, float ys, float xe, float ye)
-    {
-    int vCurr,len = outerAndHole.length;
-
-    for(vCurr=0; vCurr<len; vCurr++)
-      {
-      float[] vs = outerAndHole[vCurr];
-
-      if( isSame(vs[0]-xs,vs[1]-ys) )
-        {
-        int vNext = vCurr==len-1 ? 0 : vCurr+1;
-        float[] ve = outerAndHole[vNext];
-        if( isSame(ve[0]-xe,ve[1]-ye) ) break;
-        }
-      }
-
-    int vPrev = vCurr==0 ? len-1 : vCurr-1;
-    int vNext = vCurr>=len-1 ? 0 : vCurr+1;
-    float[] vp = outerAndHole[vPrev];
-    float[] vn = outerAndHole[vNext];
-
-    return new float[] { vp[1]-vn[1], vn[0]-vp[0] };
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeEdgeVectors(float[][][] vertices, int[][] edgesUp)
-    {
-    int numPoly = vertices.length;
-    mEdgeVectors = new float[numPoly][][];
-
-    for(int c=0; c<numPoly; c++)
-      {
-      float[][] polygon = vertices[c];
-      int numV = polygon.length;
-      mEdgeVectors[c] = new float[numV][];
-
-      for(int v=0; v<numV; v++)
-        {
-        int edgeUp = edgesUp[c][v];
-        float xs = polygon[v][0];
-        float ys = polygon[v][1];
-        int n = v==numV-1 ? 0 : v+1;
-        float xe = polygon[n][0];
-        float ye = polygon[n][1];
-
-        if( edgeUp<0 ) // external edge, normal vector
-          {
-          float[][] verts = mOuterAndHoleVertices[-edgeUp-1];
-          mEdgeVectors[c][v] = produceNormalVector(verts,xs,ys,xe,ye);
-          }
-        else // internal edge - but the vertex that begins it might still be outer!
-          {
-          int numComp = retIndexBelongs(xs,ys);
-
-          if( numComp<0 ) mEdgeVectors[c][v] = null;
-          else
-            {
-            float[][] verts = mOuterAndHoleVertices[numComp];
-            mEdgeVectors[c][v] = produceNormalVector(verts,xs,ys);
-            }
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// ret:
-// -1  : vertex (x,y) is 'inner' (does not belong to outer vertices or any hole)
-// N>=0: vertex (x,y) is outer [ N==0 --> outer, 1--> 1sr hole, 2 --> 2nd hole, etc ]
-
-  private int retIndexBelongs(float x, float y)
-    {
-    int numOuterComponents = mOuterAndHoleVertices.length;
-
-    for(int c=0; c<numOuterComponents; c++)
-      {
-      float[][] comp = mOuterAndHoleVertices[c];
-
-      for(float[] v : comp)
-        if( isSame(x-v[0],y-v[1]) ) return c;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[] retOuterVector(int component, int vert)
-    {
-    return mEdgeVectors[component][vert];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean[][] computeVertsUp(float[][][] vertices)
-    {
-    int numPoly = vertices.length;
-    boolean[][] up = new boolean[numPoly][];
-
-    for(int i=0; i<numPoly; i++)
-      {
-      float[][] v = vertices[i];
-      int len = v.length;
-      up[i] = new boolean[len];
-
-      for(int j=0; j<len; j++)
-        {
-        float[] vect = retOuterVector(i,j);
-        up[i][j] = (vect==null);
-        }
-      }
-
-    return up;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[] computeCenter(float[][] vertices)
-    {
-    int num = vertices.length;
-    float[] ret = new float[2];
-
-    for(float[] vertex : vertices)
-      {
-      ret[0] += vertex[0];
-      ret[1] += vertex[1];
-      }
-
-    ret[0] /= num;
-    ret[1] /= num;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private float[][] computeCenters(float[][][] vertices)
-    {
-    int num = vertices.length;
-    float[][] ret = new float[num][];
-    for(int i=0; i<num; i++) ret[i] = computeCenter(vertices[i]);
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeMode(float[] vL, float[] vR, float[] vT, float[] normL, float[] normR,
-                          int[][] edgesUp, boolean[][] vertsUp, float[][][] vertices, float[][] centers, int component, int curr)
-    {
-    float[][] v = vertices[component];
-    int[] edges = edgesUp[component];
-    int numPoly = v.length;
-    int next= curr==numPoly-1 ? 0 : curr+1;
-    int prev= curr==0 ? numPoly-1 : curr-1;
-    int eupc = edges[curr];
-
-    if( eupc<0 )
-      {
-      vL[0] = v[curr][0];
-      vL[1] = v[curr][1];
-      vR[0] = v[next][0];
-      vR[1] = v[next][1];
-      vT[0] = centers[component][0];
-      vT[1] = centers[component][1];
-
-      float[] outerL = retOuterVector(component,curr);
-      float[] outerR = retOuterVector(component,next);
-
-      if( outerL!=null && outerR!=null )
-        {
-        normL[0] = -outerL[0];
-        normL[1] = -outerL[1];
-        normR[0] = -outerR[0];
-        normR[1] = -outerR[1];
-        }
-      else
-        {
-        int eupp = edges[prev];
-        int eupn = edges[next];
-
-        if( eupp<0 )
-          {
-          normL[0]= vL[0]-vT[0];
-          normL[1]= vL[1]-vT[1];
-          }
-        else
-          {
-          normL[0]= v[curr][0] - v[prev][0];
-          normL[1]= v[curr][1] - v[prev][1];
-          }
-
-        if( eupn<0 )
-          {
-          normR[0]= vR[0]-vT[0];
-          normR[1]= vR[1]-vT[1];
-          }
-        else
-          {
-          int nnxt= next==numPoly-1 ? 0 : next+1;
-          normR[0]= v[next][0] - v[nnxt][0];
-          normR[1]= v[next][1] - v[nnxt][1];
-          }
-        }
-
-      return MeshBandedTriangle.MODE_NORMAL;
-      }
-    else
-      {
-      vL[0] = centers[eupc][0];
-      vL[1] = centers[eupc][1];
-      vR[0] = centers[component][0];
-      vR[1] = centers[component][1];
-      vT[0] = v[curr][0];
-      vT[1] = v[curr][1];
-
-      boolean vup = vertsUp[component][curr];
-
-      if( vup )
-        {
-        normL[0]=0;
-        normL[1]=1;
-        normR[0]=0;
-        normR[1]=1;
-
-        return MeshBandedTriangle.MODE_FLAT;
-        }
-      else
-        {
-        float[] outerT = retOuterVector(component,curr);
-
-        if( outerT!=null )
-          {
-          normL[0]= outerT[0];
-          normL[1]= outerT[1];
-          normR[0]= outerT[0];
-          normR[1]= outerT[1];
-          }
-        else
-          {
-          float dx = v[next][0]-v[curr][0];
-          float dy = v[next][1]-v[curr][1];
-          normL[0]= dx;
-          normL[1]= dy;
-          normR[0]= dx;
-          normR[1]= dy;
-          }
-
-        return MeshBandedTriangle.MODE_INVERTED;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static int[][] computeEdgesUp(float[][][] vertices)
-    {
-    int numPoly = vertices.length;
-    int[][] up = new int[numPoly][];
-
-    for(int p=0; p<numPoly; p++)
-      {
-      int numEdges = vertices[p].length;
-      up[p] = new int[numEdges];
-      for(int e=0; e<numEdges; e++) up[p][e] = isUp(vertices,p,e);
-      }
-
-    return up;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static float[][][] computeOuterAndHoleVertices(float[][][] vertices, int[][] edgesUp)
-    {
-    ArrayList<float[]> tmp = new ArrayList<>();
-
-    float[] vect = new float[] {1,0};
-    float[] first= detectFirstOuterVertex(vertices);
-    float[] next = first;
-
-    do
-      {
-      float[] prev = next;
-      next = detectNextOuterVertex(vertices,next,vect,edgesUp);
-      vect[0] = prev[0]-next[0];
-      vect[1] = prev[1]-next[1];
-      tmp.add(next);
-      }
-    while( !isSame(next[0]-first[0],next[1]-first[1]) );
-
-    int numOuterVerts= tmp.size();
-    float[][] outerVertices = new float[numOuterVerts][];
-    for(int i=0; i<numOuterVerts; i++) outerVertices[i] = tmp.remove(0);
-
-    int numEdgesDown = countEdgesDown(edgesUp);
-    int numHoleVerts= numEdgesDown-numOuterVerts;
-    float[][][] holeVertices=null;
-    if( numHoleVerts>0 ) holeVertices = computeHoles(vertices,edgesUp,outerVertices,numHoleVerts);
-    int numHoles = holeVertices==null ? 0 : holeVertices.length;
-    float[][][] ret = new float[1+numHoles][][];
-    ret[0] = outerVertices;
-    for(int i=0; i<numHoles; i++) ret[i+1] = holeVertices[i];
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void printVertices(float[][][] vertices)
-    {
-    StringBuilder sb = new StringBuilder();
-    int loop=0;
-
-    for(float[][] vert : vertices)
-      {
-      sb.append("Loop ");
-      sb.append(loop);
-      sb.append(" : { ");
-
-      loop++;
-      int num=0;
-
-      for( float[] v : vert)
-        {
-        if( num>=0 ) sb.append(", ");
-        num++;
-
-        sb.append(v[0]);
-        sb.append(", ");
-        sb.append(v[1]);
-        }
-
-      sb.append(" }\n");
-      }
-
-    System.out.println("vertices: \n"+sb);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void printBand(float[] band)
-    {
-    StringBuilder sb = new StringBuilder();
-
-    for(float v : band)
-      {
-      sb.append(v);
-      sb.append("f, ");
-      }
-
-    System.out.println("band: \n"+sb);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Specify several lists of vertices. Each list defines one polygon. (@see MeshPolygon).
- * If two such polygons share an edge (or several edges) then the edge in both of them is
- * marked 'up'.
- *
- * @param vertices   an array float[][]s, each specifying vertices of a single polygon.
- * @param band       see MeshPolygon. Shared among all polygons.
- * @param exBands    see MeshPolygon. Shared among all polygons.
- * @param exVertices see MeshPolygon. Shared among all polygons.
- */
-  public MeshMultigon(float[][][] vertices, float[] band, int exBands, int exVertices)
-    {
-    super();
-
-    int numTriangles=0;
-    for(float[][] vertex : vertices) numTriangles+=vertex.length;
-    MeshBandedTriangle[] triangles = new MeshBandedTriangle[numTriangles];
-
-    int[][] edgesUp = computeEdgesUp(vertices);
-    mOuterAndHoleVertices = computeOuterAndHoleVertices(vertices,edgesUp);
-
-    computeEdgeVectors(vertices,edgesUp);
-
-    boolean[][] vertsUp = computeVertsUp(vertices);
-    float[][] centers = computeCenters(vertices);
-
-    float[] vL = new float[2];
-    float[] vR = new float[2];
-    float[] vT = new float[2];
-    float[] normL = new float[2];
-    float[] normR = new float[2];
-
-    int index=0,len = vertices.length;
-
-    for(int i=0; i<len; i++)
-      {
-      int num=vertices[i].length;
-
-      for(int j=0; j<num; j++)
-        {
-        int mode=computeMode(vL, vR, vT, normL, normR, edgesUp, vertsUp,vertices,centers, i,j);
-        triangles[index++] = new MeshBandedTriangle(vL, vR, vT, band, normL, normR, mode, exBands, exVertices);
-        }
-      }
-
-    join(triangles);
-    mergeEffComponents();
-    mergeTexComponents();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshMultigon(MeshMultigon 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 MeshMultigon copy(boolean deep)
-    {
-    return new MeshMultigon(this,deep);
-    }
- }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshMultigon.kt b/src/main/java/org/distorted/library/mesh/MeshMultigon.kt
new file mode 100644
index 0000000..90af5f5
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshMultigon.kt
@@ -0,0 +1,767 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 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 java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Create a 'multigon' mesh - a union of several polygons.
+ *
+ * <p>
+ * Specify several lists of vertices. Each list defines one polygon. (@see MeshPolygon).
+ * If two such polygons share an edge (or several edges) then the edge in both of them is
+ * marked 'up'.
+ */
+public class MeshMultigon extends MeshBase
+  {
+  private static final float MAX_ERROR = 0.000001f;
+  private float[][][] mOuterAndHoleVertices;
+  private float[][][] mEdgeVectors;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean isSame(float dx, float dy)
+    {
+    return (dx*dx + dy*dy < MAX_ERROR);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int isUp(float[][][] vertices, int polygon, int edge)
+    {
+    float[][] p= vertices[polygon];
+    int lenP = p.length;
+    int len  = vertices.length;
+    int next = (edge==lenP-1 ? 0 : edge+1);
+
+    float v1x = p[edge][0];
+    float v1y = p[edge][1];
+    float v2x = p[next][0];
+    float v2y = p[next][1];
+
+    for(int i=0; i<len; i++)
+      if( i!=polygon )
+        {
+        int num = vertices[i].length;
+
+        for(int j=0; j<num; j++)
+          {
+          int n = (j==num-1 ? 0 : j+1);
+          float[][] v = vertices[i];
+          float x1 = v[j][0];
+          float y1 = v[j][1];
+          float x2 = v[n][0];
+          float y2 = v[n][1];
+
+          if( isSame(v2x-x1,v2y-y1) && isSame(v1x-x2,v1y-y2) ) return i;
+          }
+        }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static float[] detectFirstOuterVertex(float[][][] vertices)
+    {
+    float X = -Float.MAX_VALUE;
+    int I=0,J=0, len = vertices.length;
+
+    for(int i=0; i<len; i++ )
+      {
+      float[][] v = vertices[i];
+      int num = v.length;
+
+      for(int j=0; j<num; j++)
+        if( v[j][0]>X )
+          {
+          X = v[j][0];
+          I = i;
+          J = j;
+          }
+      }
+
+    float[] v = vertices[I][J];
+    return new float[] {v[0],v[1]};
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static double computeAngle(float x1,float y1, float x2, float y2)
+    {
+    double diff = Math.atan2(y2,x2)-Math.atan2(y1,x1);
+    return diff<0 ? diff+(2*Math.PI) : diff;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static float[] detectNextOuterVertex(float[][][] vertices, float[] curr, float[] vect, int[][] edgesUp)
+    {
+    double maxAngle = 0;
+    float x=0, y=0;
+    int numC = vertices.length;
+
+    for( int c=0; c<numC; c++ )
+      {
+      float[][] vert = vertices[c];
+      int numV = vert.length;
+
+      for( int v=0; v<numV; v++)
+        {
+        float xc = vert[v][0];
+        float yc = vert[v][1];
+
+        if( edgesUp[c][v]<0 && isSame(xc-curr[0],yc-curr[1]) )
+          {
+          int n = (v==numV-1 ? 0 : v+1);
+          float xn = vert[n][0];
+          float yn = vert[n][1];
+
+          double angle = computeAngle(vect[0], vect[1], xn-xc, yn-yc);
+
+          if (angle > maxAngle)
+            {
+            maxAngle = angle;
+            x = xn;
+            y = yn;
+            }
+
+          break;
+          }
+        }
+      }
+
+    return new float[] {x,y};
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int getNextIndex(float[][][] vertices, int[][] unclaimedEdges, int currIndex, int numHoleVerts)
+    {
+    int[] currEdge = unclaimedEdges[currIndex];
+    int currP = currEdge[0];
+    int currE = currEdge[1];
+    float[][] polygon = vertices[currP];
+    int numV = polygon.length;
+    int nextE= currE<numV-1 ? currE+1 : 0;
+
+    float x = polygon[nextE][0];
+    float y = polygon[nextE][1];
+
+    for(int e=0; e<numHoleVerts; e++)
+      {
+      int[] edge = unclaimedEdges[e];
+
+      if( edge[2]==1 )
+        {
+        int po = edge[0];
+        int ed = edge[1];
+
+        float cx = vertices[po][ed][0];
+        float cy = vertices[po][ed][1];
+
+        if( isSame(cx-x,cy-y) )
+          {
+          edge[2] = 0;
+          return e;
+          }
+        }
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int generateHole(float[][][] vertices, int[][] unclaimedEdges, float[][] output, int[][] edgesUp, int holeNumber, int numHoleVerts)
+    {
+    int firstIndex=-1;
+
+    for(int e=0; e<numHoleVerts; e++)
+      if( unclaimedEdges[e][2]==1 )
+        {
+        firstIndex = e;
+        break;
+        }
+
+    int currIndex = firstIndex;
+    int nextIndex=-1;
+    int numAdded = 0;
+
+    while( nextIndex!=firstIndex )
+      {
+      nextIndex = getNextIndex(vertices,unclaimedEdges,currIndex,numHoleVerts);
+
+      int p = unclaimedEdges[nextIndex][0];
+      int v = unclaimedEdges[nextIndex][1];
+      edgesUp[p][v] = -holeNumber-2;
+      currIndex = nextIndex;
+      int[] e = unclaimedEdges[nextIndex];
+      float[] ve = vertices[e[0]][e[1]];
+      output[numAdded][0] = ve[0];
+      output[numAdded][1] = ve[1];
+      numAdded++;
+      }
+
+    return numAdded;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static boolean doesNotBelongToOuter(float x, float y, float[][] outer)
+    {
+    for(float[] v : outer)
+      if(isSame(x-v[0], y-v[1])) return false;
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static float[][][] computeHoles(float[][][] vertices, int[][] edgesUp, float[][] outer, int numHoleVerts)
+    {
+    int[][] unclaimedEdges = new int[numHoleVerts][];
+    int index = 0;
+    int numPoly = vertices.length;
+
+    for(int p=0; p<numPoly; p++)
+      {
+      float[][] ve = vertices[p];
+      int numEdges = ve.length;
+
+      for(int e=0; e<numEdges; e++)
+        if( edgesUp[p][e]<0 )
+          {
+          float x1 = ve[e][0];
+          float y1 = ve[e][1];
+
+          int n = e<numEdges-1 ? e+1 : 0;
+          float x2 = ve[n][0];
+          float y2 = ve[n][1];
+
+          // yes, we need to check both ends of the edge - otherwise the
+          // following 3x3 situation (x - wall, o - hole ) does not work:
+          // x x x
+          // x o x
+          // o x x
+
+          if( doesNotBelongToOuter(x1,y1,outer) || doesNotBelongToOuter(x2,y2,outer) )
+            {
+            unclaimedEdges[index]=new int[] {p, e, 1};
+            index++;
+            }
+          }
+      }
+
+    int remaining = numHoleVerts;
+    ArrayList<float[][]> holes = new ArrayList<>();
+    float[][] output = new float[numHoleVerts][2];
+    int numHoles = 0;
+
+    while(remaining>0)
+      {
+      int num = generateHole(vertices,unclaimedEdges,output,edgesUp,numHoles,numHoleVerts);
+      remaining -= num;
+      numHoles++;
+
+      float[][] hole = new float[num][2];
+      for(int h=0; h<num; h++)
+        {
+        hole[h][0] = output[h][0];
+        hole[h][1] = output[h][1];
+        }
+
+      holes.add(hole);
+      }
+
+    float[][][] ret = new float[numHoles][][];
+    for(int h=0; h<numHoles; h++) ret[h] = holes.remove(0);
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int countEdgesDown(int[][] edgesUp)
+    {
+    int numEdgesDown = 0;
+
+    for(int[] edgeUp : edgesUp)
+      for(int edge : edgeUp)
+        if( edge<0 ) numEdgesDown++;
+
+    return numEdgesDown;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] produceNormalVector(float[][] outerAndHole, float xs, float ys)
+    {
+    int vCurr,len = outerAndHole.length;
+
+    for(vCurr=0; vCurr<len; vCurr++)
+      {
+      float[] vs = outerAndHole[vCurr];
+      if( isSame(vs[0]-xs,vs[1]-ys) ) break;
+      }
+
+    if( vCurr==len ) System.out.println("MeshMultigon: ERROR in produceNormalVector!!");
+
+    int vPrev = vCurr==0 ? len-1 : vCurr-1;
+    int vNext = vCurr>=len-1 ? 0 : vCurr+1;
+    float[] vp = outerAndHole[vPrev];
+    float[] vn = outerAndHole[vNext];
+
+    return new float[] { vp[1]-vn[1], vn[0]-vp[0] };
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] produceNormalVector(float[][] outerAndHole, float xs, float ys, float xe, float ye)
+    {
+    int vCurr,len = outerAndHole.length;
+
+    for(vCurr=0; vCurr<len; vCurr++)
+      {
+      float[] vs = outerAndHole[vCurr];
+
+      if( isSame(vs[0]-xs,vs[1]-ys) )
+        {
+        int vNext = vCurr==len-1 ? 0 : vCurr+1;
+        float[] ve = outerAndHole[vNext];
+        if( isSame(ve[0]-xe,ve[1]-ye) ) break;
+        }
+      }
+
+    int vPrev = vCurr==0 ? len-1 : vCurr-1;
+    int vNext = vCurr>=len-1 ? 0 : vCurr+1;
+    float[] vp = outerAndHole[vPrev];
+    float[] vn = outerAndHole[vNext];
+
+    return new float[] { vp[1]-vn[1], vn[0]-vp[0] };
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeEdgeVectors(float[][][] vertices, int[][] edgesUp)
+    {
+    int numPoly = vertices.length;
+    mEdgeVectors = new float[numPoly][][];
+
+    for(int c=0; c<numPoly; c++)
+      {
+      float[][] polygon = vertices[c];
+      int numV = polygon.length;
+      mEdgeVectors[c] = new float[numV][];
+
+      for(int v=0; v<numV; v++)
+        {
+        int edgeUp = edgesUp[c][v];
+        float xs = polygon[v][0];
+        float ys = polygon[v][1];
+        int n = v==numV-1 ? 0 : v+1;
+        float xe = polygon[n][0];
+        float ye = polygon[n][1];
+
+        if( edgeUp<0 ) // external edge, normal vector
+          {
+          float[][] verts = mOuterAndHoleVertices[-edgeUp-1];
+          mEdgeVectors[c][v] = produceNormalVector(verts,xs,ys,xe,ye);
+          }
+        else // internal edge - but the vertex that begins it might still be outer!
+          {
+          int numComp = retIndexBelongs(xs,ys);
+
+          if( numComp<0 ) mEdgeVectors[c][v] = null;
+          else
+            {
+            float[][] verts = mOuterAndHoleVertices[numComp];
+            mEdgeVectors[c][v] = produceNormalVector(verts,xs,ys);
+            }
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// ret:
+// -1  : vertex (x,y) is 'inner' (does not belong to outer vertices or any hole)
+// N>=0: vertex (x,y) is outer [ N==0 --> outer, 1--> 1sr hole, 2 --> 2nd hole, etc ]
+
+  private int retIndexBelongs(float x, float y)
+    {
+    int numOuterComponents = mOuterAndHoleVertices.length;
+
+    for(int c=0; c<numOuterComponents; c++)
+      {
+      float[][] comp = mOuterAndHoleVertices[c];
+
+      for(float[] v : comp)
+        if( isSame(x-v[0],y-v[1]) ) return c;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] retOuterVector(int component, int vert)
+    {
+    return mEdgeVectors[component][vert];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean[][] computeVertsUp(float[][][] vertices)
+    {
+    int numPoly = vertices.length;
+    boolean[][] up = new boolean[numPoly][];
+
+    for(int i=0; i<numPoly; i++)
+      {
+      float[][] v = vertices[i];
+      int len = v.length;
+      up[i] = new boolean[len];
+
+      for(int j=0; j<len; j++)
+        {
+        float[] vect = retOuterVector(i,j);
+        up[i][j] = (vect==null);
+        }
+      }
+
+    return up;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[] computeCenter(float[][] vertices)
+    {
+    int num = vertices.length;
+    float[] ret = new float[2];
+
+    for(float[] vertex : vertices)
+      {
+      ret[0] += vertex[0];
+      ret[1] += vertex[1];
+      }
+
+    ret[0] /= num;
+    ret[1] /= num;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private float[][] computeCenters(float[][][] vertices)
+    {
+    int num = vertices.length;
+    float[][] ret = new float[num][];
+    for(int i=0; i<num; i++) ret[i] = computeCenter(vertices[i]);
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeMode(float[] vL, float[] vR, float[] vT, float[] normL, float[] normR,
+                          int[][] edgesUp, boolean[][] vertsUp, float[][][] vertices, float[][] centers, int component, int curr)
+    {
+    float[][] v = vertices[component];
+    int[] edges = edgesUp[component];
+    int numPoly = v.length;
+    int next= curr==numPoly-1 ? 0 : curr+1;
+    int prev= curr==0 ? numPoly-1 : curr-1;
+    int eupc = edges[curr];
+
+    if( eupc<0 )
+      {
+      vL[0] = v[curr][0];
+      vL[1] = v[curr][1];
+      vR[0] = v[next][0];
+      vR[1] = v[next][1];
+      vT[0] = centers[component][0];
+      vT[1] = centers[component][1];
+
+      float[] outerL = retOuterVector(component,curr);
+      float[] outerR = retOuterVector(component,next);
+
+      if( outerL!=null && outerR!=null )
+        {
+        normL[0] = -outerL[0];
+        normL[1] = -outerL[1];
+        normR[0] = -outerR[0];
+        normR[1] = -outerR[1];
+        }
+      else
+        {
+        int eupp = edges[prev];
+        int eupn = edges[next];
+
+        if( eupp<0 )
+          {
+          normL[0]= vL[0]-vT[0];
+          normL[1]= vL[1]-vT[1];
+          }
+        else
+          {
+          normL[0]= v[curr][0] - v[prev][0];
+          normL[1]= v[curr][1] - v[prev][1];
+          }
+
+        if( eupn<0 )
+          {
+          normR[0]= vR[0]-vT[0];
+          normR[1]= vR[1]-vT[1];
+          }
+        else
+          {
+          int nnxt= next==numPoly-1 ? 0 : next+1;
+          normR[0]= v[next][0] - v[nnxt][0];
+          normR[1]= v[next][1] - v[nnxt][1];
+          }
+        }
+
+      return MeshBandedTriangle.MODE_NORMAL;
+      }
+    else
+      {
+      vL[0] = centers[eupc][0];
+      vL[1] = centers[eupc][1];
+      vR[0] = centers[component][0];
+      vR[1] = centers[component][1];
+      vT[0] = v[curr][0];
+      vT[1] = v[curr][1];
+
+      boolean vup = vertsUp[component][curr];
+
+      if( vup )
+        {
+        normL[0]=0;
+        normL[1]=1;
+        normR[0]=0;
+        normR[1]=1;
+
+        return MeshBandedTriangle.MODE_FLAT;
+        }
+      else
+        {
+        float[] outerT = retOuterVector(component,curr);
+
+        if( outerT!=null )
+          {
+          normL[0]= outerT[0];
+          normL[1]= outerT[1];
+          normR[0]= outerT[0];
+          normR[1]= outerT[1];
+          }
+        else
+          {
+          float dx = v[next][0]-v[curr][0];
+          float dy = v[next][1]-v[curr][1];
+          normL[0]= dx;
+          normL[1]= dy;
+          normR[0]= dx;
+          normR[1]= dy;
+          }
+
+        return MeshBandedTriangle.MODE_INVERTED;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int[][] computeEdgesUp(float[][][] vertices)
+    {
+    int numPoly = vertices.length;
+    int[][] up = new int[numPoly][];
+
+    for(int p=0; p<numPoly; p++)
+      {
+      int numEdges = vertices[p].length;
+      up[p] = new int[numEdges];
+      for(int e=0; e<numEdges; e++) up[p][e] = isUp(vertices,p,e);
+      }
+
+    return up;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static float[][][] computeOuterAndHoleVertices(float[][][] vertices, int[][] edgesUp)
+    {
+    ArrayList<float[]> tmp = new ArrayList<>();
+
+    float[] vect = new float[] {1,0};
+    float[] first= detectFirstOuterVertex(vertices);
+    float[] next = first;
+
+    do
+      {
+      float[] prev = next;
+      next = detectNextOuterVertex(vertices,next,vect,edgesUp);
+      vect[0] = prev[0]-next[0];
+      vect[1] = prev[1]-next[1];
+      tmp.add(next);
+      }
+    while( !isSame(next[0]-first[0],next[1]-first[1]) );
+
+    int numOuterVerts= tmp.size();
+    float[][] outerVertices = new float[numOuterVerts][];
+    for(int i=0; i<numOuterVerts; i++) outerVertices[i] = tmp.remove(0);
+
+    int numEdgesDown = countEdgesDown(edgesUp);
+    int numHoleVerts= numEdgesDown-numOuterVerts;
+    float[][][] holeVertices=null;
+    if( numHoleVerts>0 ) holeVertices = computeHoles(vertices,edgesUp,outerVertices,numHoleVerts);
+    int numHoles = holeVertices==null ? 0 : holeVertices.length;
+    float[][][] ret = new float[1+numHoles][][];
+    ret[0] = outerVertices;
+    for(int i=0; i<numHoles; i++) ret[i+1] = holeVertices[i];
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void printVertices(float[][][] vertices)
+    {
+    StringBuilder sb = new StringBuilder();
+    int loop=0;
+
+    for(float[][] vert : vertices)
+      {
+      sb.append("Loop ");
+      sb.append(loop);
+      sb.append(" : { ");
+
+      loop++;
+      int num=0;
+
+      for( float[] v : vert)
+        {
+        if( num>=0 ) sb.append(", ");
+        num++;
+
+        sb.append(v[0]);
+        sb.append(", ");
+        sb.append(v[1]);
+        }
+
+      sb.append(" }\n");
+      }
+
+    System.out.println("vertices: \n"+sb);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void printBand(float[] band)
+    {
+    StringBuilder sb = new StringBuilder();
+
+    for(float v : band)
+      {
+      sb.append(v);
+      sb.append("f, ");
+      }
+
+    System.out.println("band: \n"+sb);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Specify several lists of vertices. Each list defines one polygon. (@see MeshPolygon).
+ * If two such polygons share an edge (or several edges) then the edge in both of them is
+ * marked 'up'.
+ *
+ * @param vertices   an array float[][]s, each specifying vertices of a single polygon.
+ * @param band       see MeshPolygon. Shared among all polygons.
+ * @param exBands    see MeshPolygon. Shared among all polygons.
+ * @param exVertices see MeshPolygon. Shared among all polygons.
+ */
+  public MeshMultigon(float[][][] vertices, float[] band, int exBands, int exVertices)
+    {
+    super();
+
+    int numTriangles=0;
+    for(float[][] vertex : vertices) numTriangles+=vertex.length;
+    MeshBandedTriangle[] triangles = new MeshBandedTriangle[numTriangles];
+
+    int[][] edgesUp = computeEdgesUp(vertices);
+    mOuterAndHoleVertices = computeOuterAndHoleVertices(vertices,edgesUp);
+
+    computeEdgeVectors(vertices,edgesUp);
+
+    boolean[][] vertsUp = computeVertsUp(vertices);
+    float[][] centers = computeCenters(vertices);
+
+    float[] vL = new float[2];
+    float[] vR = new float[2];
+    float[] vT = new float[2];
+    float[] normL = new float[2];
+    float[] normR = new float[2];
+
+    int index=0,len = vertices.length;
+
+    for(int i=0; i<len; i++)
+      {
+      int num=vertices[i].length;
+
+      for(int j=0; j<num; j++)
+        {
+        int mode=computeMode(vL, vR, vT, normL, normR, edgesUp, vertsUp,vertices,centers, i,j);
+        triangles[index++] = new MeshBandedTriangle(vL, vR, vT, band, normL, normR, mode, exBands, exVertices);
+        }
+      }
+
+    join(triangles);
+    mergeEffComponents();
+    mergeTexComponents();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshMultigon(MeshMultigon 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 MeshMultigon copy(boolean deep)
+    {
+    return new MeshMultigon(this,deep);
+    }
+ }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshPolygon.java b/src/main/java/org/distorted/library/mesh/MeshPolygon.java
deleted file mode 100644
index 1eabdf0..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshPolygon.java
+++ /dev/null
@@ -1,376 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a polygon of any shape and varying elevations from the edges towards the center.
- * <p>
- * Specify a list of vertices. Any two adjacent vertices + the center (0,0,0) form a triangle. The
- * polygon is going to be split into such triangles, and each triangle is split into adjustable number
- * of 'bands' form the outer edge towards the center. Edges of each band can can at any elevation.
- */
-public class MeshPolygon extends MeshBase
-  {
-  private static final int NUM_CACHE = 20;
-
-  private float[][] mPolygonVertices;
-  private int mNumPolygonVertices;
-  private float[] mPolygonBands;
-  private int mNumPolygonBands;
-
-  private int remainingVert;
-  private int numVertices;
-  private int extraBand, extraVertices;
-
-  private float[] mCurveCache;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// polygonVertices>=3 , polygonBands>=2
-
-  private void computeNumberOfVertices()
-     {
-     if( mNumPolygonBands==2 && extraBand>0 )
-       {
-       numVertices = 1 + 2*mNumPolygonVertices*(1+extraBand+2*extraVertices);
-       }
-     else
-       {
-       numVertices = (mNumPolygonVertices*mNumPolygonBands+2)*(mNumPolygonBands-1) - 1;
-       numVertices+= 2*mNumPolygonVertices*(2*extraBand*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 + extraBand + 2*extraVertices;
-    int change1 = extraVertices+1;
-    int change2 = num-change1;
-    float quot = 1.0f/(extraBand+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 = extraBand-band+extraVertices;
-
-        if( index < extra )
-          {
-          float quot = ((float)extraBand-band)/(extra*num);
-          return index*quot;
-          }
-        else if( index > num+2*extraVertices-extra )
-          {
-          float quot = ((float)extraBand-band)/(extra*num);
-          return (1.0f-((float)extraBand-band)/num) + (index-num-2*extraVertices+extra)*quot;
-          }
-        else
-          {
-          return ((float)(index-extraVertices))/num;
-          }
-        }
-
-      return (float)index/num;
-      }
-
-    return 1.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 int addVertex(int vertex, int polyBand, int polyVertex, int polyEndVer, float quot,
-                        float[] attribs1, float[] attribs2)
-    {
-    remainingVert--;
-
-    float Xfirst= mPolygonVertices[polyVertex][0];
-    float Yfirst= mPolygonVertices[polyVertex][1];
-    float Xlast = mPolygonVertices[polyEndVer][0];
-    float Ylast = mPolygonVertices[polyEndVer][1];
-
-    float xEdge = Xfirst + quot*(Xlast-Xfirst);
-    float yEdge = Yfirst + quot*(Ylast-Yfirst);
-
-    float q = mPolygonBands[2*polyBand];
-    float x = q*xEdge;
-    float y = q*yEdge;
-    float z = mPolygonBands[2*polyBand+1];
-
-    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x;
-    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y;
-    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = z;
-
-    float d = derivative(1-mPolygonBands[2*polyBand]);
-
-    float vx = d*xEdge;
-    float vy = d*yEdge;
-    float vz = xEdge*xEdge + yEdge*yEdge;
-    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 && extraBand>0;
-    boolean isExtra = polyBand<extraBand;
-    int numPairs = specialCase ? extraBand+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 void buildGrid(float[] attribs1, float[] attribs2)
-    {
-    int vertex=0;
-
-    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 polygon of any shape and varying elevations from the edges towards the center.
- * Optionally make it more dense at the vertices.
- *
- * @param verticesXY [N][2] floats - polygon vertices. N pairs (x,y).
- *                   Vertices HAVE TO be specified in a COUNTERCLOCKWISE order (starting from any).
- * @param bands      2K floats; K pairs of two floats each describing a single band.
- *                   From (1.0,Z[0]) (outer edge, its Z elevation) to (0.0,Z[K]) (the center,
- *                   its elevation). The polygon is split into such concentric bands.
- *                   Must be band[2*i] > band[2*(i+1)] !
- * @param exBand     This and the next parameter describe how to make the mesh denser at the
- *                   polyVertices. 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.
- * @param centerX    the X coordinate of the 'center' of the Polygon, i.e. point of the mesh
- *                   all bands go to.
- * @param centerY    Y coordinate of the center.
- */
-  public MeshPolygon(float[][] verticesXY, float[] bands, int exBand, int exVertices, float centerX, float centerY)
-    {
-    super();
-
-    mPolygonVertices   = verticesXY;
-    mPolygonBands      = bands;
-    mNumPolygonVertices= mPolygonVertices.length;
-    mNumPolygonBands   = mPolygonBands.length /2;
-    extraBand          = exBand;
-    extraVertices      = exVertices;
-
-    if( centerX!=0.0f || centerY!=0.0f )
-      {
-      for(int v=0; v<mNumPolygonVertices; v++)
-        {
-        mPolygonVertices[v][0] -= centerX;
-        mPolygonVertices[v][1] -= centerY;
-        }
-      }
-
-    computeNumberOfVertices();
-    computeCache();
-
-    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
-    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
-
-    buildGrid(attribs1,attribs2);
-
-    if( remainingVert!=0 )
-      DistortedLibrary.logMessage("MeshPolygon: remainingVert " +remainingVert );
-
-    if( centerX!=0.0f || centerY!=0.0f )
-      {
-      for(int v=0; v<numVertices; v++)
-        {
-        attribs1[VERT1_ATTRIBS*v + POS_ATTRIB  ] += centerX;
-        attribs1[VERT1_ATTRIBS*v + POS_ATTRIB+1] += centerY;
-        attribs2[VERT2_ATTRIBS*v + TEX_ATTRIB  ] += centerX;
-        attribs2[VERT2_ATTRIBS*v + TEX_ATTRIB+1] += centerY;
-        }
-      }
-
-    setAttribs(attribs1,attribs2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public MeshPolygon(float[][] verticesXY, float[] bands, int exIndex, int exVertices)
-    {
-    this(verticesXY,bands,exIndex,exVertices,0.0f,0.0f);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a polygon of any shape and varying elevations from the edges towards the center.
- * Equivalent of the previous with exIndex=0 or exVertices=0.
- */
-  public MeshPolygon(float[][] verticesXY, float[] bands)
-    {
-    this(verticesXY,bands,0,0,0.0f,0.0f);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshPolygon(MeshPolygon 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 MeshPolygon copy(boolean deep)
-    {
-    return new MeshPolygon(this,deep);
-    }
- }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshPolygon.kt b/src/main/java/org/distorted/library/mesh/MeshPolygon.kt
new file mode 100644
index 0000000..1eabdf0
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshPolygon.kt
@@ -0,0 +1,376 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a polygon of any shape and varying elevations from the edges towards the center.
+ * <p>
+ * Specify a list of vertices. Any two adjacent vertices + the center (0,0,0) form a triangle. The
+ * polygon is going to be split into such triangles, and each triangle is split into adjustable number
+ * of 'bands' form the outer edge towards the center. Edges of each band can can at any elevation.
+ */
+public class MeshPolygon extends MeshBase
+  {
+  private static final int NUM_CACHE = 20;
+
+  private float[][] mPolygonVertices;
+  private int mNumPolygonVertices;
+  private float[] mPolygonBands;
+  private int mNumPolygonBands;
+
+  private int remainingVert;
+  private int numVertices;
+  private int extraBand, extraVertices;
+
+  private float[] mCurveCache;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// polygonVertices>=3 , polygonBands>=2
+
+  private void computeNumberOfVertices()
+     {
+     if( mNumPolygonBands==2 && extraBand>0 )
+       {
+       numVertices = 1 + 2*mNumPolygonVertices*(1+extraBand+2*extraVertices);
+       }
+     else
+       {
+       numVertices = (mNumPolygonVertices*mNumPolygonBands+2)*(mNumPolygonBands-1) - 1;
+       numVertices+= 2*mNumPolygonVertices*(2*extraBand*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 + extraBand + 2*extraVertices;
+    int change1 = extraVertices+1;
+    int change2 = num-change1;
+    float quot = 1.0f/(extraBand+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 = extraBand-band+extraVertices;
+
+        if( index < extra )
+          {
+          float quot = ((float)extraBand-band)/(extra*num);
+          return index*quot;
+          }
+        else if( index > num+2*extraVertices-extra )
+          {
+          float quot = ((float)extraBand-band)/(extra*num);
+          return (1.0f-((float)extraBand-band)/num) + (index-num-2*extraVertices+extra)*quot;
+          }
+        else
+          {
+          return ((float)(index-extraVertices))/num;
+          }
+        }
+
+      return (float)index/num;
+      }
+
+    return 1.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 int addVertex(int vertex, int polyBand, int polyVertex, int polyEndVer, float quot,
+                        float[] attribs1, float[] attribs2)
+    {
+    remainingVert--;
+
+    float Xfirst= mPolygonVertices[polyVertex][0];
+    float Yfirst= mPolygonVertices[polyVertex][1];
+    float Xlast = mPolygonVertices[polyEndVer][0];
+    float Ylast = mPolygonVertices[polyEndVer][1];
+
+    float xEdge = Xfirst + quot*(Xlast-Xfirst);
+    float yEdge = Yfirst + quot*(Ylast-Yfirst);
+
+    float q = mPolygonBands[2*polyBand];
+    float x = q*xEdge;
+    float y = q*yEdge;
+    float z = mPolygonBands[2*polyBand+1];
+
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x;
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y;
+    attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = z;
+
+    float d = derivative(1-mPolygonBands[2*polyBand]);
+
+    float vx = d*xEdge;
+    float vy = d*yEdge;
+    float vz = xEdge*xEdge + yEdge*yEdge;
+    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 && extraBand>0;
+    boolean isExtra = polyBand<extraBand;
+    int numPairs = specialCase ? extraBand+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 void buildGrid(float[] attribs1, float[] attribs2)
+    {
+    int vertex=0;
+
+    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 polygon of any shape and varying elevations from the edges towards the center.
+ * Optionally make it more dense at the vertices.
+ *
+ * @param verticesXY [N][2] floats - polygon vertices. N pairs (x,y).
+ *                   Vertices HAVE TO be specified in a COUNTERCLOCKWISE order (starting from any).
+ * @param bands      2K floats; K pairs of two floats each describing a single band.
+ *                   From (1.0,Z[0]) (outer edge, its Z elevation) to (0.0,Z[K]) (the center,
+ *                   its elevation). The polygon is split into such concentric bands.
+ *                   Must be band[2*i] > band[2*(i+1)] !
+ * @param exBand     This and the next parameter describe how to make the mesh denser at the
+ *                   polyVertices. 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.
+ * @param centerX    the X coordinate of the 'center' of the Polygon, i.e. point of the mesh
+ *                   all bands go to.
+ * @param centerY    Y coordinate of the center.
+ */
+  public MeshPolygon(float[][] verticesXY, float[] bands, int exBand, int exVertices, float centerX, float centerY)
+    {
+    super();
+
+    mPolygonVertices   = verticesXY;
+    mPolygonBands      = bands;
+    mNumPolygonVertices= mPolygonVertices.length;
+    mNumPolygonBands   = mPolygonBands.length /2;
+    extraBand          = exBand;
+    extraVertices      = exVertices;
+
+    if( centerX!=0.0f || centerY!=0.0f )
+      {
+      for(int v=0; v<mNumPolygonVertices; v++)
+        {
+        mPolygonVertices[v][0] -= centerX;
+        mPolygonVertices[v][1] -= centerY;
+        }
+      }
+
+    computeNumberOfVertices();
+    computeCache();
+
+    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+    buildGrid(attribs1,attribs2);
+
+    if( remainingVert!=0 )
+      DistortedLibrary.logMessage("MeshPolygon: remainingVert " +remainingVert );
+
+    if( centerX!=0.0f || centerY!=0.0f )
+      {
+      for(int v=0; v<numVertices; v++)
+        {
+        attribs1[VERT1_ATTRIBS*v + POS_ATTRIB  ] += centerX;
+        attribs1[VERT1_ATTRIBS*v + POS_ATTRIB+1] += centerY;
+        attribs2[VERT2_ATTRIBS*v + TEX_ATTRIB  ] += centerX;
+        attribs2[VERT2_ATTRIBS*v + TEX_ATTRIB+1] += centerY;
+        }
+      }
+
+    setAttribs(attribs1,attribs2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public MeshPolygon(float[][] verticesXY, float[] bands, int exIndex, int exVertices)
+    {
+    this(verticesXY,bands,exIndex,exVertices,0.0f,0.0f);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a polygon of any shape and varying elevations from the edges towards the center.
+ * Equivalent of the previous with exIndex=0 or exVertices=0.
+ */
+  public MeshPolygon(float[][] verticesXY, float[] bands)
+    {
+    this(verticesXY,bands,0,0,0.0f,0.0f);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshPolygon(MeshPolygon 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 MeshPolygon copy(boolean deep)
+    {
+    return new MeshPolygon(this,deep);
+    }
+ }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshQuad.java b/src/main/java/org/distorted/library/mesh/MeshQuad.java
deleted file mode 100644
index 61f6d9c..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshQuad.java
+++ /dev/null
@@ -1,90 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2018 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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a quad - two triangles with vertices at (+-0.5,+-0.5).
- * <p>
- * Mainly to have a simple example showing how to create a Mesh; otherwise a MeshQuad can be perfectly
- * emulated by a MeshRectangles(1,1) or a MeshCubes(1,1,0).
- */
-public class MeshQuad extends MeshBase
-  {
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addVertex( float x, float y, float[] attribs1, float[] attribs2, int index)
-    {
-    attribs1[VERT1_ATTRIBS*index + POS_ATTRIB  ] = x-0.5f;
-    attribs1[VERT1_ATTRIBS*index + POS_ATTRIB+1] = 0.5f-y;
-    attribs1[VERT1_ATTRIBS*index + POS_ATTRIB+2] = 0.0f;
-
-    attribs1[VERT1_ATTRIBS*index + NOR_ATTRIB  ] = 0.0f;
-    attribs1[VERT1_ATTRIBS*index + NOR_ATTRIB+1] = 0.0f;
-    attribs1[VERT1_ATTRIBS*index + NOR_ATTRIB+2] = 1.0f;
-
-    attribs2[VERT2_ATTRIBS*index + TEX_ATTRIB  ] = x;
-    attribs2[VERT2_ATTRIBS*index + TEX_ATTRIB+1] = 1.0f-y;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Creates the underlying grid of 4 vertices, normals, inflates and texture coords.
-   */
-  public MeshQuad()
-    {
-    super();
-
-    float[] attribs1= new float[VERT1_ATTRIBS*4];
-    float[] attribs2= new float[VERT2_ATTRIBS*4];
-
-    addVertex(0.0f,0.0f, attribs1, attribs2, 0);
-    addVertex(1.0f,0.0f, attribs1, attribs2, 1);
-    addVertex(0.0f,1.0f, attribs1, attribs2, 2);
-    addVertex(1.0f,1.0f, attribs1, attribs2, 3);
-
-    setAttribs(attribs1,attribs2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshQuad(MeshQuad 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 MeshQuad copy(boolean deep)
-    {
-    return new MeshQuad(this,deep);
-    }
-  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshQuad.kt b/src/main/java/org/distorted/library/mesh/MeshQuad.kt
new file mode 100644
index 0000000..61f6d9c
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshQuad.kt
@@ -0,0 +1,90 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2018 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a quad - two triangles with vertices at (+-0.5,+-0.5).
+ * <p>
+ * Mainly to have a simple example showing how to create a Mesh; otherwise a MeshQuad can be perfectly
+ * emulated by a MeshRectangles(1,1) or a MeshCubes(1,1,0).
+ */
+public class MeshQuad extends MeshBase
+  {
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addVertex( float x, float y, float[] attribs1, float[] attribs2, int index)
+    {
+    attribs1[VERT1_ATTRIBS*index + POS_ATTRIB  ] = x-0.5f;
+    attribs1[VERT1_ATTRIBS*index + POS_ATTRIB+1] = 0.5f-y;
+    attribs1[VERT1_ATTRIBS*index + POS_ATTRIB+2] = 0.0f;
+
+    attribs1[VERT1_ATTRIBS*index + NOR_ATTRIB  ] = 0.0f;
+    attribs1[VERT1_ATTRIBS*index + NOR_ATTRIB+1] = 0.0f;
+    attribs1[VERT1_ATTRIBS*index + NOR_ATTRIB+2] = 1.0f;
+
+    attribs2[VERT2_ATTRIBS*index + TEX_ATTRIB  ] = x;
+    attribs2[VERT2_ATTRIBS*index + TEX_ATTRIB+1] = 1.0f-y;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Creates the underlying grid of 4 vertices, normals, inflates and texture coords.
+   */
+  public MeshQuad()
+    {
+    super();
+
+    float[] attribs1= new float[VERT1_ATTRIBS*4];
+    float[] attribs2= new float[VERT2_ATTRIBS*4];
+
+    addVertex(0.0f,0.0f, attribs1, attribs2, 0);
+    addVertex(1.0f,0.0f, attribs1, attribs2, 1);
+    addVertex(0.0f,1.0f, attribs1, attribs2, 2);
+    addVertex(1.0f,1.0f, attribs1, attribs2, 3);
+
+    setAttribs(attribs1,attribs2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshQuad(MeshQuad 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 MeshQuad copy(boolean deep)
+    {
+    return new MeshQuad(this,deep);
+    }
+  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshSphere.java b/src/main/java/org/distorted/library/mesh/MeshSphere.java
deleted file mode 100644
index 65578aa..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshSphere.java
+++ /dev/null
@@ -1,290 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2018 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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Create a Mesh which approximates a sphere.
- * <p>
- * Do so by starting off with a 16-faced solid which is basically a regular dodecahedron with each
- * of its 8 faces vertically split into 2 triangles, and which each step divide each of its triangular
- * faces into smaller and smaller subtriangles and inflate their vertices to lay on the surface or the
- * sphere.
- */
-public class MeshSphere extends MeshBase
-  {
-  private static final int NUMFACES = 16;
-  private static final double P = Math.PI;
-
-  // An array of 16 entries, each describing a single face of a solid in an (admittedly) weird
-  // fashion. Each face is a triangle, with 2 vertices on the same latitude.
-  // Single row is (longitude of V1, longitude of V2, (common) latitude of V1 and V2, latitude of V3)
-  // longitude of V3 is simply midpoint of V1 and V2 so we don't have to specify it here.
-
-  private static final double[][] FACES =      {
-
-      { 0.00*P, 0.25*P, 0.0, 0.5*P },
-      { 0.25*P, 0.50*P, 0.0, 0.5*P },
-      { 0.50*P, 0.75*P, 0.0, 0.5*P },
-      { 0.75*P, 1.00*P, 0.0, 0.5*P },
-      { 1.00*P, 1.25*P, 0.0, 0.5*P },
-      { 1.25*P, 1.50*P, 0.0, 0.5*P },
-      { 1.50*P, 1.75*P, 0.0, 0.5*P },
-      { 1.75*P, 0.00*P, 0.0, 0.5*P },
-
-      { 0.00*P, 0.25*P, 0.0,-0.5*P },
-      { 0.25*P, 0.50*P, 0.0,-0.5*P },
-      { 0.50*P, 0.75*P, 0.0,-0.5*P },
-      { 0.75*P, 1.00*P, 0.0,-0.5*P },
-      { 1.00*P, 1.25*P, 0.0,-0.5*P },
-      { 1.25*P, 1.50*P, 0.0,-0.5*P },
-      { 1.50*P, 1.75*P, 0.0,-0.5*P },
-      { 1.75*P, 0.00*P, 0.0,-0.5*P },
-                                               };
-  private int currentVert;
-  private int numVertices;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Each of the 16 faces of the solid requires (level*level + 4*level) vertices for the face
-// itself and a join to the next face (which requires 2 vertices). We don't need the join in case
-// of the last, 16th face, thus the -2.
-// (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.
-
-  private void computeNumberOfVertices(int level)
-    {
-    numVertices = NUMFACES*level*(level+4) -2;
-    currentVert = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void repeatVertex(float[] attribs1, float[] attribs2)
-    {
-    if( currentVert>0 )
-      {
-      attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currentVert-1) + POS_ATTRIB  ];
-      attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currentVert-1) + POS_ATTRIB+1];
-      attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currentVert-1) + POS_ATTRIB+2];
-
-      attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currentVert-1) + NOR_ATTRIB  ];
-      attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currentVert-1) + NOR_ATTRIB+1];
-      attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currentVert-1) + NOR_ATTRIB+2];
-
-      attribs2[VERT2_ATTRIBS*currentVert + TEX_ATTRIB  ] = attribs2[VERT2_ATTRIBS*(currentVert-1) + TEX_ATTRIB  ];
-      attribs2[VERT2_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs2[VERT2_ATTRIBS*(currentVert-1) + TEX_ATTRIB+1];
-
-      currentVert++;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Supposed to return the latitude of the point between two points on the sphere with latitudes
-// lat1 and lat2, so if for example quot=0.2, then it will return the latitude of something 20%
-// along the way from lat1 to lat2.
-//
-// This is approximation only - in general it is of course not true that the midpoint of two points
-// on a unit sphere with spherical coords (A1,B1) and (A2,B2) is ( (A1+A2)/2, (B1+B2)/2 ) - take
-// (0,0) and (PI, epsilon) as a counterexample.
-//
-// Here however, the latitudes we are interested at are the latitudes of the vertices of a regular
-// icosahedron - i.e. +=A and +=PI/2, whose longitudes are close, and we don't really care if the
-// split into smaller triangles is exact.
-//
-// quot better be between 0.0 and 1.0.
-// this is 'directed' i.e. from lat1 to lat2.
-
-  private double midLatitude(double lat1, double lat2, double quot)
-    {
-    return lat1*(1.0-quot)+lat2*quot;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Same in case of longitude. This is for our needs exact, because we are ever only calling this with
-// two longitudes of two vertices with the same latitude. Additional problem: things can wrap around
-// the circle.
-// this is 'undirected' i.e. we don't assume from lon1 to lon2 - just along the smaller arc joining
-// lon1 to lon2.
-
-  private double midLongitude(double lon1, double lon2, double quot)
-    {
-    double min, max;
-
-    if( lon1<lon2 ) { min=lon1; max=lon2; }
-    else            { min=lon2; max=lon1; }
-
-    double diff = max-min;
-    if( diff>P ) { diff=2*P-diff; min=max; }
-
-    double ret = min+quot*diff;
-    if( ret>=2*P ) ret-=2*P;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// linear map (column,row, level):
-//
-// (      0,       0, level) -> (lonV1,latV12)
-// (      0, level-1, level) -> (lonV3,latV3 )
-// (level-1,       0, level) -> (lonV2,latV12)
-
-  private void addVertex(float[] attribs1, float[] attribs2, int column, int row, int level,
-                         double lonV1, double lonV2, double latV12, double latV3)
-    {
-    double quotX = (double)column/level;
-    double quotY = (double)row   /level;
-    double quotZ;
-
-    if( latV12*latV3 < 0.0 )  // equatorial triangle
-      {
-      quotZ = quotX + 0.5*quotY;
-      }
-    else                      // polar triangle
-      {
-      quotZ = (quotY==1.0 ? 0.5 : quotX / (1.0-quotY));
-      }
-
-    double longitude = midLongitude(lonV1, lonV2, quotZ );
-    double latitude  = midLatitude(latV12, latV3, quotY );
-
-    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 / 2.0f);
-    float y = (float)(sinLAT        / 2.0f);
-    float z = (float)(cosLAT*cosLON / 2.0f);
-
-    double texX = 0.5 + longitude/(2*P);
-    if( texX>=1.0 ) texX-=1.0;
-
-    double texY = 0.5 + latitude/P;
-
-    attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
-    attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
-    attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
-                                                             //  In case of this Mesh so it happens that
-    attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB  ] = 2*x;//  the vertex coords, normal vector, and
-    attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+1] = 2*y;//  inflate vector have identical (x,y,z).
-    attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+2] = 2*z;//
-
-    attribs2[VERT2_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
-    attribs2[VERT2_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, which looks like a seam with a narrow copy of
-    // the whole texture there. Solution: remap texX to 1.0.
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-
-    if( currentVert>=3 && texX==0.0 )
-      {
-      double tex1 = attribs2[VERT2_ATTRIBS*(currentVert-2) + TEX_ATTRIB];
-      double tex2 = attribs2[VERT2_ATTRIBS*(currentVert-3) + TEX_ATTRIB];
-
-      // if the triangle is not degenerate and last vertex was on the western hemisphere
-      if( tex1!=tex2 && tex1>0.5 )
-        {
-        attribs2[VERT2_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildFace(float[] attribs1, float[] attribs2, int level, int face, double lonV1, double lonV2, double latV12, double latV3)
-    {
-    for(int row=0; row<level; row++)
-      {
-      for (int column=0; column<level-row; column++)
-        {
-        addVertex(attribs1, attribs2, column, row  , level, lonV1, lonV2, latV12, latV3);
-        if (column==0 && !(face==0 && row==0 ) ) repeatVertex(attribs1, attribs2);
-        addVertex(attribs1, attribs2, column, row+1, level, lonV1, lonV2, latV12, latV3);
-        }
-
-      addVertex(attribs1, attribs2, level-row, row , level, lonV1, lonV2, latV12, latV3);
-      if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs1, attribs2);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Creates the underlying grid of vertices with the usual attribs which approximates a sphere.
-   * <p>
-   * When level=1, it outputs the 12 vertices of a regular icosahedron.
-   * When level=N, it divides each of the 20 icosaherdon's triangular faces into N^2 smaller triangles
-   * (by dividing each side into N equal segments) and 'inflates' the internal vertices so that they
-   * touch the sphere.
-   *
-   * @param level Specifies the approximation level. Valid values: level &ge; 1
-   */
-  public MeshSphere(int level)
-    {
-    super();
-
-    computeNumberOfVertices(level);
-    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
-    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
-
-    for(int face=0; face<NUMFACES; face++ )
-      {
-      buildFace(attribs1, attribs2, level, face, FACES[face][0], FACES[face][1], FACES[face][2], FACES[face][3]);
-      }
-
-    if( currentVert!=numVertices )
-      DistortedLibrary.logMessage("MeshSphere: currentVert= " +currentVert+" numVertices="+numVertices );
-
-    setAttribs(attribs1, attribs2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy cconstructor.
- */
-  public MeshSphere(MeshSphere 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 MeshSphere copy(boolean deep)
-    {
-    return new MeshSphere(this,deep);
-    }
-  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshSphere.kt b/src/main/java/org/distorted/library/mesh/MeshSphere.kt
new file mode 100644
index 0000000..65578aa
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshSphere.kt
@@ -0,0 +1,290 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2018 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Create a Mesh which approximates a sphere.
+ * <p>
+ * Do so by starting off with a 16-faced solid which is basically a regular dodecahedron with each
+ * of its 8 faces vertically split into 2 triangles, and which each step divide each of its triangular
+ * faces into smaller and smaller subtriangles and inflate their vertices to lay on the surface or the
+ * sphere.
+ */
+public class MeshSphere extends MeshBase
+  {
+  private static final int NUMFACES = 16;
+  private static final double P = Math.PI;
+
+  // An array of 16 entries, each describing a single face of a solid in an (admittedly) weird
+  // fashion. Each face is a triangle, with 2 vertices on the same latitude.
+  // Single row is (longitude of V1, longitude of V2, (common) latitude of V1 and V2, latitude of V3)
+  // longitude of V3 is simply midpoint of V1 and V2 so we don't have to specify it here.
+
+  private static final double[][] FACES =      {
+
+      { 0.00*P, 0.25*P, 0.0, 0.5*P },
+      { 0.25*P, 0.50*P, 0.0, 0.5*P },
+      { 0.50*P, 0.75*P, 0.0, 0.5*P },
+      { 0.75*P, 1.00*P, 0.0, 0.5*P },
+      { 1.00*P, 1.25*P, 0.0, 0.5*P },
+      { 1.25*P, 1.50*P, 0.0, 0.5*P },
+      { 1.50*P, 1.75*P, 0.0, 0.5*P },
+      { 1.75*P, 0.00*P, 0.0, 0.5*P },
+
+      { 0.00*P, 0.25*P, 0.0,-0.5*P },
+      { 0.25*P, 0.50*P, 0.0,-0.5*P },
+      { 0.50*P, 0.75*P, 0.0,-0.5*P },
+      { 0.75*P, 1.00*P, 0.0,-0.5*P },
+      { 1.00*P, 1.25*P, 0.0,-0.5*P },
+      { 1.25*P, 1.50*P, 0.0,-0.5*P },
+      { 1.50*P, 1.75*P, 0.0,-0.5*P },
+      { 1.75*P, 0.00*P, 0.0,-0.5*P },
+                                               };
+  private int currentVert;
+  private int numVertices;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Each of the 16 faces of the solid requires (level*level + 4*level) vertices for the face
+// itself and a join to the next face (which requires 2 vertices). We don't need the join in case
+// of the last, 16th face, thus the -2.
+// (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.
+
+  private void computeNumberOfVertices(int level)
+    {
+    numVertices = NUMFACES*level*(level+4) -2;
+    currentVert = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void repeatVertex(float[] attribs1, float[] attribs2)
+    {
+    if( currentVert>0 )
+      {
+      attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currentVert-1) + POS_ATTRIB  ];
+      attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currentVert-1) + POS_ATTRIB+1];
+      attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currentVert-1) + POS_ATTRIB+2];
+
+      attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(currentVert-1) + NOR_ATTRIB  ];
+      attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(currentVert-1) + NOR_ATTRIB+1];
+      attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(currentVert-1) + NOR_ATTRIB+2];
+
+      attribs2[VERT2_ATTRIBS*currentVert + TEX_ATTRIB  ] = attribs2[VERT2_ATTRIBS*(currentVert-1) + TEX_ATTRIB  ];
+      attribs2[VERT2_ATTRIBS*currentVert + TEX_ATTRIB+1] = attribs2[VERT2_ATTRIBS*(currentVert-1) + TEX_ATTRIB+1];
+
+      currentVert++;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Supposed to return the latitude of the point between two points on the sphere with latitudes
+// lat1 and lat2, so if for example quot=0.2, then it will return the latitude of something 20%
+// along the way from lat1 to lat2.
+//
+// This is approximation only - in general it is of course not true that the midpoint of two points
+// on a unit sphere with spherical coords (A1,B1) and (A2,B2) is ( (A1+A2)/2, (B1+B2)/2 ) - take
+// (0,0) and (PI, epsilon) as a counterexample.
+//
+// Here however, the latitudes we are interested at are the latitudes of the vertices of a regular
+// icosahedron - i.e. +=A and +=PI/2, whose longitudes are close, and we don't really care if the
+// split into smaller triangles is exact.
+//
+// quot better be between 0.0 and 1.0.
+// this is 'directed' i.e. from lat1 to lat2.
+
+  private double midLatitude(double lat1, double lat2, double quot)
+    {
+    return lat1*(1.0-quot)+lat2*quot;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Same in case of longitude. This is for our needs exact, because we are ever only calling this with
+// two longitudes of two vertices with the same latitude. Additional problem: things can wrap around
+// the circle.
+// this is 'undirected' i.e. we don't assume from lon1 to lon2 - just along the smaller arc joining
+// lon1 to lon2.
+
+  private double midLongitude(double lon1, double lon2, double quot)
+    {
+    double min, max;
+
+    if( lon1<lon2 ) { min=lon1; max=lon2; }
+    else            { min=lon2; max=lon1; }
+
+    double diff = max-min;
+    if( diff>P ) { diff=2*P-diff; min=max; }
+
+    double ret = min+quot*diff;
+    if( ret>=2*P ) ret-=2*P;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// linear map (column,row, level):
+//
+// (      0,       0, level) -> (lonV1,latV12)
+// (      0, level-1, level) -> (lonV3,latV3 )
+// (level-1,       0, level) -> (lonV2,latV12)
+
+  private void addVertex(float[] attribs1, float[] attribs2, int column, int row, int level,
+                         double lonV1, double lonV2, double latV12, double latV3)
+    {
+    double quotX = (double)column/level;
+    double quotY = (double)row   /level;
+    double quotZ;
+
+    if( latV12*latV3 < 0.0 )  // equatorial triangle
+      {
+      quotZ = quotX + 0.5*quotY;
+      }
+    else                      // polar triangle
+      {
+      quotZ = (quotY==1.0 ? 0.5 : quotX / (1.0-quotY));
+      }
+
+    double longitude = midLongitude(lonV1, lonV2, quotZ );
+    double latitude  = midLatitude(latV12, latV3, quotY );
+
+    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 / 2.0f);
+    float y = (float)(sinLAT        / 2.0f);
+    float z = (float)(cosLAT*cosLON / 2.0f);
+
+    double texX = 0.5 + longitude/(2*P);
+    if( texX>=1.0 ) texX-=1.0;
+
+    double texY = 0.5 + latitude/P;
+
+    attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB  ] = x;  //
+    attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+1] = y;  //
+    attribs1[VERT1_ATTRIBS*currentVert + POS_ATTRIB+2] = z;  //
+                                                             //  In case of this Mesh so it happens that
+    attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB  ] = 2*x;//  the vertex coords, normal vector, and
+    attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+1] = 2*y;//  inflate vector have identical (x,y,z).
+    attribs1[VERT1_ATTRIBS*currentVert + NOR_ATTRIB+2] = 2*z;//
+
+    attribs2[VERT2_ATTRIBS*currentVert + TEX_ATTRIB  ] = (float)texX;
+    attribs2[VERT2_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, which looks like a seam with a narrow copy of
+    // the whole texture there. Solution: remap texX to 1.0.
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    if( currentVert>=3 && texX==0.0 )
+      {
+      double tex1 = attribs2[VERT2_ATTRIBS*(currentVert-2) + TEX_ATTRIB];
+      double tex2 = attribs2[VERT2_ATTRIBS*(currentVert-3) + TEX_ATTRIB];
+
+      // if the triangle is not degenerate and last vertex was on the western hemisphere
+      if( tex1!=tex2 && tex1>0.5 )
+        {
+        attribs2[VERT2_ATTRIBS*(currentVert-1) + TEX_ATTRIB] = 1.0f;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildFace(float[] attribs1, float[] attribs2, int level, int face, double lonV1, double lonV2, double latV12, double latV3)
+    {
+    for(int row=0; row<level; row++)
+      {
+      for (int column=0; column<level-row; column++)
+        {
+        addVertex(attribs1, attribs2, column, row  , level, lonV1, lonV2, latV12, latV3);
+        if (column==0 && !(face==0 && row==0 ) ) repeatVertex(attribs1, attribs2);
+        addVertex(attribs1, attribs2, column, row+1, level, lonV1, lonV2, latV12, latV3);
+        }
+
+      addVertex(attribs1, attribs2, level-row, row , level, lonV1, lonV2, latV12, latV3);
+      if( row!=level-1 || face!=NUMFACES-1 ) repeatVertex(attribs1, attribs2);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Creates the underlying grid of vertices with the usual attribs which approximates a sphere.
+   * <p>
+   * When level=1, it outputs the 12 vertices of a regular icosahedron.
+   * When level=N, it divides each of the 20 icosaherdon's triangular faces into N^2 smaller triangles
+   * (by dividing each side into N equal segments) and 'inflates' the internal vertices so that they
+   * touch the sphere.
+   *
+   * @param level Specifies the approximation level. Valid values: level &ge; 1
+   */
+  public MeshSphere(int level)
+    {
+    super();
+
+    computeNumberOfVertices(level);
+    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+    for(int face=0; face<NUMFACES; face++ )
+      {
+      buildFace(attribs1, attribs2, level, face, FACES[face][0], FACES[face][1], FACES[face][2], FACES[face][3]);
+      }
+
+    if( currentVert!=numVertices )
+      DistortedLibrary.logMessage("MeshSphere: currentVert= " +currentVert+" numVertices="+numVertices );
+
+    setAttribs(attribs1, attribs2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy cconstructor.
+ */
+  public MeshSphere(MeshSphere 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 MeshSphere copy(boolean deep)
+    {
+    return new MeshSphere(this,deep);
+    }
+  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshSquare.java b/src/main/java/org/distorted/library/mesh/MeshSquare.java
deleted file mode 100644
index 41566ca..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshSquare.java
+++ /dev/null
@@ -1,252 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2017 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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Create a flat, rectangular grid.
- * <p>
- * Perfect if you just want to display a flat Texture. If you are not planning to apply any VERTEX
- * effects to it, use MeshRectangles(1,1), i.e. a Quad. Otherwise, create more vertices for more realistic effects!
- */
-public class MeshSquare extends MeshBase
-  {
-  private int mCols, mRows;
-  private int remainingVert;
-  private int numVertices;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Create a flat, full grid.
-
-  private void computeNumberOfVertices(int cols, int rows)
-     {
-     mRows=rows;
-     mCols=cols;
-
-     if( cols==1 && rows==1 )
-       {
-       numVertices = 4;
-       }
-     else
-       {
-       numVertices = 2*( mRows*mCols +2*mRows - 1) +2*(mCols>=2 ? mRows:0) +
-                     (mCols>=2 && mRows>=2 ? 2*mRows-2 : 1);
-       }
-
-     remainingVert = numVertices;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int addVertex(int vertex, float x, float y, float[] attribs1, float[] attribs2)
-     {
-     remainingVert--;
-
-     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] =    x;
-     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] =   -y;
-     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = 0.0f;
-
-     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB  ] = 0.0f;
-     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+1] = 0.0f;
-     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+2] = 1.0f;
-
-     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = x+0.5f;
-     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = 0.5f-y;
-
-     return vertex+1;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int repeatLast(int vertex, float[] attribs1, float[] attribs2)
-     {
-     if( vertex>0 )
-       {
-       remainingVert--;
-
-       attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(vertex-1) + POS_ATTRIB  ];
-       attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(vertex-1) + POS_ATTRIB+1];
-       attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(vertex-1) + POS_ATTRIB+2];
-
-       attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(vertex-1) + NOR_ATTRIB  ];
-       attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(vertex-1) + NOR_ATTRIB+1];
-       attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(vertex-1) + NOR_ATTRIB+2];
-
-       attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = attribs2[VERT2_ATTRIBS*(vertex-1) + TEX_ATTRIB  ];
-       attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = attribs2[VERT2_ATTRIBS*(vertex-1) + TEX_ATTRIB+1];
-
-       vertex++;
-       }
-
-     return vertex;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildGrid(float[] attribs1, float[] attribs2)
-     {
-     boolean currBlockIsNE, lastBlockIsNE = false;
-     int vertex = 0;
-     float x,y;
-     final float dx = 1.0f/mCols;
-     final float dy = 1.0f/mRows;
-
-     y =-0.5f;
-
-     for(int row=0; row<mRows; row++)
-       {
-       x =-0.5f;
-
-       for(int col=0; col<mCols; col++)
-         {
-         currBlockIsNE = (2*row<=mRows-1)^(2*col<=mCols-1);
-
-         if( col==0 || (lastBlockIsNE^currBlockIsNE) )
-           {
-           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
-           vertex= addVertex( vertex, x, y+(currBlockIsNE?0:dy), attribs1,attribs2);
-           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
-           if( lastBlockIsNE^currBlockIsNE)  vertex = repeatLast(vertex,attribs1,attribs2);
-           vertex= addVertex( vertex, x, y+(currBlockIsNE?dy:0), attribs1,attribs2);
-           }
-         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?0:dy), attribs1,attribs2);
-         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?dy:0), attribs1,attribs2);
-
-         lastBlockIsNE = currBlockIsNE;
-         x+=dx;
-         }
-
-       y+=dy;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildGrid(float[] attribs1, float[] attribs2, float[] xLoc, float[] yLoc)
-     {
-     boolean currBlockIsNE,lastBlockIsNE = false;
-     int vertex = 0;
-     float dx,dy,x,y= yLoc[0];
-
-     for(int row=0; row<mRows; row++)
-       {
-       x = xLoc[0];
-       dy= yLoc[row+1];
-
-       for(int col=0; col<mCols; col++)
-         {
-         dx = xLoc[col+1];
-         currBlockIsNE = (2*row<=mRows-1)^(2*col<=mCols-1);
-
-         if( col==0 || (lastBlockIsNE^currBlockIsNE) )
-           {
-           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
-           vertex= addVertex( vertex, x, y+(currBlockIsNE?0:dy), attribs1,attribs2);
-           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
-           if( lastBlockIsNE^currBlockIsNE)  vertex = repeatLast(vertex,attribs1,attribs2);
-           vertex= addVertex( vertex, x, y+(currBlockIsNE?dy:0), attribs1,attribs2);
-           }
-         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?0:dy), attribs1,attribs2);
-         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?dy:0), attribs1,attribs2);
-
-         lastBlockIsNE = currBlockIsNE;
-         x+=dx;
-         }
-
-       y+=dy;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Creates a rectangular grid of vertices, normals and texture coords.
- *
- * @param cols Number of columns in the grid.
- * @param rows Number of rows in the grid.
- */
-  public MeshSquare(int cols, int rows)
-    {
-    super();
-    computeNumberOfVertices(cols,rows);
-
-    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
-    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
-
-    buildGrid(attribs1,attribs2);
-
-    if( remainingVert!=0 )
-      DistortedLibrary.logMessage("MeshSquare: remainingVert " +remainingVert );
-
-    setAttribs(attribs1,attribs2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Creates a rectangular grid of vertices, normals and texture coords.
- *
- * @param xLoc list of x-coordinates of vertices. First value: distance of the left edge from 0.
- *             Next values: distance of the next column from the previous. Must be NonNull!
- * @param yLoc list of y-coordinates of vertices. First value: distance of the bottom edge from 0.
- *             Next values: distance of the next row from the previous. Must be NonNull!
- */
-  public MeshSquare(float[] xLoc, float[] yLoc)
-    {
-    super();
-    computeNumberOfVertices(xLoc.length-1,yLoc.length-1);
-
-    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
-    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
-
-    buildGrid(attribs1,attribs2,xLoc,yLoc);
-
-    if( remainingVert!=0 )
-      DistortedLibrary.logMessage("MeshSquare: remainingVert " +remainingVert );
-
-    setAttribs(attribs1,attribs2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshSquare(MeshSquare 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 MeshSquare copy(boolean deep)
-    {
-    return new MeshSquare(this,deep);
-    }
- }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshSquare.kt b/src/main/java/org/distorted/library/mesh/MeshSquare.kt
new file mode 100644
index 0000000..41566ca
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshSquare.kt
@@ -0,0 +1,252 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2017 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Create a flat, rectangular grid.
+ * <p>
+ * Perfect if you just want to display a flat Texture. If you are not planning to apply any VERTEX
+ * effects to it, use MeshRectangles(1,1), i.e. a Quad. Otherwise, create more vertices for more realistic effects!
+ */
+public class MeshSquare extends MeshBase
+  {
+  private int mCols, mRows;
+  private int remainingVert;
+  private int numVertices;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Create a flat, full grid.
+
+  private void computeNumberOfVertices(int cols, int rows)
+     {
+     mRows=rows;
+     mCols=cols;
+
+     if( cols==1 && rows==1 )
+       {
+       numVertices = 4;
+       }
+     else
+       {
+       numVertices = 2*( mRows*mCols +2*mRows - 1) +2*(mCols>=2 ? mRows:0) +
+                     (mCols>=2 && mRows>=2 ? 2*mRows-2 : 1);
+       }
+
+     remainingVert = numVertices;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int addVertex(int vertex, float x, float y, float[] attribs1, float[] attribs2)
+     {
+     remainingVert--;
+
+     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] =    x;
+     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] =   -y;
+     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = 0.0f;
+
+     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB  ] = 0.0f;
+     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+1] = 0.0f;
+     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+2] = 1.0f;
+
+     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = x+0.5f;
+     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = 0.5f-y;
+
+     return vertex+1;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int repeatLast(int vertex, float[] attribs1, float[] attribs2)
+     {
+     if( vertex>0 )
+       {
+       remainingVert--;
+
+       attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(vertex-1) + POS_ATTRIB  ];
+       attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(vertex-1) + POS_ATTRIB+1];
+       attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(vertex-1) + POS_ATTRIB+2];
+
+       attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB  ] = attribs1[VERT1_ATTRIBS*(vertex-1) + NOR_ATTRIB  ];
+       attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+1] = attribs1[VERT1_ATTRIBS*(vertex-1) + NOR_ATTRIB+1];
+       attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+2] = attribs1[VERT1_ATTRIBS*(vertex-1) + NOR_ATTRIB+2];
+
+       attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = attribs2[VERT2_ATTRIBS*(vertex-1) + TEX_ATTRIB  ];
+       attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = attribs2[VERT2_ATTRIBS*(vertex-1) + TEX_ATTRIB+1];
+
+       vertex++;
+       }
+
+     return vertex;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildGrid(float[] attribs1, float[] attribs2)
+     {
+     boolean currBlockIsNE, lastBlockIsNE = false;
+     int vertex = 0;
+     float x,y;
+     final float dx = 1.0f/mCols;
+     final float dy = 1.0f/mRows;
+
+     y =-0.5f;
+
+     for(int row=0; row<mRows; row++)
+       {
+       x =-0.5f;
+
+       for(int col=0; col<mCols; col++)
+         {
+         currBlockIsNE = (2*row<=mRows-1)^(2*col<=mCols-1);
+
+         if( col==0 || (lastBlockIsNE^currBlockIsNE) )
+           {
+           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
+           vertex= addVertex( vertex, x, y+(currBlockIsNE?0:dy), attribs1,attribs2);
+           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
+           if( lastBlockIsNE^currBlockIsNE)  vertex = repeatLast(vertex,attribs1,attribs2);
+           vertex= addVertex( vertex, x, y+(currBlockIsNE?dy:0), attribs1,attribs2);
+           }
+         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?0:dy), attribs1,attribs2);
+         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?dy:0), attribs1,attribs2);
+
+         lastBlockIsNE = currBlockIsNE;
+         x+=dx;
+         }
+
+       y+=dy;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildGrid(float[] attribs1, float[] attribs2, float[] xLoc, float[] yLoc)
+     {
+     boolean currBlockIsNE,lastBlockIsNE = false;
+     int vertex = 0;
+     float dx,dy,x,y= yLoc[0];
+
+     for(int row=0; row<mRows; row++)
+       {
+       x = xLoc[0];
+       dy= yLoc[row+1];
+
+       for(int col=0; col<mCols; col++)
+         {
+         dx = xLoc[col+1];
+         currBlockIsNE = (2*row<=mRows-1)^(2*col<=mCols-1);
+
+         if( col==0 || (lastBlockIsNE^currBlockIsNE) )
+           {
+           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
+           vertex= addVertex( vertex, x, y+(currBlockIsNE?0:dy), attribs1,attribs2);
+           if( row!=0 && col==0 ) vertex = repeatLast(vertex,attribs1,attribs2);
+           if( lastBlockIsNE^currBlockIsNE)  vertex = repeatLast(vertex,attribs1,attribs2);
+           vertex= addVertex( vertex, x, y+(currBlockIsNE?dy:0), attribs1,attribs2);
+           }
+         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?0:dy), attribs1,attribs2);
+         vertex= addVertex( vertex, x+dx, y+(currBlockIsNE?dy:0), attribs1,attribs2);
+
+         lastBlockIsNE = currBlockIsNE;
+         x+=dx;
+         }
+
+       y+=dy;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates a rectangular grid of vertices, normals and texture coords.
+ *
+ * @param cols Number of columns in the grid.
+ * @param rows Number of rows in the grid.
+ */
+  public MeshSquare(int cols, int rows)
+    {
+    super();
+    computeNumberOfVertices(cols,rows);
+
+    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+    buildGrid(attribs1,attribs2);
+
+    if( remainingVert!=0 )
+      DistortedLibrary.logMessage("MeshSquare: remainingVert " +remainingVert );
+
+    setAttribs(attribs1,attribs2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Creates a rectangular grid of vertices, normals and texture coords.
+ *
+ * @param xLoc list of x-coordinates of vertices. First value: distance of the left edge from 0.
+ *             Next values: distance of the next column from the previous. Must be NonNull!
+ * @param yLoc list of y-coordinates of vertices. First value: distance of the bottom edge from 0.
+ *             Next values: distance of the next row from the previous. Must be NonNull!
+ */
+  public MeshSquare(float[] xLoc, float[] yLoc)
+    {
+    super();
+    computeNumberOfVertices(xLoc.length-1,yLoc.length-1);
+
+    float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+    float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+    buildGrid(attribs1,attribs2,xLoc,yLoc);
+
+    if( remainingVert!=0 )
+      DistortedLibrary.logMessage("MeshSquare: remainingVert " +remainingVert );
+
+    setAttribs(attribs1,attribs2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshSquare(MeshSquare 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 MeshSquare copy(boolean deep)
+    {
+    return new MeshSquare(this,deep);
+    }
+ }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/mesh/MeshTriangle.java b/src/main/java/org/distorted/library/mesh/MeshTriangle.java
deleted file mode 100644
index 386ef3c..0000000
--- a/src/main/java/org/distorted/library/mesh/MeshTriangle.java
+++ /dev/null
@@ -1,146 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-/**
- * Create a Mesh which approximates a triangle with vertices at (-0.5,-0.5),(+0.5,-0.5),(0.0,0.5)
- */
-public class MeshTriangle extends MeshBase
-  {
-  private int numVertices;
-  private int remainingVert;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int addVertex(int vertex, float x, float y, float[] attribs1, float[] attribs2)
-     {
-     remainingVert--;
-
-     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x-0.5f;
-     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y-0.5f;
-     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = 0.0f;
-
-     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB  ] = 0.0f;
-     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+1] = 0.0f;
-     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+2] = 1.0f;
-
-     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = x;
-     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = y;
-
-     return vertex+1;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeNumberOfVertices(int level)
-     {
-     numVertices = level*(level+2);
-     remainingVert = numVertices;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int buildRow(int vertex,float sx, float sy, int length, float dx, float dy, float[] attribs1, float[] attribs2)
-    {
-    for(int i=0; i<length; i++)
-      {
-      vertex = addVertex(vertex, sx   , sy   , attribs1, attribs2);
-      vertex = addVertex(vertex, sx+dx, sy+dy, attribs1, attribs2);
-      sx += 2*dx;
-      }
-
-    vertex = addVertex(vertex, sx, sy, attribs1, attribs2);
-
-    return vertex;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildGrid(float[] attribs1, float[] attribs2, int level)
-     {
-     float sx = 0.0f;
-     float sy = 0.0f;
-     float dx = 0.5f/level;
-     float dy = 1.0f/level;
-     int vertex = 0;
-
-     for(int row=level; row>=1; row--)
-       {
-       vertex = buildRow(vertex,sx,sy,row,dx,dy,attribs1,attribs2);
-
-       sx += 2*dx*(row-0.5f);
-       sy += dy;
-       dx *= -1;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Creates the underlying grid of vertices with the usual attribs which approximates a triangle
-   * with vertices at (-0.5,-0.5),(+0.5,-0.5),(0.0,0.5)
-   *
-   * @param level Specifies the level of slicing. Valid values: level &ge; 1
-   */
-  public MeshTriangle(int level)
-     {
-     super();
-
-     computeNumberOfVertices(level);
-
-     float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
-     float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
-
-     buildGrid(attribs1, attribs2, level);
-
-     if( remainingVert!=0 )
-       DistortedLibrary.logMessage("MeshTriangle: remainingVert " +remainingVert );
-
-     setAttribs(attribs1, attribs2);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- */
-  public MeshTriangle(MeshTriangle 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 MeshTriangle copy(boolean deep)
-    {
-    return new MeshTriangle(this,deep);
-    }
-  }
diff --git a/src/main/java/org/distorted/library/mesh/MeshTriangle.kt b/src/main/java/org/distorted/library/mesh/MeshTriangle.kt
new file mode 100644
index 0000000..386ef3c
--- /dev/null
+++ b/src/main/java/org/distorted/library/mesh/MeshTriangle.kt
@@ -0,0 +1,146 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Create a Mesh which approximates a triangle with vertices at (-0.5,-0.5),(+0.5,-0.5),(0.0,0.5)
+ */
+public class MeshTriangle extends MeshBase
+  {
+  private int numVertices;
+  private int remainingVert;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int addVertex(int vertex, float x, float y, float[] attribs1, float[] attribs2)
+     {
+     remainingVert--;
+
+     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB  ] = x-0.5f;
+     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+1] = y-0.5f;
+     attribs1[VERT1_ATTRIBS*vertex + POS_ATTRIB+2] = 0.0f;
+
+     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB  ] = 0.0f;
+     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+1] = 0.0f;
+     attribs1[VERT1_ATTRIBS*vertex + NOR_ATTRIB+2] = 1.0f;
+
+     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB  ] = x;
+     attribs2[VERT2_ATTRIBS*vertex + TEX_ATTRIB+1] = y;
+
+     return vertex+1;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeNumberOfVertices(int level)
+     {
+     numVertices = level*(level+2);
+     remainingVert = numVertices;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int buildRow(int vertex,float sx, float sy, int length, float dx, float dy, float[] attribs1, float[] attribs2)
+    {
+    for(int i=0; i<length; i++)
+      {
+      vertex = addVertex(vertex, sx   , sy   , attribs1, attribs2);
+      vertex = addVertex(vertex, sx+dx, sy+dy, attribs1, attribs2);
+      sx += 2*dx;
+      }
+
+    vertex = addVertex(vertex, sx, sy, attribs1, attribs2);
+
+    return vertex;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildGrid(float[] attribs1, float[] attribs2, int level)
+     {
+     float sx = 0.0f;
+     float sy = 0.0f;
+     float dx = 0.5f/level;
+     float dy = 1.0f/level;
+     int vertex = 0;
+
+     for(int row=level; row>=1; row--)
+       {
+       vertex = buildRow(vertex,sx,sy,row,dx,dy,attribs1,attribs2);
+
+       sx += 2*dx*(row-0.5f);
+       sy += dy;
+       dx *= -1;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Creates the underlying grid of vertices with the usual attribs which approximates a triangle
+   * with vertices at (-0.5,-0.5),(+0.5,-0.5),(0.0,0.5)
+   *
+   * @param level Specifies the level of slicing. Valid values: level &ge; 1
+   */
+  public MeshTriangle(int level)
+     {
+     super();
+
+     computeNumberOfVertices(level);
+
+     float[] attribs1= new float[VERT1_ATTRIBS*numVertices];
+     float[] attribs2= new float[VERT2_ATTRIBS*numVertices];
+
+     buildGrid(attribs1, attribs2, level);
+
+     if( remainingVert!=0 )
+       DistortedLibrary.logMessage("MeshTriangle: remainingVert " +remainingVert );
+
+     setAttribs(attribs1, attribs2);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ */
+  public MeshTriangle(MeshTriangle 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 MeshTriangle copy(boolean deep)
+    {
+    return new MeshTriangle(this,deep);
+    }
+  }
