commit 353f75808be0cbf95fa16cb71fbdeb3104817e6b
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Jan 11 23:15:40 2019 +0000

    Make Distort truly 3D.

diff --git a/src/main/java/org/distorted/library/effect/VertexEffectDistort.java b/src/main/java/org/distorted/library/effect/VertexEffectDistort.java
index b240ae4..fd7cb9f 100644
--- a/src/main/java/org/distorted/library/effect/VertexEffectDistort.java
+++ b/src/main/java/org/distorted/library/effect/VertexEffectDistort.java
@@ -48,18 +48,22 @@ public class VertexEffectDistort extends VertexEffect
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// Point (Px,Py) gets moved by vector (Wx,Wy,Wz) where Wx/Wy = Vx/Vy i.e. Wx=aVx and Wy=aVy where
+// Def: P-current vertex being distorted, S - center of the effect, X- point where half-line SP meets
+// the region circle (if P is inside the circle); (Vx,Vy,Vz) - force vector.
+//
+// horizontal part
+// Point (Px,Py) gets moved by vector (Wx,Wy) where Wx/Wy = Vx/Vy i.e. Wx=aVx and Wy=aVy where
 // a=Py/Sy (N --> when (Px,Py) is above (Sx,Sy)) or a=Px/Sx (W) or a=(w-Px)/(w-Sx) (E) or a=(h-Py)/(h-Sy) (S)
 // It remains to be computed which of the N,W,E or S case we have: answer: a = min[ Px/Sx , Py/Sy , (w-Px)/(w-Sx) , (h-Py)/(h-Sy) ]
 // Computations above are valid for screen (0,0)x(w,h) but here we have (-w/2,-h/2)x(w/2,h/2)
 //
-// the vertical part
-// Let |(v.x,v.y),(ux,uy)| = |PS|, ux-v.x=dx,uy-v.y=dy, f(x) (0<=x<=|SX|) be the shape of the side of the bubble.
-// H(v.x,v.y) = |PS|>|SX| ? 0 : f(|PX|)
-// N(v.x,v.y) = |PS|>|SX| ? (0,0,1) : ( -(dx/|PS|)sin(beta), -(dy/|PS|)sin(beta), cos(beta) ) where tan(beta) is f'(|PX|)
-// ( i.e. normalize( dx, dy, -|PS|/f'(|PX|))
+// vertical part
+// Let vector PS = (dx,dy), f(x) (0<=x<=|SX|) be the shape of the side of the bubble.
+// H(Px,Py) = |PS|>|SX| ? 0 : f(|PX|)
+// N(Px,Py) = |PS|>|SX| ? (0,0,1) : ( -(dx/|PS|)sin(beta), -(dy/|PS|)sin(beta), cos(beta) ) where tan(beta) is f'(|PX|)
+// ( i.e. normalize( dx, dy, -|PS|/f'(|PX|) )
 //
-// Now we also have to take into account the effect horizontal move by V=(u_dVx[i],u_dVy[i]) will have on the normal vector.
+// Now we also have to take into account the effect horizontal move by V=(Vx,Vy) will have on the normal vector.
 // Solution:
 // 1. Decompose the V into two subcomponents, one parallel to SX and another perpendicular.
 // 2. Convince yourself (draw!) that the perpendicular component has no effect on normals.
@@ -67,29 +71,29 @@ public class VertexEffectDistort extends VertexEffect
 //    can be negative depending on the direction)
 // 4. that in turn leaves the x and y parts of the normal unchanged and multiplies the z component by a!
 //
-// |Vpar| = (u_dVx[i]*dx - u_dVy[i]*dy) / sqrt(ps_sq) = (Vx*dx-Vy*dy)/ sqrt(ps_sq)  (-Vy because y is inverted)
+// |Vpar| = (Vx*dx-Vy*dy) / sqrt(ps_sq)  (-Vy because y is inverted)
 // a =  (|SX| - |Vpar|)/|SX| = 1 - |Vpar|/((sqrt(ps_sq)/(1-d)) = 1 - (1-d)*|Vpar|/sqrt(ps_sq) = 1-(1-d)*(Vx*dx-Vy*dy)/ps_sq
 //
 // Side of the bubble
 //
 // choose from one of the three bubble shapes: the cone, the thin bubble and the thick bubble
 // Case 1:
