// compute sphere dpdu and dpdv
double zradius = Math.sqrt(phit.x * phit.x + phit.y * phit.y);
double invzradius = 1.0 / zradius;
double cosphi = phit.x / invzradius;
double sinphi = phit.y / invzradius;
Vector dpdu = new Vector(-phiMax * phit.y, phiMax * phit.x, 0.0);
Vector dpdv = new Vector(phit.z * cosphi, phit.z * sinphi, -radius * Math.sin(theta)).mulLocal(thetaMax - thetaMin);
// compute sphere dndu and dndv
Vector d2Pduu = new Vector(phit.x, phit.y, 0).mulLocal(-phiMax * phiMax);
Vector d2Pduv = new Vector(-sinphi, cosphi, 0).mulLocal( (thetaMax - thetaMin) * phit.z * phiMax );
Vector d2Pdvv = new Vector(phit.x, phit.y, phit.z).mulLocal( -(thetaMax - thetaMin) * (thetaMax - thetaMin) );
// compute coefficients for fundamental forms
double E = dpdu.dot(dpdu);
double F = dpdu.dot(dpdv);
double G = dpdv.dot(dpdv);
Vector N = dpdu.cross(dpdv).normalizeLocal();
double e = N.dot(d2Pduu);
double f = N.dot(d2Pduv);
double g = N.dot(d2Pdvv);
// compute dndu and dndv from fundamental form coefficients
double invEGF2 = 1.0 / (E*G - F*F);
Normal dndu = new Normal(dpdu.mul( (f*F - e*G) * invEGF2 ).addLocal( dpdv.mul((e*F - f*E) * invEGF2) ));
Normal dndv = new Normal(dpdu.mul( (g*F - f*G) * invEGF2 ).addLocal( dpdv.mul((f*F - g*E) * invEGF2) ));