commit e8925fcde66a1ada1f590b690c3ac812f7e5fa01
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Jun 5 00:34:31 2020 +0100

    Big change to MeshBase:
    1) split the list of Components into two: 'texture map' components and 'effect' components.
       Reason: imagine the Rubik Cube with a Solver. When setting up a initial position in the Solver, we need to be able to set texture maps of each individual face of each cubit.
       Thus previously (when there was only one Component which kept info both about Effect associations and Texture Maps) a 5x5x5 Rubik Cube would have to have 98*6 = almost 600 components.
       This alone would mean 1200 uniforms in the Vertex Shader --> more than the guaranteed 1024.
       Splitting the Component into two parts lets us merge the 6 'Effect' components of each Rubik Cubit into one (thus leaving only 98 Effect Components in the Verteex Shader --> 196 uniforms) while still allowing for change of the texture map of each individual face.
    2) (re-) add the 'apply a matrix effect' (this time with associations)

diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
index 6ceabb7..b47ce67 100644
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
@@ -126,7 +126,7 @@ public class EffectQueuePostprocess extends EffectQueue
     String mainVertHeader= version + ("#define NUM_VERTEX "   + ( numV>0 ? DistortedLibrary.getMax(EffectType.VERTEX  ) : 0 ) + "\n");
     String mainFragHeader= version + "\n";
 
-    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxComponents() + "\n";
+    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
 
     String enabledEffectV= VertexEffect.getGLSL();
 
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.java b/src/main/java/org/distorted/library/main/DistortedLibrary.java
index ddf485c..a18007b 100644
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.java
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.java
@@ -218,7 +218,7 @@ public class DistortedLibrary
     String mainVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n");
     String mainFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n");
 
-    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxComponents() + "\n";
+    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
 
     String enabledEffectV= VertexEffect.getGLSL();
     String enabledEffectF= FragmentEffect.getGLSL();
@@ -309,7 +309,7 @@ public class DistortedLibrary
     String fullVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX ) : 0 ) + "\n");
     String fullFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " +                                         0   + "\n");
 
-    fullVertHeader += "#define MAX_COMPON " + MeshBase.getMaxComponents() + "\n";
+    fullVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
 
     String enabledEffectV= VertexEffect.getAllGLSL();
     String enabledEffectF= "{}";
@@ -348,7 +348,7 @@ public class DistortedLibrary
     String mainVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n") + ("#define OIT\n");
     String mainFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n") + ("#define OIT\n");
 
-    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxComponents() + "\n";
+    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
 
     String enabledEffectV= VertexEffect.getGLSL();
     String enabledEffectF= FragmentEffect.getGLSL();
diff --git a/src/main/java/org/distorted/library/mesh/DeferredJobs.java b/src/main/java/org/distorted/library/mesh/DeferredJobs.java
index 50f539b..fa82c72 100644
--- a/src/main/java/org/distorted/library/mesh/DeferredJobs.java
+++ b/src/main/java/org/distorted/library/mesh/DeferredJobs.java
@@ -19,6 +19,7 @@
 
 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;
