function [K,H,k1,k2,a1,b1,g1,a2,b2,g2] = struik(dzx,dzy,dzxx,dzyy,dzxy)
% function [K,H,k1,k2,a1,b1,g1,a2,b2,g2] = struik(dzx,dzy,dzxx,dzyy,dzxy)
% Calculates Gaussian (K) and mean curvature (H), most positive and
% least positive principal curvatures (k1 and k2, respectively), and
% the associated direction cosines for k1 and k2 (a1,b1,g1, and 
% a2, b2, and g2, respectively) of a surface Z (Z = xi + yj + zk).
% This code relies upon a classical approach (e.g., Struik, 1961).
% See equations of Struik (1961), p. 74,75,80,81
% The eigenvector trends are found and then used to obtain the direction
% cosines of the eigenvectors in three dimensions.
% This code passed several tests on 6/21/07 with parabolic cylinders, a
% monkey saddle, and an "egg carton" surface (z = cos(x)*cos(y)).
%
% (1)  Z = xi + yj + z(x,y)k, where big Z is the surface to be analyzed
% (2)  dZ/dx = i + (dz/dx)k.  This is a tangent that trends east.
% (3)  dZ/dy = j + (dz/dy)k.  This tangent trends north. 
% (4)  d2Z/dx2 = (d2z/dx2)k,  d2Z/dy2 = (d2z/dy2)k, d2Z/dxdy = (d2z/dxdy)k. 
%
% Input parameters (Do not confuse big Z with little z) 
% dzx,dzy = first partial derivatives of z with respect to x and y
% dzxx,dzyy,dzxy =  second part. derivs. of z with respect to x, xy, and y  
%
% Output parameters 
% K = Gaussian curvature (k1*k2)
% H = Mean curvature (H) (k1 + k2)/2
% k1 = most positive principal curvature
% k2 = most negative principal curvature
% a1,b1,g1 = direction cosines for k1 with respect to the x,y,z axes
% a2,b2,g2 = direction cosines for k2 with respect to the x,y,z axes
%       % See online notes for GG303 for more on direction cosines
%
% Examples
% [X,Y] = meshgrid(-2:0.1:2,-1:0.1:1);
% % Test 1: Parabolic cylinder parallel to x-axis z = x.^2
% z = X.^2; dzx = 2.*X; dzy = 0.*Y;
% dzxx = 2*ones(size(X)); dzxy =  dzy; dzyy = dzy;
% [K,H,k1,k2,a1,b1,g1,a2,b2,g2] = struik(dzx,dzy,dzxx,dzyy,dzxy)
% % Test 2: Parabolic cylinder parallel to y-axis z = y.^2
% z = Y.^2;dzx = 0.*X; dzy = 0.*dzx; 
% dzxx = dzx; dzxy = dzx; dzyy = 2*ones(size(Y));
% [K,H,k1,k2,a1,b1,g1,a2,b2,g2] = struik(dzx,dzy,dzxx,dzyy,dzxy)
% % Test 3: Parabolic cylinder parallel with axis in xy plane
% t = pi/4*ones(size(X));     
% cost = cos(t);  sint = sin(t);
% z = (X.*cost + Y.*sint).^2;
% dzx = 2*(X.*cost + Y.*sint).*cost; dzy = 2*(X.*cost + Y.*sint).*sint;  
% dzxx = 2*cost.*cost; dzxy = 2*sint.*cost; dzyy = 2*sint.*sint;
% [K,H,k1,k2,a1,b1,g1,a2,b2,g2] = struik(dzx,dzy,dzxx,dzyy,dzxy)
% % Test 4: Monkey saddle z = x.^3 - 3xy.^2;
% z = X.^3 - 3*x.*y.^2;
% dzx = 3*X.^2 - 3*Y.^2; dzy = -6*X.*Y;
% dzxx = 6*X; dzxy = -6*Y; dzyy = -6*X;
% % Test 5: Egg carton z = cos(x).*cos(y);
% z = cos(X).*cos(Y);
% dzx = -sin(X).*cos(Y); dzy = -cos(X).*sin(Y);
% dzxx = -cos(X).*cos(Y); dzxy =  sin(X).*sin(Y); dzyy = -cos(X).*cos(Y);

[m,n] = size(dzx);

% Calculate First (E,F,G) and Second (e,f,g) Fundamental Coefficients
% Pollard & Fletcher (2005), among others, use L,M,N instead of e,f,g.
% See Monge Patch formulas of Struik, eq. 2-7, p. 58, and eq. 5-9 on p. 75.
E = 1 + dzx.^2;	        % E = dZx*dZx (dot product of dZx with itself);
F =     dzx.*dzy;	    % F = dZx*dZy (dot product of dZx with dZy);
G = 1 + dzy.^2;	        % G = dZy*dZy (dot product of dZy with itself);
EG_F2 = E.*G - F.^2;
root1 = sqrt(EG_F2);    % root1 is the length of (dZ/dx) x (dZ/dy)
e = dzxx./root1;        f = dzxy./root1;        g = dzyy./root1;

