Listing 2: The CSurface class implementation

//...................................................................
//
// surface.cpp

// INCLUDES

#include <stdio.h>                   // For IO
#include <math.h>                    // For Math 
#include "surface.h"                 // For Surface class

// DEFINES
    
#define IMAX_INTENSITY    255        // Maximum display intensity 
#define RAD        0.01745329        // Degrees to radian conversion
#define ILLUM_LAT        45.0        // Illumination elevation angle
#define ILLUM_LON        45.0        // Illumination azimuth angle

//.............................. CSurface ...........................
// Constructor
//
CSurface::CSurface(void)
{
   
   m_GridSize   = m_Smax = 0;            // zero class members      
   m_Nx  = m_Ny = m_Nz = m_Nb = 0;                               
   m_Image      = 0;                    
   m_Hz         = 0;                              
   m_Elevations = 0; 
}

//............................. ~CSurface ...........................
// Destructor
//
CSurface::~CSurface(void)
{
   m_GridSize   = m_Smax = 0;                            
   m_Nx  = m_Ny = m_Nz = m_Nb = 0;             // zero class members

   if(m_Hz         != 0) delete [] m_Hz;       // free alloc'd memory
   if(m_Image      != 0) delete [] m_Image;
   if(m_Elevations != 0) delete [] m_Elevations;
   
   m_Image      = 0;                    
   m_Hz         = 0;                              
   m_Elevations = 0; 
}

//.......................... surfaceRead ............................
// Read elevation data.
//
short CSurface::surfaceRead(char *fileName)
{
   // Application dependent. Include code to read surface data. This
   // function should, at a minimum, allocate memory for m_Elevations
   // and fill this memory with data defining the surface.  The 
   // surface dimensions m_Nx and m_Ny should also be set here along
   //  with m_GridSize the uniform spacing between elevation data, 
   // and the bitmap edge buffer m_Nb. If no edge buffer is required 
   // m_Nb should be set to one.
   
   return(1);
}

//.......................... surfaceDraw ............................
// Display rendered surface on display device.
//
short CSurface::surfaceDraw(_HANDLE h, _POINTER iPtr, _POINTER jPtr)
{
   // Device dependent. Include code to take lambert shaded bitmap 
   // and display it on the desired device. For Windows define
   // _HANDLE to be CDC*; for X-Windows define _HANDLE as Widget and
   // _POINTER as XtPointer.  
   
   return(1); 
}

//.................................. surfaceFindSmax ................
// Compute intensity scale factor.  Smax is found by computing  
// the slope  of the  "best fit" line to the cumulative elevation
// histogram.  The equation of an arbitary line is y = 0.5*(1 + x*s)  
// where s/2 is the slope.  The equation of the fit error is:
//
//       E[s] = sum{i: (0.5*(1+i*s) - m_Hz[i])^2 } 
//      -m_Nz <= i <= m_Nz 
//
// Taking the derivative with respect to s gives:
//
// dE[s]/ds =  sum {i: i*(0.5*(1+i*s) - m_Hz[i]) } 
//     -m_Nz <= i <= m_Nz 
//
// Setting to zero and solving for s/2 = Smax gives:
//
//Smax = sumH/sumI
//
// where
//
// sumH = sum{i: i*(m_Hz[i] - m_Hz[-i]) }
//    1 <= i <= m_Nz
//
// sumI = sum{i: 2*i*i }
//    1 <= i <= m_Nz
//
short CSurface::surfaceFindSmax(void)
{
      
   double       sumI,sumH;                // Least squares sums       
   double       hmax,hp,hn;               // Histogram values         
   long         i;                        // Counters                
   
   if(surfaceHistogram() == 0)            // compute histogram
     return(0);                           // return 0 if unsucessful
         
   hmax = m_Hz[2*m_Nz];                   // max elevation difference
   sumI = 0.0;                            // initialize sums
   sumH = 0.0;
         
   for(i = 1; i <= m_Nz; i++)             // Go through histogram ...
    {                                     // Normalize histogram
      hp = ((double) m_Hz[m_Nz+i])/hmax;  // values at -/+ i 
      hn = ((double) m_Hz[m_Nz-i])/hmax;  // Compute sums need for
      sumH += i*(hp - hn);                // Smax calculation
      sumI += 2.0*i*i;        
    } 

    m_Smax = sumH/sumI;                   // compute Smax from slope
    
    return(1);                           // return sucess.
 
 
}
      
