/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2003 by Leon Pennington
    email                : leon@leonscape.co.uk
**************************************************************************

**************************************************************************
*                                                                        *
*  This program is free software; you can redistribute it and/or modify  *
*  it under the terms of the GNU General Public License as published by  *
*  the Free Software Foundation; either version 2 of the License, or     *
*  (at your option) any later version.                                   *
*                                                                        *
**************************************************************************/

#include "pmheightfieldroam.h"

PMHeightFieldROAM::PMHeightFieldROAM( const QString filename,
                                      double waterLevel,
                                      unsigned short detail )
{
   m_pPoints = 0;
   m_pTree = 0;
   m_pNextNode = 0;
   m_waterLevel = (unsigned short)(waterLevel * 65535);

   if ( !mapData( filename ) ) return;

   m_distance = 65537 - ( detail * 256 );
   calcLevel();
   int z = m_size - 1;
   m_usedPoints = m_numLines = 0;

   m_pTree = new triNodeStructure[ ( ( m_size - 1 ) *
                                   ( 4 * ( m_size - 1 ) ) ) - 2 ];

   if ( !m_pTree )
   {
      m_fail = true;
      return;
   }

   m_pNextNode = m_pTree + 1;
   m_pNextNode->base = m_pTree;
   m_pTree->base = m_pNextNode++;

   varNode(m_pTree, z, 0, 0, 0, 0, z, 0);
   varNode(m_pTree + 1, 0, z, z, z, z, 0, 0);

   sptNode(m_pTree, 0);
   sptNode(m_pTree + 1, 0);

   pntNode(m_pTree, z, 0, 0, 0, 0, z);
   pntNode(m_pTree + 1, 0, z, z, z, z, 0);
}


PMHeightFieldROAM::~PMHeightFieldROAM( )
{
   if ( m_pPoints ) delete [ ] m_pPoints;
   if ( m_pTree ) delete [ ] m_pTree;
}


void PMHeightFieldROAM::setLOD( unsigned short detail )
{
   if ( m_fail ) return;
   m_distance = 65537 - ( detail * 256 );

   triNodeStructure* current = m_pTree;
   while ( current != m_pNextNode )
   {
      current->split = false;
      current++;
   }

   int z = m_size - 1;
   m_usedPoints = m_numLines = 0;
   clearPoints();

   sptNode( m_pTree, 0 );
   sptNode( m_pTree + 1, 0 );

   pntNode( m_pTree, z, 0, 0, 0, 0, z );
   pntNode( m_pTree + 1, 0, z, z, z, z, 0 );
}

bool PMHeightFieldROAM::lineExist( unsigned short x,
                                   unsigned short y,
                                   int line ) const
{
   if ( m_pPoints[ x + ( y * m_size ) ].lines[ line ] ) return true;
   else return false;
}

bool PMHeightFieldROAM::mapData(const QString filename)
{
   QImage scaledMap;
   QImage mapFile( filename );

   if ( mapFile.isNull( ) )
   {
      m_fail = true;
      return false;
   }

   if ( mapFile.width( ) > 192 || mapFile.height( ) > 192 )
   {
      scaledMap = mapFile.scale( 257, 257 );
   }
   else if ( mapFile.width( ) > 96 || mapFile.height( ) > 96 )
   {
      scaledMap = mapFile.scale( 129, 129 );
   }
   else if ( mapFile.width( ) > 48 || mapFile.height( ) > 48)
   {
      scaledMap = mapFile.scale( 65, 65 );
   }
   else
   {
      scaledMap = mapFile.scale( 33, 33 );
   }

   if ( scaledMap.isNull( ) )
   {
      m_fail = true;
      return false;
   }

   bool colourIndex;
   m_size = scaledMap.width( );
   m_numPoints = m_size * m_size;
   pointStructure* pts;
   m_pPoints = new pointStructure[ m_numPoints ];

   if ( !m_pPoints )
   {
      m_fail = true;
      return false;
   }

   if ( mapFile.depth( ) > 8 )
      colourIndex = false;
   else
   {
      scaledMap = scaledMap.convertDepthWithPalette( 8, mapFile.colorTable( ), 256 );
      colourIndex = true;
   }

   for ( int y = 0, y2 = ( m_size - 1 ) ; y < m_size ; ++y, --y2 )
   {
      for ( int x = 0 ; x < m_size ; ++x )
      {
      
         pts = &m_pPoints[ x + ( y2 * m_size ) ];
         if ( colourIndex )
         {
            pts->hgt = scaledMap.pixelIndex( x, y ) * 256;
         }
         else
         {
            pts->hgt = ( 256 * qRed( scaledMap.pixel( x, y ) ) ) +
                                  qGreen( scaledMap.pixel( x, y ) );
         }

         if ( pts->hgt < m_waterLevel )
            pts->hgt = m_waterLevel;

         pts->used = false;
         for ( int z = 0 ; z < 8 ; ++z )
         {
            pts->lines[ z ] = 0;
         }
      }
   }
   m_fail = false;
   return true;
}