% Calculate Gaussian (K) and mean (H) curvatures.
% See eq. 7-2 and 7-3 of Struik (p. 83) for K and H.
% See p. 282 and 283 of section 14.4 of Gray for k1 and k2.
K = (e.*g - f.^2)./(EG_F2);                     % K = k1.*k2;
H = (E.*g - 2*f.*F + e.*G)./(2*(EG_F2));        % H = (k1+k2)/2;
% r = sqrt(H.^ 2 - K);       % k1 = H + r;       % k2 = H - r;

% Calculate principal directions (a_,b_,g_) & principal curvatures
% tan_theta = trend of principal direction = dy/dx
[theta1,theta2,k1,k2,a1,b1,a2,b2] = initialize(m,n);
% Solve for tan_theta (=dy/dx) in the following equation
% to get the direction cosines (a_,b_) of the principal trends
% a.*tan_theta.^2 + b.*tan_theta + c = 0.  See Struik, p. 80, eq. 6-5a 
% tan_theta1 = -b + sqrt(b.^2 - 4ac)/2a
% tan_theta2 = -b - sqrt(b.^2 - 4ac)/2a
a = F.*g-G.*f;      b = E.*g-G.*e;      c = E.*f-F.*e; 
sqrtb2_4ac = sqrt(b.^2 - 4.*a.*c);

% General case: eigenvectors do not trend parallel to x and y
% For data from surfaces in nature, this should be the only case used
i = find(a~=0);	 
% Find tan_theta (= dy/dx) for directions of k1,k2.  See Struik, eq. 6-5a
theta1(i) = atan( (-b(i) + sqrtb2_4ac(i))./(2.*a(i)) );
theta2(i) = atan( (-b(i) - sqrtb2_4ac(i))./(2.*a(i)) );
a1(i) = cos(theta1(i)); 	% alpha for k1;
b1(i) = sin(theta1(i));     % beta for k1;
a2(i) = cos(theta2(i));     % alpha for k2;
b2(i) = sin(theta2(i));     % beta for k2;
% Calculate k1 and k2 using eq. 6-3 of Struik
k1(i) = (e(i)+2.*f(i).*tan(theta1(i))+g(i).*tan(theta1(i)).^2)...
      ./(E(i)+2.*F(i).*tan(theta1(i))+G(i).*tan(theta1(i)).^2);
k2(i) = (e(i)+2.*f(i).*tan(theta2(i))+g(i).*tan(theta2(i)).^2)...
      ./(E(i)+2.*F(i).*tan(theta2(i))+G(i).*tan(theta2(i)).^2);

% Special cases
% Parametric lines are local lines of curvature, (e.g., surface is locally
% cylindrical with an axis trending parallel to x or y) and umbilic points
% (where  curvatures are the same in every direction [locally spherical])
% This is where F=f=0 and so a=c=0.  
j = find(a==0);    
% Calculate k1 and k2 using eq. 6-8 of Struik
k1(j) = e(j)./E(j);  	% k1 is along the x-direction (Struik, p. 81)
k2(j) = g(j)./G(j);     % k2 is along the y-direction (Struik, p. 81) 

% The results so far do NOT require k1 >= k2.
% Check results to make sure k2 does not exceed k1 and that the proper
% direction cosines and proper principal values are matched up.
index = find(k2>k1);
% Where needed, switch the k1,k2 values; a1,a2 values; and b1,b2 values   
c2 = k1(index);     k1(index) = k2(index);      k2(index) = c2; 
A2 = a1(index);     a1(index) = a2(index);      a2(index) = A2; 
B2 = b1(index);     b1(index) = b2(index);      b2(index) = B2; 
 
% Find the eigenvector z-components in 3D by multiplying the x,y components
% by dz/dx and dz/dy, respectively, and summing.  See (2) and (3) above.
g1 = a1.*dzx + b1.*dzy;         g2 = a2.*dzx + b2.*dzy;

% Now normalize the eigenvector components
length_e1 = sqrt(a1.^2 + b1.^2 + g1.^2);
length_e2 = sqrt(a2.^2 + b2.^2 + g2.^2);
a1 = a1./length_e1;     b1 = b1./length_e1;     g1 = g1./length_e1; 
a2 = a2./length_e2;     b2 = b2./length_e2;     g2 = g2./length_e2; 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [m1,m2,m3,m4,m5,m6,m7,m8] = initialize(m,n)
% function [m1,m2,m3,m4,m5,m6,m7,m8] = initialize(m,n)
% Initializes matrices to zeros(m,n) and ones(m,n)
m1 = zeros(m,n);    m2 = m1;	m3 = m1;    m4 = m1;   
m5 = ones(m,n);     m6 = m1;    m7 = m1;    m8 = m5;		