//.................................. surfaceHistogram ...............
// Compute elevation difference histogram. A histogram of elevation
// differences in both directions is accumulated for the surface.  
// The elevation differences Dx(x,y) and Dy(z,y) are determined form
// adjacent points:
//
// Dx(x,y) = m_Elevations[x+1][y] - m_Elevations[x-1][y]
//
// Dy(x,y) = m_Elevations[x][y+1] - m_Elevations[x][y+1] 
//
// Both Dx and Dy are limited to lie between 0 and 2*m_Nz. If 
// possible, it is more efficient to compute m_Hz when reading in the 
// surface data.
//
short CSurface::surfaceHistogram(void)
    
 {
      
   long          i,j;                        // Counters                    
   long          Dx,Dy;                      // Elevation differences
   data _HUGE    *Zleft;                     // Elevation pointers     
   data _HUGE    *Zright;              
   data _HUGE    *Ztop;                
   data _HUGE    *Zbottom;              
   m_Nz = (long) (m_GridSize + 0.5);          // Histogram number
   
   m_Hz = new data[2*m_Nz];                   // allocate memory
   if(m_Hz == 0) return(0);                   // no memory for
                                              // computing Smax 
   for(i = 0; i <= 2*m_Nz; i++)               // zero histogram
    m_Hz[i] = 0; 

   Zleft   = (m_Elevations + m_Nx + 1);       // left elevation
   Zright  = (m_Elevations + m_Nx + 3);       // right elevation
   Zbottom = (m_Elevations + 1);              // bottom elevation
   Ztop    = (m_Elevations + 2*(m_Nx + 1)+1); // top elevation

   for (j = 1; j < m_Ny; j++)                 // compute histogram
    {
      for (i = 1; i < m_Nx; i++)
       {   
         Dx = m_Nz + (long) (*Zright-*Zleft); // x-difference
         Dy = m_Nz + (long) (*Ztop-*Zbottom); // y-difference  
         Dx = (Dx < 0 ? 0 : Dx);              // check greater
         Dy = (Dy < 0 ? 0 : Dy);              // than zero
         Dx = (Dx > 2*m_Nz ? 2*m_Nz : Dx);    // check less than
         Dy = (Dy > 2*m_Nz ? 2*m_Nz : Dy);    // upper bound
           
         m_Hz[Dx] += 1;                       // increment histogram
         m_Hz[Dy] += 1;                       // at these differences

         ++Zleft;                             // increment  pointers
         ++Zright;
         ++Zbottom;
         ++Ztop;
       }
      Zleft   += 2;                           // increment pointers
      Zright  += 2;
      Zbottom += 2;
      Ztop    += 2;
   }

   for(i = 1; i <= 2*m_Nz; i++)               // sum histogram to make
    m_Hz[i] = m_Hz[i] + m_Hz[i - 1];          // it cumulative. 
    
   return(1);                                 // return success
}

//................................... surfaceLSRender ...............
// Render surface into an image using Lambert Shading. Each surface
// elevation is map directly to a pixel.  The intensity of each pixel
// is computed using the Lambert intensity equation:
//
// I(x,y) = Ix[z(x+1,y) - z(x-1,y)] + Iy[z(x,y+1) - z(x, y-1)] + Io
//
// where z = m_Elevations.  In order to avoid non-integer arithmetic
// when the surface elevations are integers and scaling factor, FAC, 
// is computed and Ix, Iy, Io are scaled accordingly.  This results 
// in a substantial increase in speed. Pixels at the bitmap edge are 
// not computed since elevations in one direction are not available; 
// rather these pixel are assigned the value computed for their 
// inside neighbor.  The result cannot be noticed.      
//
short CSurface::surfaceLSRender(void)
    