void PMHeightFieldROAM::calcLevel( )
{
   int i = 0;
   int j = m_size;

   while( j != 1)
   {
      j /= 2;
      i++;
   }
   m_maxLevel = i * 2;
}

void PMHeightFieldROAM::varNode ( triNodeStructure* current,
                                  int x1, int y1,
                                  int x2, int y2,
                                  int x3, int y3,
                                  int level )
{
   int xm = (x1 + x3) >> 1;
   int ym = (y1 + y3) >> 1;

   if ( level >= m_maxLevel )
   {
      unsigned short z1 = height( x1, y1 );
      unsigned short z3 = height( x3, y3 );

      float zm = ( ( z3 - z1 ) / 2 ) + z1;

      current->vari = (int)( std::abs( zm - height( xm, ym ) ) );
      return;
   }

   current->lchd = m_pNextNode++;
   current->rchd = m_pNextNode++;

   varNode(current->lchd, x3, y3, xm, ym, x2, y2, level + 1);
   varNode(current->rchd, x2, y2, xm, ym, x1, y1, level + 1);

   current->vari = current->lchd->vari + current->rchd->vari;
}


void PMHeightFieldROAM::sptNode ( triNodeStructure* current, int level )
{
   if ( !current->split )
   {
      if ( level >= m_maxLevel ) return;

      if (current->vari > m_distance) split(current);
      else return;
   }

   sptNode(current->lchd, level + 1);
   sptNode(current->rchd, level + 1);
}


void PMHeightFieldROAM::split( triNodeStructure* current )
{
   current->split = true;

   if ( current->base )
   {
      if ( current->base->base != current ) split( current->base );
   }

   triNodeStructure* child;

   //left child
   child = current->lchd;
   child->base = current->lnbr;
   if ( current->lnbr )
   {
      if ( current->lnbr->rnbr == current ) current->lnbr->rnbr = child;
      else current->lnbr->base = child;
   }
   child->lnbr = current->rchd;

   //rightchild
   child = current->rchd;
   child->base = current->rnbr;
   if ( current->rnbr )
   {
      if ( current->rnbr->lnbr == current ) current->rnbr->lnbr = child;
      else current->rnbr->base = child;
   }
   child->rnbr = current->lchd;

   if ( current->base )
   {
      if ( !current->base->split ) split( current->base );
      current->lchd->rnbr = current->base->rchd;
      current->rchd->lnbr = current->base->lchd;
   }
}

void PMHeightFieldROAM::pntNode( triNodeStructure* current,
                                 int x1, int y1,
                                 int x2, int y2,
                                 int x3, int y3 )
{
   if (current->split)
   {
      int xm = (x1 + x3) >> 1;
      int ym = (y1 + y3) >> 1;
      pntNode( current->lchd, x3, y3, xm, ym, x2, y2 );
      pntNode( current->rchd, x2, y2, xm, ym, x1, y1 );
   }
   else
   {
      pointStructure* pts[3];
      pts[0] = &m_pPoints[ x1 + ( y1 * m_size ) ];
      pts[1] = &m_pPoints[ x2 + ( y2 * m_size ) ];
      pts[2] = &m_pPoints[ x3 + ( y3 * m_size ) ];

      if ( m_waterLevel != 0 )
      {
         if ( pts[0]->hgt <= m_waterLevel )
         {
            if ( pts[0]->hgt == pts[1]->hgt && pts[0]->hgt == pts[2]->hgt )
            return;
         }
      }


      for ( int i = 0 ; i < 3 ; ++i )
      {
         if ( !pts[ i ]->used )
         {
            pts[ i ]->pos = m_usedPoints++;
            pts[ i ]->used = true;
         }
      }

      addLine( pts[ 0 ], pts[ 1 ] );
      addLine( pts[ 1 ], pts[ 2 ] );
      addLine( pts[ 2 ], pts[ 0 ] );
   }
}

void PMHeightFieldROAM::addLine( pointStructure* pts1, pointStructure* pts2 )
{
   for ( int i = 0 ; i < 8 ; ++i )
   {
      if ( pts1->lines[ i ] )
      {
         if ( pts1->lines[ i ] == pts2 ) return;
      }
      else
      {
         for ( int j = 0 ; pts2->lines[ j ] ; ++j )
         {
            if ( pts2->lines[ j ] == pts1 ) return;
         }
         pts1->lines[ i ] = pts2;
         m_numLines++;
         return;
      }
   }
}

void PMHeightFieldROAM::clearPoints( )
{
   for ( unsigned i = 0 ; i < m_numPoints ; ++i )
   {
      m_pPoints[ i ].used = false;
      for ( int j = 0 ; j < 8 ; ++j )
      {
         m_pPoints[ i ].lines[ j ] = 0;
      }
   }
}