@@ -36,11 +37,13 @@ import android.util.Log;
  */
 public class DeferredJobs
   {
-  private static final int JOB_TYPE_VERTEX = 0;
-  private static final int JOB_TYPE_MERGE  = 1;
-  private static final int JOB_TYPE_JOIN   = 2;
-  private static final int JOB_TYPE_COPY   = 3;
-  private static final int JOB_TYPE_TEXTURE= 4;
+  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 ArrayList<JobNode> mJobs = new ArrayList<>();
 
@@ -51,69 +54,75 @@ public class DeferredJobs
     private int mType;
     private MeshBase mTarget;
     private MeshBase[] mSource;
-    private EffectQueueVertex mEffects;
+    private EffectQueueVertex mVertexEffects;
+    private MatrixEffect mMatrixEffect;
     private Static4D[] mMaps;
+    private int mAndAssoc, mEcuAssoc;
 
-    Job(int type, MeshBase target, MeshBase[] source, VertexEffect effect, Static4D[] maps)
+    Job(int type, MeshBase target, MeshBase[] source, VertexEffect vEff, MatrixEffect mEff, Static4D[] maps, int and, int ecu)
       {
-      mType   = type;
-      mTarget = target;
-      mSource = source;
-      mMaps   = maps;
-
-      if( effect!=null )
+      mType     = type;
+      mTarget   = target;
+      mSource   = source;
+      mMaps     = maps;
+      mAndAssoc = and;
+      mEcuAssoc = ecu;
+
+      if( vEff!=null )
         {
-        mEffects= new EffectQueueVertex();
-        mEffects.add(effect);
+        mVertexEffects= new EffectQueueVertex();
+        mVertexEffects.add(vEff);
         }
+
+      mMatrixEffect = mEff;
       }
 
     void addEffect(VertexEffect effect)
       {
-      mEffects.add(effect);
+      mVertexEffects.add(effect);
       }
 
     void execute()
       {
       switch(mType)
         {
-        case JOB_TYPE_VERTEX : DistortedLibrary.adjustVertices(mTarget, mEffects);
-                               //Log.e("jobs", "executing vertex job");
-                               //mTarget.print();
-                               break;
-        case JOB_TYPE_MERGE  : mTarget.merge();
-                               //Log.e("jobs", "executing merge job");
-                               //mTarget.print();
-                               break;
-        case JOB_TYPE_JOIN   : mTarget.joinAttribs(mSource);
-                               //Log.e("jobs", "executing join job");
-                               //mTarget.print();
-                               break;
-        case JOB_TYPE_COPY   : mTarget.copy(mSource[0]);
-                               //Log.e("jobs", "executing copy job");
-                               //mTarget.print();
-                               break;
-        case JOB_TYPE_TEXTURE: mTarget.textureMap(mMaps);
-                               //Log.e("jobs", "executing textureMap job");
-                               //mTarget.print();
-                               break;
+        case JOB_TYPE_VERTEX   : DistortedLibrary.adjustVertices(mTarget, mVertexEffects);
+                                 break;
+        case JOB_TYPE_MATRIX   : mTarget.applyMatrix(mMatrixEffect,mAndAssoc,mEcuAssoc);
+                                 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);
+                                 break;
         }
+/*
+      Log.e("jobs", "executing "+print()+" job");
+      mTarget.print();
+*/
       }
 
     void clear()
       {
-      if( mEffects!=null ) mEffects.removeAll(false);
+      if( mVertexEffects!=null ) mVertexEffects.removeAll(false);
       }
 
     String print()
       {
       switch(mType)
         {
-        case JOB_TYPE_VERTEX : return "VERTEX (eff: "+mEffects.getNumEffects()+" effToBe: "+mEffects.getNumEffectsToBe()+")";
-        case JOB_TYPE_MERGE  : return "MERGE";
-        case JOB_TYPE_JOIN   : return "JOIN: "+mSource.length;
-        case JOB_TYPE_COPY   : return "COPY";
-        case JOB_TYPE_TEXTURE: return "TEXTURE";
+        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";
         }
 
       return null;
@@ -217,7 +226,7 @@ public class DeferredJobs
 
     if( jn==null )
       {
-      Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null);
+      Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null,null,0,0);
       JobNode node = new JobNode(job);
       mJobs.add(node);
       return node;
@@ -231,7 +240,7 @@ public class DeferredJobs
         }
       else
         {
-        Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null);
+        Job job = new Job(JOB_TYPE_VERTEX,target,null,effect,null,null,0,0);
         JobNode node = new JobNode(job);
         node.mPrevJobs.add(jn);
         jn.mNextJobs.add(node);
@@ -243,10 +252,36 @@ public class DeferredJobs
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static JobNode merge(MeshBase target)
+  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,andAssoc,ecuAssoc);
+    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);
+    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,target,null,null,null);
+    Job job = new Job(JOB_TYPE_MERGE_EFF,target,null,null,null,null,0,0);
     JobNode node = new JobNode(job);
     node.mPrevJobs.add(jn);
     jn.mNextJobs.add(node);