-// f(t) = t, i.e. f(x) = uz * x/|SX|   (a cone)
-// -|PS|/f'(|PX|) = -|PS|*|SX|/uz but since ps_sq=|PS|^2 and d=|PX|/|SX| then |PS|*|SX| = ps_sq/(1-d)
-// so finally -|PS|/f'(|PX|) = -ps_sq/(uz*(1-d))
+// f(t) = t, i.e. f(x) = Vz * x/|SX|   (a cone)
+// -|PS|/f'(|PX|) = -|PS|*|SX|/Vz but since ps_sq=|PS|^2 and d=|PX|/|SX| then |PS|*|SX| = ps_sq/(1-d)
+// so finally -|PS|/f'(|PX|) = -ps_sq/(Vz*(1-d))
 //
 // Case 2:
 // f(t) = 3t^2 - 2t^3 --> f(0)=0, f'(0)=0, f'(1)=0, f(1)=1 (the bell curve)
-// here we have t = x/|SX| which makes f'(|PX|) = 6*uz*|PS|*|PX|/|SX|^3.
-// so -|PS|/f'(|PX|) = (-|SX|^3)/(6uz|PX|) =  (-|SX|^2) / (6*uz*d) but
+// here we have t = x/|SX| which makes f'(|PX|) = 6*Vz*|PS|*|PX|/|SX|^3.
+// so -|PS|/f'(|PX|) = (-|SX|^3)/(6Vz|PX|) =  (-|SX|^2) / (6*Vz*d) but
 // d = |PX|/|SX| and ps_sq = |PS|^2 so |SX|^2 = ps_sq/(1-d)^2
-// so finally -|PS|/f'(|PX|) = -ps_sq/ (6uz*d*(1-d)^2)
+// so finally -|PS|/f'(|PX|) = -ps_sq/ (6Vz*d*(1-d)^2)
 //
 // Case 3:
 // f(t) = 3t^4-8t^3+6t^2 would be better as this satisfies f(0)=0, f'(0)=0, f'(1)=0, f(1)=1,
 // f(0.5)=0.7 and f'(t)= t(t-1)^2 >=0 for t>=0 so this produces a fuller, thicker bubble!
-// then -|PS|/f'(|PX|) = (-|PS|*|SX)) / (12uz*d*(d-1)^2) but |PS|*|SX| = ps_sq/(1-d) (see above!)
-// so finally -|PS|/f'(|PX|) = -ps_sq/ (12uz*d*(1-d)^3)
+// then -|PS|/f'(|PX|) = (-|PS|*|SX)) / (12Vz*d*(d-1)^2) but |PS|*|SX| = ps_sq/(1-d) (see above!)
+// so finally -|PS|/f'(|PX|) = -ps_sq/ (12Vz*d*(1-d)^3)
 //
 // Now, new requirement: we have to be able to add up normal vectors, i.e. distort already distorted surfaces.
 // If a surface is given by z = f(x,y), then the normal vector at (x0,y0) is given by (-df/dx (x0,y0), -df/dy (x0,y0), 1 ).
@@ -97,8 +101,23 @@ public class VertexEffectDistort extends VertexEffect
 // then the normal to g = f1+f2 is simply given by (f1x+f2x,f1y+f2y,1), i.e. if the third components are equal, then we
 // can simply add up the first and second components.
 //
-// Thus we actually want to compute N(v.x,v.y) = a*(-(dx/|PS|)*f'(|PX|), -(dy/|PS|)*f'(|PX|), 1) and keep adding
+// Thus we actually want to compute N(Px,Py) = a*(-(dx/|PS|)*f'(|PX|), -(dy/|PS|)*f'(|PX|), 1) and keep adding
 // the first two components. (a is the horizontal part)