{
    
  byte _HUGE    *iPtr;                       // Image pointer     
  double        x,y,z;                       // Gradients   
  double        sr2;                         // square root of 2
  long          Ix, Iy, Io;                  // Intensity coeffs
  long          FAC;                         // Scale factor            
  long          Dx, Dy;                      // Terrain slopes          
  long          i, j;                        // Counters                
  short         I;                           // Pixel intensity
  data _HUGE    *Zbottom;                    // Elevation pointers
  data _HUGE    *Zleft;
  data _HUGE    *Zright;              
  data _HUGE    *Ztop;
  if(surfaceFindSmax() == 0) return(0);      // compute Smax

  // determine illumination vector
  sr2 = sqrt(2.0);
  x = m_Smax*sr2*cos(RAD*ILLUM_LON)*cos(RAD*ILLUM_LAT); // Ix
  y = m_Smax*sr2*sin(RAD*ILLUM_LON)*cos(RAD*ILLUM_LAT); // Iy

  z = ( x < y ? x : y);                    // z is min illumination
  FAC = 1;                                 // intensity scale factor
  while( z < 1.0)                          // choose scale factor so
   {                                       // z is greater than one.
     z   = 10.0*z;                         // This allows integer
     FAC = 10*FAC;                         // arithmetic for
   }                                       // determining intensities
                                          
  // mid intensity
  Io = (FAC*IMAX_INTENSITY)*sr2*sin(RAD*ILLUM_LAT);/2; 
  Ix = (long) ((FAC*IMAX_INTENSITY-Io)*x);       // scaled sun vector
  Iy = (long) ((FAC*IMAX_INTENSITY-Io)*y);       // components
           
//  Intensity generation 

  Zleft   = (m_Elevations + m_Nx + 1);     // left elevation
  Zright  = (m_Elevations + m_Nx + 3);     // right elevation
  Zbottom = (m_Elevations + 1);            // bottom elevation
  Ztop    = (m_Elevations + 2*(m_Nx+1)+1); // top elevation
  iPtr    = (m_Image + m_Nx + m_Nb + 1);   // pointer to image bitmap
              
  for (j = 1; j < m_Ny; j++)               // compute intensity...
   {
     for (i = 1; i < m_Nx; i++)
      {             
        Dx = *Zright - *Zleft;               // x-gradient
        Dy = *Ztop - *Zbottom;               // y-gradient

        I = (short) ((Io+Ix*Dx+Iy*Dy)/FAC);  // raw intensity
        I = (I > 0 ? I : 0);                 // lower limit
        I = (I < IMAX_INTENSITY ? I : IMAX_INTENSITY);
        *iPtr = IMAX_INTENSITY - I;          // translate
             
        ++Zleft;                             // increment elevation
        ++Zright;                            // pointers
        ++Zbottom;
        ++Ztop;
        ++iPtr;                              // and bitmap pointer
      }
     iPtr    += (m_Nb + 1);                  // inc bitmap pointer
     Zleft   += 2;                           // increment elevation
     Zright  += 2;                           // pointers
     Zbottom += 2;
     Ztop    += 2;
   }

// At this point the surface image is complete except for the pixel
// that run along its edge.  These pixels are filled using adjacent 
// values computed above.

   for (j = 0; j < m_Ny; j++)               // fill first and last
    {                                       // column
      *(m_Image + (m_Nx + m_Nb)*j) =  
          *(m_Image + (m_Nx + m_Nb)*j + 1);
      *(m_Image+(m_Nx+m_Nb)*j+m_Nx) =  
          *(m_Image+(m_Nx+m_Nb)*j+m_Nx-1);
    }
       
   for (i = 0; i <= m_Nx; i++)            // fill first and last row
    {
     *(m_Image + i)                =  *(m_Image+(m_Nx+m_Nb) +i);
     *(m_Image+(m_Nx+m_Nb)*m_Ny+i) = 
         *(m_Image+(m_Nx+m_Nb)*(m_Ny-1)+ i);
    }
          
   return(1);                               // return success
}
//End of File