public RectangleRn WrappingRectangle() { 
            Euler euler = new Euler();
            euler.phi      = phi;
            euler.theta    = theta;
            euler.psi      = psi;
            euler.psi_a    = Euler.AXIS_Z ;
            euler.theta_a  = Euler.AXIS_X;
            euler.phi_a    = Euler.AXIS_Z;
            
            if (FP.zero(length)) {
                Point3D beg3d = new Point3D();
                Point3D end3d = new Point3D();
                euler.transform(beg3d, new Point3D(0.0, 0.0));
                euler.transform(end3d, new Point3D(length, 0.0));
                RectangleRn r = beg3d.toRectangle();
                end3d.addToRectangle(r);
                return r;
            } else { 
                double l, ls, lc;
                Point3D[] v = new Point3D[4];
                Point3D tv = new Point3D();
                l  = length / 2.0; 
                ls = Math.Sin(l);
                lc = Math.Cos(l);
                euler.phi += l;
                
                v[0] = new Point3D(lc,  lc<0 ? -1.0 : -ls, 0.0);
                v[1] = new Point3D(1.0, lc<0 ? -1.0 : -ls, 0.0);
                v[2] = new Point3D(lc,  lc<0 ? +1.0 : +ls, 0.0);
                v[3] = new Point3D(1.0, lc<0 ? +1.0 : +ls, 0.0) ;
 
                Point3D min = new Point3D(1.0, 1.0, 1.0);
                Point3D max = new Point3D(-1.0, -1.0, -1.0);
 
                for (int i=0; i<4; i++) {
                    euler.transform(tv, v[i]);
                    if (tv.x < -1.0) {
                        min.x = -1.0;
                    } else if (tv.x > 1.0) {
                        max.x = 1.0;
                    } else { 
                        if (tv.x < min.x) { 
                            min.x = tv.x;
                        }
                        if (tv.x > max.x) { 
                            max.x = tv.x;
                        }
                    }
                    
                    if (tv.y < -1.0) {
                        min.y = -1.0;
                    } else if ( tv.y > 1.0 ) {
                        max.y = 1.0;
                    } else {
                        if (tv.y < min.y) { 
                            min.y = tv.y;
                        }
                        if (tv.y > max.y) { 
                            max.y = tv.y;
                        }
                    }
                    if (tv.z < -1.0) {
                        min.z = -1.0;
                    } else if (tv.z > 1.0) {
                        max.z = 1.0;
                    } else { 
                        if (tv.z < min.z) { 
                            min.z = tv.z;
                        }
                        if (tv.z > max.z) { 
                            max.z = tv.z;
                        }
                    } 
                }  
                return new RectangleRn(new double[]{min.x, min.y, min.z, max.x, max.y, max.z});
            }
        }
         public Line(Point beg, Point end) { 
             double l = beg.Distance(end);
             if (FP.eq(l, Math.PI)) {
                 Debug.Assert(FP.eq(beg.ra, end.ra));
                 phi = -Math.PI/2;
                 theta = Math.PI/2;
                 psi = beg.ra < 0.0 ? Math.PI*2 + beg.ra : beg.ra;
                 length = Math.PI;
                 return;
             }
             if (beg.Equals(end)) { 
                 phi    = Math.PI/2;
                 theta  = beg.dec;
                 psi    = beg.ra - Math.PI/2;
                 length = 0.0;
             } else { 
                 Point3D beg3d = new Point3D(beg);
                 Point3D end3d = new Point3D(end);
                 Point3D tp = new Point3D();
                 Point spt = beg3d.cross(end3d).toSpherePoint();
                 Euler euler = new Euler();
                 euler.phi     = - spt.ra - Math.PI/2;
                 euler.theta   =   spt.dec - Math.PI/2;
                 euler.psi     =   0.0 ;
                 euler.psi_a   = Euler.AXIS_Z;
                 euler.theta_a = Euler.AXIS_X;
                 euler.phi_a   = Euler.AXIS_Z;
                 euler.transform(tp, beg3d);
                 spt = tp.toSpherePoint();
 
                 // invert
                 phi = spt.ra;
                 theta = -euler.theta;
                 psi = -euler.phi;
                 length = l;
             }
         }
 public bool Contains(Point p) { 
     Euler euler = new Euler();
     Point3D spt = new Point3D();
     euler.phi     = -psi;
     euler.theta   = -theta;
     euler.psi     = -phi;
     euler.psi_a   = Euler.AXIS_Z;
     euler.theta_a = Euler.AXIS_X;
     euler.phi_a   = Euler.AXIS_Z;
     euler.transform(spt, new Point3D(p));
     Point sp = spt.toSpherePoint();
     return FP.zero(sp.dec) && FP.ge(sp.ra, 0.0) && FP.le(sp.ra, length);
 }                      
         public RectangleRn WrappingRectangle() { 
             Point3D[] v = new Point3D[8];
             Point3D tv = new Point3D();
             Euler euler = new Euler();
 
             double r0 = Math.Sin(rad0);
             double r1 = Math.Sin(rad1);
             double d = Math.Cos(rad1);
 
             v[0] = new Point3D(d, -r0, -r1);
             v[1] = new Point3D(d, +r0, -r1);
             v[2] = new Point3D(d, -r0, r1);
             v[3] = new Point3D(d, +r0, r1);
             v[4] = new Point3D(1.0, -r0, -r1);
             v[5] = new Point3D(1.0, +r0, -r1);
             v[6] = new Point3D(1.0, -r0, r1);
             v[7] = new Point3D(1.0, +r0, r1);
             
             euler.psi_a    = Euler.AXIS_Z ;
             euler.theta_a  = Euler.AXIS_Y;
             euler.phi_a    = Euler.AXIS_X;
             euler.phi      = phi;
             euler.theta    = theta;
             euler.psi      = psi;
 
             Point3D min = new Point3D(1.0, 1.0, 1.0);
             Point3D max = new Point3D(-1.0, -1.0, -1.0);
 
             for (int i=0; i<8; i++) {
                 euler.transform(tv, v[i]);
                 if (tv.x < -1.0) {
                     min.x = -1.0;
                 } else if (tv.x > 1.0) {
                     max.x = 1.0;
                 } else { 
                     if (tv.x < min.x) { 
                         min.x = tv.x;
                     }
                     if (tv.x > max.x) { 
                         max.x = tv.x;
                     }
                 }
       
                 if (tv.y < -1.0) {
                     min.y = -1.0;
                 } else if ( tv.y > 1.0 ) {
                     max.y = 1.0;
                 } else {
                     if (tv.y < min.y) { 
                         min.y = tv.y;
                     }
                     if (tv.y > max.y) { 
                         max.y = tv.y;
                     }
                 }
                 if (tv.z < -1.0) {
                     min.z = -1.0;
                 }  else if (tv.z > 1.0) {
                     max.z = 1.0;
                 } else { 
                     if (tv.z < min.z) { 
                         min.z = tv.z;
                     }
                     if (tv.z > max.z) { 
                         max.z = tv.z;
                     }
                 } 
             }
             return new RectangleRn(new double[]{min.x, min.y, min.z, max.x, max.y, max.z});
         }
 internal Point3D cross(Point3D p) { 
     return new Point3D(y * p.z - z * p.y,
                        z * p.x - x * p.z,
                        x * p.y - y * p.x);
 }
     internal void transform(Point3D result, Point3D input)
     {
         int t = 0;
         double a, sa, ca;
         double x1, y1, z1;
         double x2, y2, z2;
         
         x1 = input.x;
         y1 = input.y;
         z1 = input.z;
 
         for (int i=0; i<3; i++) {
             switch (i) {
             case 0: 
                 a = phi; 
                 t = phi_a; 
                 break;
             case 1: 
                 a = theta; 
                 t = theta_a; 
                 break;
             default: 
                 a = psi; 
                 t = psi_a; 
                 break;
             }
             
             if (FP.zero(a)) {
                 continue;
             }
             
             sa = Math.Sin(a);
             ca = Math.Cos(a);
 
             switch (t) {                    
             case AXIS_X :
                 x2 = x1;
                 y2 = ca*y1 - sa*z1;
                 z2 = sa*y1 + ca*z1;
                 break;
             case AXIS_Y :
                 x2 = ca*x1 + sa*z1;
                 y2 = y1;
                 z2 = ca*z1 - sa*x1;
                 break;
             default:
                 x2 = ca*x1 - sa*y1;
                 y2 = sa*x1 + ca*y1;
                 z2 = z1;
                 break;               
             }
             x1 = x2;
             y1 = y2;
             z1 = z2;
         }
         result.x = x1;
         result.y = y1;
         result.z = z1;
     }