+//
+// 2019-01-09 fixes for stuff discovered with the Earth testing app
+// Now, the above stuff works only if the normal vector is close to (0,0,1). To make it work for any situation,
+// rotate the normal, force and ps vectors along the axis (n.y,-n.x,0) and angle A between the normal and (0,0,1)
+// then modify the rotated normal (so (0,0,1)) and rotate the modified normal back.
+//
+// if we have a vector (vx,vy,vz) that w want to rotate with a rotation that turns the normal into (0,0,1), then
+// a) axis of rotation = (n.x,n.y,n.z) x (0,0,1) = (n.y,-n.x,0)
+// b) angle of rotation A: cosA = (n.x,n.y,n.z)*(0,0,1) = n.z     (and sinA = + sqrt(1-n.z^2))
+//
+// so normalized axisNor = (n.x/ sinA, -n.y/sinA, 0) and now from Rodrigues rotation formula
+// Vrot = v*cosA + (axisNor x v)*sinA + axisNor*(axis*v)*(1-cosA)
+//
+// which makes Vrot = (a+n.y*c , b-n.y*c , v*n) where
+// a = vx*nz-vz*nx , b = vy*nz-vz*ny , c = (vx*ny-vy*nx)/(1+nz)    (unless n=(0,0,-1))
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Have to call this before the shaders get compiled (i.e before Distorted.onCreate()) for the Effect to work.
@@ -107,24 +126,34 @@ public class VertexEffectDistort extends VertexEffect
     {
     addEffect(EffectName.DISTORT,
 
-        "vec3 center = vUniforms[effect+1].yzw; \n"
-      + "vec3 ps = center-v; \n"
-      + "vec3 force = vUniforms[effect].xyz; \n"
-      + "float d = degree(vUniforms[effect+2],center,ps); \n"
-      + "float denom = dot(ps+(1.0-d)*force,ps); \n"
-      + "float one_over_denom = 1.0/(denom-0.001*(sign(denom)-1.0)); \n"          // = denom==0 ? 1000:1/denom;
+        "vec3 center = vUniforms[effect+1].yzw;                        \n"
+      + "vec3 ps = center-v;                                           \n"
+      + "vec3 force = vUniforms[effect].xyz;                           \n"
+      + "vec4 region= vUniforms[effect+2];                             \n"
+      + "float d = degree(region,center,ps);                           \n"
+
+      + "if( d>0.0 )                                                   \n"
+      + "  {                                                           \n"
+      + "  v += d*force;                                               \n"
 
-       //v.z += force.z*d;                                                        // cone
-       //b = -(force.z*(1.0-d))*one_over_denom;                                   //
+      + "  float tp = 1.0+n.z;                                         \n"
+      + "  float tr = 1.0 / (tp - (1.0 - sign(tp)));                   \n"
 
-       //v.z += force.z*d*d*(3.0-2.0*d);                                          // thin bubble
-       //b = -(6.0*force.z*d*(1.0-d)*(1.0-d))*one_over_denom;                     //
+      + "  float ap = ps.x*n.z - ps.z*n.x;                             \n"   // likewise rotate the ps vector
+      + "  float bp = ps.y*n.z - ps.z*n.y;                             \n"   //
+      + "  float cp =(ps.x*n.y - ps.y*n.x)*tr;                         \n"   //
+      + "  vec3 psRot = vec3( ap+n.y*cp , bp-n.x*cp , dot(ps,n) );     \n"   //
 
-      + "v.z += force.z*d*d*(3.0*d*d -8.0*d +6.0); \n"                            // thick bubble
-      + "float b = -(12.0*force.z*d*(1.0-d)*(1.0-d)*(1.0-d))*one_over_denom; \n"  //
+      + "  psRot = normalize(psRot);                                   \n"
+      + "  vec3 N = vec3( -dot(force,n)*psRot.xy, region.w );          \n"   // modified rotated normal
+                                                                             // dot(force,n) is rotated force.z
+      + "  float an = N.x*n.z + N.z*n.x;                               \n"   // now create the normal vector
+      + "  float bn = N.y*n.z + N.z*n.y;                               \n"   // back from our modified normal
+      + "  float cn =(N.x*n.y - N.y*n.x)*tr;                           \n"   // rotated back
+      + "  n = vec3( an+n.y*cn , bn-n.x*cn , -N.x*n.x-N.y*n.y+N.z*n.z);\n"   // notice 4 signs change!
 
-      + "v.xy += d*force.xy; \n"
-      + "n.xy += n.z*b*ps.xy;"
+      + "  n = normalize(n);                                           \n"
+      + "  }                                                           \n"
       );
     }
 