@@ -260,7 +295,7 @@ public class DeferredJobs
     {
     JobNode jn;
 
-    Job job = new Job(JOB_TYPE_JOIN,target,meshes,null,null);
+    Job job = new Job(JOB_TYPE_JOIN,target,meshes,null,null,null,0,0);
     JobNode node = new JobNode(job);
 
     for (MeshBase mesh : meshes)
@@ -285,7 +320,7 @@ public class DeferredJobs
     JobNode jn = mesh.mJobNode[0];
     MeshBase[] meshes = new MeshBase[1];
     meshes[0] = mesh;
-    Job job = new Job(JOB_TYPE_COPY,target,meshes,null,null);
+    Job job = new Job(JOB_TYPE_COPY,target,meshes,null,null,null,0,0);
     JobNode node = new JobNode(job);
     node.mPrevJobs.add(jn);
     jn.mNextJobs.add(node);
@@ -298,7 +333,7 @@ public class DeferredJobs
   static JobNode textureMap(MeshBase target, Static4D[] maps)
     {
     JobNode jn = target.mJobNode[0];
-    Job job = new Job(JOB_TYPE_TEXTURE,target,null,null,maps);
+    Job job = new Job(JOB_TYPE_TEXTURE,target,null,null,null,maps,0,0);
     JobNode node = new JobNode(job);
     node.mPrevJobs.add(jn);
     jn.mNextJobs.add(node);
diff --git a/src/main/java/org/distorted/library/mesh/MeshBase.java b/src/main/java/org/distorted/library/mesh/MeshBase.java
index 8e8c85f..39abe09 100644
--- a/src/main/java/org/distorted/library/mesh/MeshBase.java
+++ b/src/main/java/org/distorted/library/mesh/MeshBase.java
@@ -20,8 +20,10 @@
 package org.distorted.library.mesh;
 
 import android.opengl.GLES30;
+import android.opengl.Matrix;
 import android.util.Log;
 
+import org.distorted.library.effect.MatrixEffect;
 import org.distorted.library.effect.VertexEffect;
 import org.distorted.library.effectqueue.EffectQueue;
 import org.distorted.library.main.InternalBuffer;
@@ -42,7 +44,7 @@ import java.util.ArrayList;
  */
 public abstract class MeshBase
    {
-   private static final int MAX_COMPONENTS= 100;
+   private static final int MAX_EFFECT_COMPONENTS= 100;
    private static final int DEFAULT_ASSOCIATION = 0xffffffff;
 
    // sizes of attributes of an individual vertex.
@@ -88,17 +90,17 @@ public abstract class MeshBase
    private static int[] mEquAssociationH = new int[EffectQueue.MAIN_VARIANTS];
    private static int[] mAndAssociationH = new int[EffectQueue.MAIN_VARIANTS];
 
-   private static class Component
+   private static class TexComponent
      {
      private int mEndIndex;
      private Static4D mTextureMap;
 
-     Component(int end)
+     TexComponent(int end)
        {
        mEndIndex  = end;
        mTextureMap= new Static4D(0,0,1,1);
        }
-     Component(Component original)
+     TexComponent(TexComponent original)
        {
        mEndIndex = original.mEndIndex;
 
@@ -115,21 +117,24 @@ public abstract class MeshBase
        }
      }
 
-   private ArrayList<Component> mComponent;
+   private ArrayList<TexComponent> mTexComponent;
+   private ArrayList<Integer> mEffComponent;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    MeshBase()
      {
-     mShowNormals= false;
-     mInflate    = 0.0f;
-     mComponent  = new ArrayList<>();
-     mEquAssociation= new int[MAX_COMPONENTS];
-     mAndAssociation= new int[MAX_COMPONENTS];
+     mShowNormals  = false;
+     mInflate      = 0.0f;
+     mTexComponent = new ArrayList<>();
+     mEffComponent = new ArrayList<>();
+
+     mEquAssociation= new int[MAX_EFFECT_COMPONENTS];
+     mAndAssociation= new int[MAX_EFFECT_COMPONENTS];
 
      mJobNode = new DeferredJobs.JobNode[1];
 
-     for(int i=0; i<MAX_COMPONENTS; i++)
+     for(int i=0; i<MAX_EFFECT_COMPONENTS; i++)
        {
        mAndAssociation[i] = DEFAULT_ASSOCIATION;
        mEquAssociation[i] = i;
@@ -149,10 +154,10 @@ public abstract class MeshBase
      mInflate    = original.mInflate;
      mNumVertices= original.mNumVertices;
 
-     mAndAssociation= new int[MAX_COMPONENTS];
-     System.arraycopy(original.mAndAssociation, 0, mAndAssociation, 0, MAX_COMPONENTS);
-     mEquAssociation= new int[MAX_COMPONENTS];
-     System.arraycopy(original.mEquAssociation, 0, mEquAssociation, 0, MAX_COMPONENTS);
+     mAndAssociation= new int[MAX_EFFECT_COMPONENTS];
+     System.arraycopy(original.mAndAssociation, 0, mAndAssociation, 0, MAX_EFFECT_COMPONENTS);
+     mEquAssociation= new int[MAX_EFFECT_COMPONENTS];
+     System.arraycopy(original.mEquAssociation, 0, mEquAssociation, 0, MAX_EFFECT_COMPONENTS);
 
      if( deep )
        {
@@ -188,15 +193,18 @@ public abstract class MeshBase
 
    private void shallowCopy(MeshBase original)
      {
-     int size = original.mComponent.size();
-     mComponent = new ArrayList<>();
+     int texComSize = original.mTexComponent.size();
+     mTexComponent = new ArrayList<>();
 
-     for(int i=0; i<size; i++)
+     for(int i=0; i<texComSize; i++)
        {
-       Component comp = new Component(original.mComponent.get(i));
-       mComponent.add(comp);
+       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_READ);
      mVertAttribs2= new float[mNumVertices*VERT2_ATTRIBS];
      System.arraycopy(original.mVertAttribs2,0,mVertAttribs2,0,mNumVertices*VERT2_ATTRIBS);
@@ -205,14 +213,29 @@ public abstract class MeshBase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   void merge()
+   void mergeTexComponentsNow()
      {
-     int num = mComponent.size();
+     int num = mTexComponent.size();
 
      if( num>1 )
        {
-       mComponent.clear();
-       mComponent.add(new Component(mNumVertices-1));
+       mTexComponent.clear();
+       mTexComponent.add(new TexComponent(mNumVertices-1));
+
+       mVBO2.invalidate();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void mergeEffComponentsNow()
+     {
+     int num = mEffComponent.size();
+
+     if( num>1 )
+       {
+       mEffComponent.clear();
+       mEffComponent.add(mNumVertices-1);
 
        for(int index=0; index<mNumVertices; index++)
          {
@@ -225,9 +248,76 @@ public abstract class MeshBase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   int numComponents()
+   void applyMatrix(MatrixEffect effect, int andAssoc, int equAssoc)
+     {
+     float[] matrix   = new float[16];
+     float[] uniforms = new float[7];
+     int start, end=-1, numComp = mEffComponent.size();
+
+     Matrix.setIdentityM(matrix,0);
+     effect.compute(uniforms,0,0,0);
+     effect.apply(matrix, uniforms, 0);
+
+     for(int i=0; i<numComp; i++)
+       {
+       start = end;
+       end   = mEffComponent.get(i);
+
+       if( (andAssoc & mAndAssociation[i]) != 0 || (equAssoc == mEquAssociation[i]) )
+         {
+         applyMatrixToComponent(matrix,start+1,end+1);
+         }
+       }
+
+     mVBO1.invalidate();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void applyMatrixToComponent(float[] matrix, int start, int end)
+     {
+     float x,y,z;
+
+     for(int index=start; 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  ] = matrix[0]*x + matrix[4]*y + matrix[ 8]*z + matrix[12];
+       mVertAttribs1[index+POS_ATTRIB+1] = matrix[1]*x + matrix[5]*y + matrix[ 9]*z + matrix[13];
+       mVertAttribs1[index+POS_ATTRIB+2] = matrix[2]*x + matrix[6]*y + matrix[10]*z + matrix[14];
+
+       x = mVertAttribs1[index+NOR_ATTRIB  ];
+       y = mVertAttribs1[index+NOR_ATTRIB+1];
+       z = mVertAttribs1[index+NOR_ATTRIB+2];
+
+       mVertAttribs1[index+NOR_ATTRIB  ] = matrix[0]*x + matrix[4]*y + matrix[ 8]*z;
+       mVertAttribs1[index+NOR_ATTRIB+1] = matrix[1]*x + matrix[5]*y + matrix[ 9]*z;
+       mVertAttribs1[index+NOR_ATTRIB+2] = matrix[2]*x + matrix[6]*y + matrix[10]*z;
+
+       x = mVertAttribs1[index+INF_ATTRIB  ];
+       y = mVertAttribs1[index+INF_ATTRIB+1];
+       z = mVertAttribs1[index+INF_ATTRIB+2];
+
+       mVertAttribs1[index+INF_ATTRIB  ] = matrix[0]*x + matrix[4]*y + matrix[ 8]*z;
+       mVertAttribs1[index+INF_ATTRIB+1] = matrix[1]*x + matrix[5]*y + matrix[ 9]*z;
+       mVertAttribs1[index+INF_ATTRIB+2] = matrix[2]*x + matrix[6]*y + matrix[10]*z;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   int numTexComponents()
+     {
+     return mTexComponent.size();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   int numEffComponents()
      {
-     return mComponent.size();
+     return mEffComponent.size();
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -239,7 +329,8 @@ public abstract class MeshBase
      mVertAttribs1= vert1Attribs;
      mVertAttribs2= vert2Attribs;
 
-     mComponent.add(new Component(mNumVertices-1));
+     mTexComponent.add(new TexComponent(mNumVertices-1));
+     mEffComponent.add(mNumVertices-1);
 
      mVBO1.invalidate();
      mVBO2.invalidate();
@@ -250,38 +341,49 @@ public abstract class MeshBase
    void joinAttribs(MeshBase[] meshes)
      {
      MeshBase mesh;
-     Component comp;
+     TexComponent comp;
      int numMeshes = meshes.length;
-     int origVertices = mNumVertices;
-     int origComponents=0,numComponents,numVertices;
+     int numVertices,origVertices = mNumVertices;
+     int origTexComponents=0,numTexComponents;
+     int origEffComponents=0,numEffComponents;
 
      if( origVertices>0 )
        {
-       origComponents = mComponent.size();
+       origTexComponents = mTexComponent.size();
        mNumVertices+= ( mNumVertices%2==1 ? 2:1 );
-       mComponent.get(origComponents-1).mEndIndex = mNumVertices-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];
-       numComponents = mesh.mComponent.size();
+       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<numComponents; j++)
+       for(int j=0; j<numTexComponents; j++)
          {
-         comp = new Component(mesh.mComponent.get(j));
+         comp = new TexComponent(mesh.mTexComponent.get(j));
          comp.mEndIndex += (extraVerticesBefore+mNumVertices+extraVerticesAfter);
-         mComponent.add(comp);
+         mTexComponent.add(comp);
+         }
 
-         if( origComponents<MAX_COMPONENTS )
+       for(int j=0; j<numEffComponents; j++)
+         {
+         int index = mesh.mEffComponent.get(j);
+         index += (extraVerticesBefore+mNumVertices+extraVerticesAfter);
+         mEffComponent.add(index);
+
+         if( origEffComponents<MAX_EFFECT_COMPONENTS )
            {
-           mAndAssociation[origComponents] = mesh.mAndAssociation[j];
-           mEquAssociation[origComponents] = mesh.mEquAssociation[j];
-           origComponents++;
+           mAndAssociation[origEffComponents] = mesh.mAndAssociation[j];
+           mEquAssociation[origEffComponents] = mesh.mEquAssociation[j];
+           origEffComponents++;
            }
          }
 
@@ -294,10 +396,10 @@ public abstract class MeshBase
 
      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    );
+       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 )
@@ -315,8 +417,8 @@ public abstract class MeshBase
 
        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    );
+         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);
@@ -343,11 +445,11 @@ public abstract class MeshBase
        android.util.Log.e("mesh", "join: origVertices: "+origVertices+" numVertices: "+mNumVertices);
        }
 
-     int endIndex, index=0, numComp = mComponent.size();
+     int endIndex, index=0, numEffComp = mEffComponent.size();
 
-     for(int component=0; component<numComp; component++)
+     for(int component=0; component<numEffComp; component++)
        {
-       endIndex = mComponent.get(component).mEndIndex;
+       endIndex = mEffComponent.get(component);
 
        for( ; index<=endIndex; index++) newAttribs2[VERT2_ATTRIBS*index+COM_ATTRIB] = component;
        }
@@ -382,18 +484,18 @@ public abstract class MeshBase
 
    void textureMap(Static4D[] maps)
      {
-     int num_comp = mComponent.size();
+     int num_comp = mTexComponent.size();
      int num_maps = maps.length;
      int min = Math.min(num_comp, num_maps);
      int vertex = 0;
      Static4D newMap, oldMap;
-     Component comp;
+     TexComponent comp;
      float newW, newH, ratW, ratH, movX, movY;
 
      for(int i=0; i<min; i++)
        {
        newMap = maps[i];
-       comp = mComponent.get(i);
+       comp = mTexComponent.get(i);
 
        if( newMap!=null )
          {
@@ -425,9 +527,9 @@ public abstract class MeshBase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   public static int getMaxComponents()
+   public static int getMaxEffComponents()
      {
-     return MAX_COMPONENTS;
+     return MAX_EFFECT_COMPONENTS;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -543,8 +645,8 @@ public abstract class MeshBase
  */
    public void send(int variant)
      {
-     GLES30.glUniform1iv( mEquAssociationH[variant], MAX_COMPONENTS, mEquAssociation, 0);
-     GLES30.glUniform1iv( mAndAssociationH[variant], MAX_COMPONENTS, mAndAssociation, 0);
+     GLES30.glUniform1iv( mEquAssociationH[variant], MAX_EFFECT_COMPONENTS, mEquAssociation, 0);
+     GLES30.glUniform1iv( mAndAssociationH[variant], MAX_EFFECT_COMPONENTS, mAndAssociation, 0);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -638,17 +740,33 @@ public abstract class MeshBase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Merge all components of this Mesh into a single one.
+ * Merge all texture components of this Mesh into a single one.
  */
-   public void mergeComponents()
+   public void mergeTexComponents()
      {
      if( mJobNode[0]==null )
        {
-       merge();
+       mergeTexComponentsNow();
        }
      else
        {
-       mJobNode[0] = DeferredJobs.merge(this);
+       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);
        }
      }
 
@@ -666,6 +784,29 @@ public abstract class MeshBase
      mTFO.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.
@@ -725,7 +866,7 @@ public abstract class MeshBase
  */
    public Static4D getTextureMap(int component)
      {
-     return (component>=0 && component<mComponent.size()) ? mComponent.get(component).mTextureMap : null;
+     return (component>=0 && component<mTexComponent.size()) ? mTexComponent.get(component).mTextureMap : null;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -745,7 +886,7 @@ public abstract class MeshBase
  */
   public void setEffectAssociation(int component, int andAssociation, int equAssociation)
     {
-    if( component>=0 && component<MAX_COMPONENTS )
+    if( component>=0 && component<MAX_EFFECT_COMPONENTS )
       {
       mAndAssociation[component] = andAssociation;
       mEquAssociation[component] = equAssociation;
diff --git a/src/main/java/org/distorted/library/mesh/MeshJoined.java b/src/main/java/org/distorted/library/mesh/MeshJoined.java
index f7bbd51..0ad5586 100644
--- a/src/main/java/org/distorted/library/mesh/MeshJoined.java
+++ b/src/main/java/org/distorted/library/mesh/MeshJoined.java
@@ -22,7 +22,7 @@ package org.distorted.library.mesh;
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class MeshJoined extends MeshBase
-  {
+{
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Join a list of (probably already changed by Vertex Effects) Meshes into one.
@@ -56,13 +56,4 @@ public class MeshJoined extends MeshBase
    {
    return new MeshJoined(this,deep);
    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return how many basic Meshes is this Mesh joined from.
- */
-  public int getNumComponents()
-     {
-     return numComponents();
-     }
-  }
+}