diff --git a/src/main/java/org/distorted/library/effect/VertexEffectSwirl.java b/src/main/java/org/distorted/library/effect/VertexEffectSwirl.java
index 70a5d73..950c078 100644
--- a/src/main/java/org/distorted/library/effect/VertexEffectSwirl.java
+++ b/src/main/java/org/distorted/library/effect/VertexEffectSwirl.java
@@ -70,21 +70,20 @@ public class VertexEffectSwirl extends VertexEffect
     {
     addEffect(EffectName.SWIRL,
 
-        "vec3 center  = vUniforms[effect+1].yzw; \n"
-      + "vec3 PS = center-v.xyz; \n"
-      + "vec4 SO = vUniforms[effect+2]; \n"
-      + "float d1_circle = degree_region(SO,PS); \n"
-      + "float d1_bitmap = degree_bitmap(center,PS); \n"
-
-      + "float alpha = vUniforms[effect].x; \n"
-      + "float sinA = sin(alpha); \n"
-      + "float cosA = cos(alpha); \n"
+        "vec3 center = vUniforms[effect+1].yzw;                             \n"
+      + "vec3 PS = center-v.xyz;                                            \n"
+      + "vec4 SO = vUniforms[effect+2];                                     \n"
+      + "float d1_circle = degree_region(SO,PS);                            \n"
+      + "float d1_object = degree_object(center,PS);                        \n"
+      + "float alpha = vUniforms[effect].x;                                 \n"
+      + "float sinA = sin(alpha);                                           \n"
+      + "float cosA = cos(alpha);                                           \n"
 
       + "vec3 PS2 = vec3( PS.x*cosA+PS.y*sinA,-PS.x*sinA+PS.y*cosA, PS.z ); \n" // vector PS rotated by A radians clockwise around center.
-      + "vec4 SG = (1.0-d1_circle)*SO; \n"                                      // coordinates of the dilated circle P is going to get rotated around
-      + "float d2 = max(0.0,degree(SG,center,PS2)); \n"                         // make it a max(0,deg) because otherwise when center=left edge of the
-                                                                                // bitmap some points end up with d2<0 and they disappear off view.
-      + "v.xy += min(d1_circle,d1_bitmap)*(PS.xy - PS2.xy/(1.0-d2)); \n"        // if d2=1 (i.e P=center) we should have P unchanged. How to do it?
+      + "vec4 SG = (1.0-d1_circle)*SO;                                      \n" // coordinates of the dilated circle P is going to get rotated around
+      + "float d2 = max(0.0,degree(SG,center,PS2));                         \n" // make it a max(0,deg) because otherwise when center=left edge of the
+                                                                                // object some points end up with d2<0 and they disappear off view.
+      + "v.xy += min(d1_circle,d1_object)*(PS.xy - PS2.xy/(1.0-d2));        \n" // if d2=1 (i.e P=center) we should have P unchanged. How to do it?
       );
     }
 
diff --git a/src/main/res/raw/main_vertex_shader.glsl b/src/main/res/raw/main_vertex_shader.glsl
index 7720a97..4211d36 100644
--- a/src/main/res/raw/main_vertex_shader.glsl
+++ b/src/main/res/raw/main_vertex_shader.glsl
@@ -59,33 +59,41 @@ uniform vec4 vUniforms[3*NUM_VERTEX];// i-th effect is 3 consecutive vec4's: [3*
 // We still have to avoid division by 0 when p.x = +- u_objD.x or p.y = +- u_objD.y (i.e on the edge of the Object)
 // We do that by first multiplying the above 'float d' with sign(denominator1*denominator2)^2.
 //
+// 2019-01-09: make this 3D. The trick: we want only the EDGES of the cuboid to stay constant.
+// the interiors of the Faces move! Thus, we want the MIDDLE of the PS/(sign(PS)*u_objD+S) !
 //////////////////////////////////////////////////////////////////////////////////////////////
-// return degree of the point as defined by the bitmap rectangle
+// return degree of the point as defined by the object cuboid (u_objD.x X u_objD.y X u_objD.z)
 
-float degree_bitmap(in vec3 S, in vec3 PS)
+float degree_object(in vec3 S, in vec3 PS)
   {
+  vec3 ONE = vec3(1.0,1.0,1.0);
   vec3 A = sign(PS)*u_objD + S;
 
-  vec3 signA = sign(A);                                    //
-  vec3 signA_SQ = signA*signA;                             // div = PS/A if A!=0, 0 otherwise.
-  vec3 div = signA_SQ*PS/(A-(vec3(1.0,1.0,1.0)-signA_SQ)); //
+  vec3 signA = sign(A);                      //
+  vec3 signA_SQ = signA*signA;               // div = PS/A if A!=0, 0 otherwise.
+  vec3 div = signA_SQ*PS/(A-(ONE-signA_SQ)); //
 
-  return 1.0-max(div.x,div.y);
+  float d1= div.x-div.y;
+  float d2= div.y-div.z;
+  float d3= div.x-div.z;
+
+  if( d1*d2>0.0 ) return 1.0-div.y;          //
+  if( d1*d3>0.0 ) return 1.0-div.z;          // return 1-middle(div.x,div.y,div.z)
+  return 1.0-div.x;                          //
   }
 
 //////////////////////////////////////////////////////////////////////////////////////////////
-// Return degree of the point as defined by the Region. Currently only supports circular regions.
+// Return degree of the point as defined by the Region. Currently only supports spherical regions.
 //
-// Let us first introduce some notation.
 // Let 'PS' be the vector from point P (the current vertex) to point S (the center of the effect).
-// Let region.xy be the vector from point S to point O (the center point of the region circle)
-// Let region.z be the radius of the region circle.
-// (This all should work regardless if S is inside or outside of the circle).
+// Let region.xyz be the vector from point S to point O (the center point of the region sphere)
+// Let region.w be the radius of the region sphere.
+// (This all should work regardless if S is inside or outside of the sphere).
 //
-// Then, the degree of a point with respect to a given (circular!) Region is defined by:
+// Then, the degree of a point with respect to a given (spherical!) Region is defined by:
 //
-// If P is outside the circle, return 0.
-// Otherwise, let X be the point where the halfline SP meets the region circle - then return |PX|/||SX|,
+// If P is outside the sphere, return 0.
+// Otherwise, let X be the point where the halfline SP meets the sphere - then return |PX|/||SX|,
 // aka the 'degree' of point P.
 //
 // We solve the triangle OPX.
@@ -117,7 +125,7 @@ float degree_region(in vec4 region, in vec3 PS)
   }
 
 //////////////////////////////////////////////////////////////////////////////////////////////
-// return min(degree_bitmap,degree_region). Just like degree_region, currently only supports circles.
+// return min(degree_object,degree_region). Just like degree_region, currently only supports spheres.
 
 float degree(in vec4 region, in vec3 S, in vec3 PS)
   {
@@ -130,7 +138,15 @@ float degree(in vec4 region, in vec3 S, in vec3 PS)
   vec3 signA = sign(A);
   vec3 signA_SQ = signA*signA;
   vec3 div = signA_SQ*PS/(A-(vec3(1.0,1.0,1.0)-signA_SQ));
-  float E = 1.0-max(div.x,div.y);
+
+  float d1= div.x-div.y;
+  float d2= div.y-div.z;
+  float d3= div.x-div.z;
+  float E;
+
+       if( d1*d2>0.0 ) E= 1.0-div.y;
+  else if( d1*d3>0.0 ) E= 1.0-div.z;
+  else                 E= 1.0-div.x;
 
   float ps_sq = dot(PS,PS);
   float one_over_ps_sq = 1.0/(ps_sq-(sign(ps_sq)-1.0));  // return 1.0 if ps_sq = 0.0
