/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2000-2001 by Andreas Zehender
    email                : zehender@kde.org
**************************************************************************

**************************************************************************
*                                                                        *
*  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 "pmlight.h"

#include "pmoutputdevice.h"
#include "pmxmlhelper.h"
#include "pmlightedit.h"
#include "pmmemento.h"
#include "pmviewstructure.h"
#include "pm3dcontrolpoint.h"
#include "pmmath.h"
#include "pmmatrix.h"

#include <kdebug.h>
#include "pmglobals.h"

#include <klocale.h>

const PMVector locationDefault = PMVector( 0, 0, 0 );
const PMColor colorDefault = PMColor( 1.0, 1.0, 1.0 );
const double radiusDefault = 70.0;
const double falloffDefault = 70.0;
const double tightnessDefault = 10;
const PMVector pointAtDefault = PMVector( 0, 0, 1 );
const PMVector areaAxis1Default = PMVector( 1, 0, 0 );
const PMVector areaAxis2Default = PMVector( 0, 1, 0 );
const int areaSize1Default = 3;
const int areaSize2Default = 3;
const int adaptiveDefault = 0;
const bool jitterDefault = false;
const int fadePowerDefault = 1;
const double fadeDistanceDefault = 10.0;

PMViewStructure* PMLight::s_pDefaultPointStructure = 0;
PMViewStructure* PMLight::s_pDefaultSpotStructure = 0;
PMViewStructure* PMLight::s_pDefaultCylindricalStructure = 0;
double PMLight::s_pointLightSize = 0.25;
int PMLight::s_nCylinderLines = 8;
int PMLight::s_nSpotLines = 8;
double PMLight::s_length = 0.5;

PMLight::PMLight( )
      : Base( )
{
   m_location = locationDefault;
   m_color = colorDefault;
   m_type = PointLight;
   m_radius = radiusDefault;
   m_falloff = falloffDefault;
   m_tightness = tightnessDefault;
   m_pointAt = pointAtDefault;
   m_bAreaLight = false;
   m_areaAxis1 = areaAxis1Default;
   m_areaAxis2 = areaAxis2Default;
   m_areaSize1 = areaSize1Default;
   m_areaSize2 = areaSize2Default;
   m_adaptive = adaptiveDefault;
   m_jitter = jitterDefault;
   m_bFading = false;
   m_fadeDistance = fadeDistanceDefault;
   m_fadePower = fadePowerDefault;
   m_bMediaInteraction = true;
   m_bMediaAttenuation = true;
}

PMLight::~PMLight( )
{
}

QString PMLight::description( ) const
{
   return i18n( "light" );
}

void PMLight::serialize( PMOutputDevice& dev ) const
{
   dev.objectBegin( QString( "light_source" ) );

   serializeName( dev );
   dev.writeLine( m_location.serialize( ) + ", " + m_color.serialize( ) );

   if( m_type == SpotLight )
      dev.writeLine( QString( "spotlight" ) );
   else if( m_type == CylinderLight )
      dev.writeLine( QString( "cylinder" ) );
   else if( m_type == ShadowlessLight )
      dev.writeLine( QString( "shadowless" ) );

   if( ( m_type == SpotLight ) || ( m_type == CylinderLight ) )
   {
      dev.writeLine( QString( "radius %1" ).arg( m_radius ) );
      dev.writeLine( QString( "falloff %1" ).arg( m_falloff ) );
      if( m_tightness != tightnessDefault )
         dev.writeLine( QString( "tightness %1" ).arg( m_tightness ) );
      dev.writeLine( QString( "point_at " ) + m_pointAt.serialize( ) );
   }

   if( m_bAreaLight )
   {
      dev.writeLine( QString( "area_light " ) + m_areaAxis1.serialize( )
                     + QString( ", " ) + m_areaAxis2.serialize( )
                     + QString( ", %1, %2" ).arg( m_areaSize1 ).arg( m_areaSize2 ) );
      if( m_adaptive != adaptiveDefault )
         dev.writeLine( QString( "adaptive %1" ).arg( m_adaptive ) );
      if( m_jitter )
         dev.writeLine( QString( "jitter" ) );
   }

   if( m_bFading )
   {
      dev.writeLine( QString( "fade_distance %1" ).arg( m_fadeDistance ) );
      dev.writeLine( QString( "fade_power %1" ).arg( m_fadePower ) );
   }

   if( !m_bMediaInteraction )
      dev.writeLine( QString( "media_interaction off" ) );
   if( !m_bMediaAttenuation )
      dev.writeLine( QString( "media_attenuation off" ) );

   Base::serialize( dev );
   dev.objectEnd( );
}

void PMLight::serialize( QDomElement& e, QDomDocument& doc ) const
{
   e.setAttribute( "location", m_location.serializeXML( ) );
   e.setAttribute( "color", m_color.serializeXML( ) );

   switch( m_type )
   {
      case SpotLight:
         e.setAttribute( "lighttype", "spotlight" );
         break;
      case CylinderLight:
         e.setAttribute( "lighttype", "cylinder" );
         break;
      case ShadowlessLight:
         e.setAttribute( "lighttype", "shadowless" );
         break;
      case PointLight:
         e.setAttribute( "lighttype", "point" );
         break;
   }

   if( ( m_type == SpotLight ) || ( m_type == CylinderLight ) )
   {
      e.setAttribute( "radius", m_radius );
      e.setAttribute( "falloff", m_falloff );
      e.setAttribute( "tightness", m_tightness );
      e.setAttribute( "point_at", m_pointAt.serializeXML( ) );
   }

   if( m_bAreaLight )
   {
      e.setAttribute( "area_light", "1" );
      e.setAttribute( "area_light_a", m_areaAxis1.serializeXML( ) );
      e.setAttribute( "area_light_b", m_areaAxis2.serializeXML( ) );
      e.setAttribute( "area_size_a", m_areaSize1 );
      e.setAttribute( "area_size_b", m_areaSize2 );
      e.setAttribute( "adaptive", m_adaptive );
      
      if( m_jitter )
         e.setAttribute( "jitter", "1" );
      else
         e.setAttribute( "jitter", "0" );
   }
   else
      e.setAttribute( "area_light", "0" );

   if( m_bFading )
   {
      e.setAttribute( "fading", "1" );
      e.setAttribute( "fade_distance" , m_fadeDistance );
      e.setAttribute( "fade_power", m_fadePower );
   }
   else
      e.setAttribute( "fading", "0" );

   if( m_bMediaInteraction )
      e.setAttribute( "media_interaction", "1" );
   else
      e.setAttribute( "media_interaction", "0" );
   
   if( m_bMediaAttenuation )
      e.setAttribute( "media_attenuation", "1" );
   else
      e.setAttribute( "media_attenuation", "0" );

   Base::serialize( e, doc );
}

void PMLight::readAttributes( const PMXMLHelper& h )
{
   QString str;
   
   m_location = h.vectorAttribute( "location", locationDefault );
   m_color = h.colorAttribute( "color", colorDefault );

   str = h.stringAttribute( "lighttype", "point" );
   if( str == "point" )
      m_type = PointLight;
   else if( str == "spotlight" )
      m_type = SpotLight;
   else if( str == "cylinder" )
      m_type = CylinderLight;
   else if( str == "shadowless" )
      m_type = ShadowlessLight;
   else
      m_type = PointLight;

   if( ( m_type == SpotLight ) || ( m_type == CylinderLight ) )
   {
      m_radius = h.doubleAttribute( "radius", radiusDefault );
      m_falloff = h.doubleAttribute( "falloff", falloffDefault );
      m_tightness = h.doubleAttribute( "tightness", tightnessDefault );
      m_pointAt = h.vectorAttribute( "point_at", pointAtDefault );
   }

   m_bAreaLight = h.boolAttribute( "area_light", false );
   if( m_bAreaLight )
   {
      m_areaAxis1 = h.vectorAttribute( "area_light_a", areaAxis1Default );
      m_areaAxis2 = h.vectorAttribute( "area_light_b", areaAxis2Default );
      m_areaSize1 = h.intAttribute( "area_size_a", areaSize1Default );
      m_areaSize2 = h.intAttribute( "area_size_b", areaSize2Default );
      m_adaptive = h.intAttribute( "adaptive", adaptiveDefault );
      m_jitter = h.boolAttribute( "jitter", jitterDefault );
   }
   m_bFading = h.boolAttribute( "fading", false );
   if( m_bFading )
   {
      m_fadeDistance = h.doubleAttribute( "fade_distance", fadeDistanceDefault );
      m_fadePower = h.intAttribute( "fade_power", m_fadePower );
   }
   m_bMediaInteraction = h.boolAttribute( "media_interaction", true );
   m_bMediaAttenuation = h.boolAttribute( "media_attenuation", true );
   
   Base::readAttributes( h );
}

bool PMLight::isA( PMObjectType t ) const
{
   if( t == PMTLight )
      return true;
   return Base::isA( t );
}

bool PMLight::containsLooksLike( ) const
{
   bool c = false;
   PMObject* o = firstChild( );
   while( o && !c )
   {
      if( o->type( ) == PMTLooksLike )
         c = true;
      o = o->nextSibling( );
   }
   return c;
}

bool PMLight::canInsert( PMObjectType t, const PMObject*,
                         const PMObjectList* ) const
{
   switch( t )
   {
      case PMTRotate:
      case PMTScale:
      case PMTTranslate:
      case PMTMatrix:
      case PMTComment:
      case PMTRaw:
         return true;
      case PMTLooksLike:
         return !containsLooksLike( );
      default:
         break;
   }
   return false;
}

int PMLight::canInsert( const PMObjectList& list,
                        const PMObject* ) const
{
   bool c = containsLooksLike( );
   int num = 0;
   PMObjectListIterator it( list );

   for( ; it.current( ); ++it )
   {
      switch( it.current( )->type( ) )
      {
         case PMTRotate:
         case PMTScale:
         case PMTTranslate:
         case PMTMatrix:
         case PMTComment:
         case PMTRaw:
            num++;
            break;
         case PMTLooksLike:
            if( !c )
               num++;
            c = true;
            break;
         default:
            break;
      }
   }
   return num;   
}

int PMLight::canInsert( const QValueList<PMObjectType>& list,
                        const PMObject* ) const
{
   bool c = containsLooksLike( );
   int num = 0;
   QValueList<PMObjectType>::ConstIterator it;

   for( it = list.begin( ); it != list.end( ); ++it )
   {
      switch( *it )
      {
         case PMTRotate:
         case PMTScale:
         case PMTTranslate:
         case PMTMatrix:
         case PMTComment:
         case PMTRaw:
            num++;
            break;
         case PMTLooksLike:
            if( !c )
               num++;
            c = true;
            break;
         default:
            break;
      }
   }
   return num;   
}

void PMLight::setLocation( const PMVector& p )
{
   if( p != m_location )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMLocationID, m_location );
      m_location = p;
      m_location.resize( 3 );
      setViewStructureChanged( );
   }
}

void PMLight::setColor( const PMColor& c )
{
   if( c != m_color )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMColorID, m_color );
      m_color = c;
   }
}

void PMLight::setLightType( PMLightType t )
{
   if( t != m_type )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMTypeID, m_type );
      m_type = t;
      setViewStructureChanged( );
   }
}

void PMLight::setRadius( double r )
{
   if( !approx( r, m_radius ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMRadiusID, m_radius );
      m_radius = r;
      setViewStructureChanged( );
   }
}

void PMLight::setFalloff( double f )
{
   if( !approx( f, m_falloff ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMFalloffID, m_falloff );
      m_falloff = f;
      setViewStructureChanged( );
   }
}

void PMLight::setTightness( double t )
{
   if( !approx( t, m_tightness ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMTightnessID, m_tightness );
      m_tightness = t;
   }
}

void PMLight::setPointAt( const PMVector& v )
{
   if( !m_pointAt.approxEqual( v ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMPointAtID, m_pointAt );
      m_pointAt = v;
      setViewStructureChanged( );
   }
}

void PMLight::setAreaLight( bool yes )
{
   if( yes != m_bAreaLight )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAreaLightID, m_bAreaLight );
      m_bAreaLight = yes;
      setViewStructureChanged( );
   }
}

void PMLight::setAxis1( const PMVector& v )
{
   if( !m_areaAxis1.approxEqual( v ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAreaAxis1ID, m_areaAxis1 );
      m_areaAxis1 = v;
      setViewStructureChanged( );
   }
}

void PMLight::setAxis2( const PMVector& v )
{
   if( !m_areaAxis2.approxEqual( v ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAreaAxis2ID, m_areaAxis2 );
      m_areaAxis2 = v;
      setViewStructureChanged( );
   }
}

void PMLight::setSize1( int s )
{
   if( s != m_areaSize1 )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAreaSize1ID, m_areaSize1 );
      m_areaSize1 = s;
      setViewStructureChanged( );
   }
}

void PMLight::setSize2( int s )
{
   if( s != m_areaSize2 )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAreaSize2ID, m_areaSize2 );
      m_areaSize2 = s;
      setViewStructureChanged( );
   }
}

void PMLight::setAdaptive( int a )
{
   if( a != m_adaptive )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAdaptiveID, m_adaptive );
      m_adaptive = a;
   }
}

void PMLight::setJitter( bool j )
{
   if( j != m_jitter )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMJitterID, m_jitter );
      m_jitter = j;
   }
}

void PMLight::setFading( bool y )
{
   if( y != m_bFading )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMFadingID, m_bFading );
      m_bFading = y;
   }
}

void PMLight::setFadeDistance( double d )
{
   if( !approx( d, m_fadeDistance ) )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMFadeDistanceID, m_fadeDistance );
      m_fadeDistance = d;
   }
}

void PMLight::setFadePower( int p )
{
   if( p != m_fadePower )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMFadePowerID, m_fadePower );
      m_fadePower = p;
   }
}

void PMLight::setMediaInteraction( bool y )
{
   if( y != m_bMediaInteraction )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMInteractionID, m_bMediaInteraction );
      m_bMediaInteraction = y;
   }
}

void PMLight::setMediaAttenuation( bool y )
{
   if( y != m_bMediaAttenuation )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTLight, PMAttenuationID, m_bMediaAttenuation );
      m_bMediaAttenuation = y;
   }
}

PMDialogEditBase* PMLight::editWidget( QWidget* parent ) const
{
   return new PMLightEdit( parent );
}

void PMLight::restoreMemento( PMMemento* s )
{
   PMMementoDataIterator it( s );
   PMMementoData* data;

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTLight )
      {
         switch( data->valueID( ) )
         {
            case PMLocationID:
               setLocation( data->vectorData( ) );
               break;
            case PMColorID:
               setColor( data->colorData( ) );
               break;
            case PMTypeID:
               setLightType( ( PMLightType ) ( data->intData( ) ) );
               break;
            case PMRadiusID:
               setRadius( data->doubleData( ) );
               break;
            case PMFalloffID:
               setFalloff( data->doubleData( ) );
               break;
            case PMTightnessID:
               setTightness( data->doubleData( ) );
               break;
            case PMPointAtID:
               setPointAt( data->vectorData( ) );
               break;
            case PMAreaLightID:
               setAreaLight( data->boolData( ) );
               break;
            case PMAreaAxis1ID:
               setAxis1( data->vectorData( ) );
               break;
            case PMAreaAxis2ID:
               setAxis2( data->vectorData( ) );
               break;
            case PMAreaSize1ID:
               setSize1( data->intData( ) );
               break;
            case PMAreaSize2ID:
               setSize2( data->intData( ) );
               break;
            case PMAdaptiveID:
               setAdaptive( data->intData( ) );
               break;
            case PMJitterID:
               setJitter( data->boolData( ) );
               break;
            case PMFadingID:
               setFading( data->boolData( ) );
               break;
            case PMFadeDistanceID:
               setFadeDistance( data->doubleData( ) );
               break;
            case PMFadePowerID:
               setFadePower( data->intData( ) );
               break;
            case PMInteractionID:
               setMediaInteraction( data->boolData( ) );
               break;
            case PMAttenuationID:
               setMediaAttenuation( data->boolData( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMLight::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );
}

void PMLight::createViewStructure( )
{
   if( ( m_type == PointLight ) || ( m_type == ShadowlessLight ) )
   { 
      if( !m_pViewStructure )
      {
         m_pViewStructure = new PMViewStructure( defaultPointStructure( ) );
         m_pViewStructure->points( ).detach( );
      }
      else
      {
         m_pViewStructure->points( ).resize(
            defaultPointStructure( )->points( ).size( ) );
         m_pViewStructure->lines( ) = defaultPointStructure( )->lines( );
      }
         
      PMPointArray& points = m_pViewStructure->points( );
         
      int i;
      double c = s_pointLightSize / sqrt( 3 );
      for( i = 0; i < 14; i++ )
         points[i] = PMPoint( m_location );
         
      points[0][0] += s_pointLightSize;   
      points[1][0] -= s_pointLightSize;
      points[2][1] += s_pointLightSize;
      points[3][1] -= s_pointLightSize;
      points[4][2] += s_pointLightSize;
      points[5][2] -= s_pointLightSize;
         
      for( i = 0; i < 4; i++ )
      {
         points[6+2*i][0] += c;
         points[6+2*i][1] += ( i & 1 ? c : -c );
         points[6+2*i][2] += ( i & 2 ? c : -c );
         points[7+2*i][0] -= c;
         points[7+2*i][1] -= ( i & 1 ? c : -c );
         points[7+2*i][2] -= ( i & 2 ? c : -c );
      }
   }
   else if( m_type == SpotLight )
   {
      if( !m_pViewStructure )
      {
         m_pViewStructure = new PMViewStructure( defaultSpotStructure( ) );
         m_pViewStructure->points( ).detach( );
      }
      else
      {
         m_pViewStructure->points( ).resize(
            defaultSpotStructure( )->points( ).size( ) );
         m_pViewStructure->lines( ) = defaultSpotStructure( )->lines( );
      }
         
      PMPointArray& points = m_pViewStructure->points( );
         
      points[0] = PMPoint( m_location );
         
      PMVector pointAtVector = m_pointAt - m_location;
      double pl = pointAtVector.abs( );
      if( approxZero( pl ) )
         pointAtVector = PMVector( 0.0, 0.0, 1.0 );
      else
         pointAtVector /= pl;
      PMVector endPoint = pointAtVector.orthogonal( );
      PMMatrix rotation = PMMatrix::rotation( pointAtVector,
                                              2 * M_PI / s_nSpotLines );
      double length, r1, r2, a1, a2;
      length = s_length;
      a1 = m_radius;
      a2 = m_falloff;
      if( a1 < 0 ) a1 = 0;
      if( a2 < 0 ) a2 = 0;
      if( a1 > a2 ) a1 = a2;
      if( a1 >= 89.9 ) a1 = 89.9;
      if( a2 >= 89.9 ) a2 = 89.9;
      a1 *= M_PI / 180;
      a2 *= M_PI / 180;
      r1 = tan( a1 ) * length;
      r2 = tan( a2 ) * length;

      if( r2 > length )
      {
         double d = length / r2;
         r1 *= d;
         r2 *= d;
         length *= d;
      }

      endPoint *= r2;
      double r;
      if( approxZero( r2 ) )
         r = 1;
      else
         r = r1 / r2;
      
      PMVector circleCenter = m_location + length * pointAtVector;
      points[1] = PMPoint( circleCenter + endPoint );
      points[s_nSpotLines + 1] = PMPoint( circleCenter + endPoint * r );
         
      int i;
      for( i = 2; i < ( s_nSpotLines + 1 ); i++ )
      {
         endPoint = rotation * endPoint;
         points[i] = PMPoint( circleCenter + endPoint );
         points[s_nSpotLines + i] = PMPoint( circleCenter + endPoint * r );
      }
      points[s_nSpotLines*2+1] = m_pointAt;
   }
   else if( m_type == CylinderLight )
   {
      if( !m_pViewStructure )
      {
         m_pViewStructure = new PMViewStructure( defaultCylindricalStructure( ) );
         m_pViewStructure->points( ).detach( );
      }
      else
      {
         m_pViewStructure->points( ).resize(
            defaultCylindricalStructure( )->points( ).size( ) );
         m_pViewStructure->lines( ) = defaultCylindricalStructure( )->lines( );
      }
         
      PMPointArray& points = m_pViewStructure->points( );
         
      points[s_nCylinderLines*4] = PMPoint( m_location );
      points[s_nCylinderLines*4+1] = PMPoint( m_pointAt );
         
      PMVector pointAtVector = m_pointAt - m_location;
      double pl = pointAtVector.abs( );
      if( approxZero( pl ) )
         pointAtVector = PMVector( 0.0, 0.0, 1.0 );
      else
         pointAtVector /= pl;
      PMVector endPoint = pointAtVector.orthogonal( );
      PMMatrix rotation = PMMatrix::rotation( pointAtVector,
                                              2 * M_PI / s_nCylinderLines );
      double r1, r2;
      r1 = m_radius / 100;
      r2 = m_falloff / 100;
      if( r1 < 0 ) r1 = 0;
      if( r2 < 0 ) r2 = 0;
      if( r1 > r2 ) r1 = r2;

      endPoint *= r2;
      double r;
      if( approxZero( r2 ) )
         r = 1;
      else
         r = r1 / r2;
      
      PMVector circleCenter = m_location + s_length * pointAtVector;
      points[0] = PMPoint( circleCenter + endPoint );
      points[s_nCylinderLines] = PMPoint( m_location + endPoint );
      points[2*s_nCylinderLines] = PMPoint( circleCenter + endPoint * r );
      points[3*s_nCylinderLines] = PMPoint( m_location + endPoint * r );
         
      int i;
      for( i = 1; i < s_nCylinderLines; i++ )
      {
         endPoint = rotation * endPoint;
         points[i] = PMPoint( circleCenter + endPoint );
         points[s_nCylinderLines + i] = PMPoint( m_location + endPoint );
         points[2*s_nCylinderLines + i] = PMPoint( circleCenter + endPoint * r );
         points[3*s_nCylinderLines + i] = PMPoint( m_location + endPoint * r );
      }
   }

   if( m_bAreaLight )
   {
      int s1, s2;

      s1 = m_areaSize1;
      s2 = m_areaSize2;
      if( s1 < 1 ) s1 = 1;
      if( s2 < 1 ) s2 = 1;

      if( ( s1 > 1 ) || ( s2 > 1 ) )
      {
         int x, y, h;
         int ps, ls;
         PMVector bp;
         
         PMPointArray& points = m_pViewStructure->points( );
         PMLineArray& lines = m_pViewStructure->lines( );
         points.detach( );
         lines.detach( );
         
         ps = points.size( );
         ls = lines.size( );
         points.resize( ps + s1*s2 );
         lines.resize( ls + s1*(s2-1) + s2*(s1-1) );

         if( s1 == 1 )
         {
            bp = m_location - m_areaAxis2/2;
            for( y = 0; y < s2; y++ )
               points[ps + y] = PMPoint( bp + m_areaAxis2
                                         * ( (double)y/(double)(s2-1) ) );
            for( y = 0; y < ( s2-1 ); y++ )
               lines[ls+y] = PMLine( ps + y, ps + y+1 );
         }
         else if( s2 == 1 )
         {
            bp = m_location - m_areaAxis1/2;
            for( x = 0; x < s1; x++ )
               points[ps + x] = PMPoint( bp + m_areaAxis1
                                         * ( (double)x/(double)(s1-1) ) );
            for( x = 0; x < ( s1-1 ); x++ )
               lines[ls+x] = PMLine( ps + x, ps + x+1 );
         }
         else
         {
            bp = m_location - m_areaAxis1/2 - m_areaAxis2/2;
            for( x = 0; x < s1; x++ )
               for( y = 0; y < s2; y++ )
                  points[ps + y*s1 + x] =
                     PMPoint( bp + m_areaAxis1 * ( (double)x/(double)(s1-1) )
                              + m_areaAxis2 * ( (double)y/(double)(s2-1) ) );
            
            for( x = 0; x < s1; x++ )
            {
               for( y = 0; y < (s2-1); y++ )
               {
                  h = ps + x + s1*y;
                  lines[ls + x*(s2-1) + y] = PMLine( h, h+s1 );
               }
            }
            
            ls += s1*(s2-1);
            for( y = 0; y < s2; y++ )
            {
               for( x = 0; x < (s1-1); x++ )
               {
                  h = ps + x + s1*y;
                  lines[ls + y*(s1-1) + x] = PMLine( h, h+1 );
               }
            }
         }
      }     
   }
}

PMViewStructure* PMLight::defaultPointStructure( ) const
{
   if( !s_pDefaultPointStructure )
   {
      s_pDefaultPointStructure = new PMViewStructure( 14, 7 );
//      PMPointArray& points = s_pDefaultPointStructure->points( );
      PMLineArray& lines = s_pDefaultPointStructure->lines( );

      lines[0] = PMLine( 0, 1 );
      lines[1] = PMLine( 2, 3 );
      lines[2] = PMLine( 4, 5 );
      lines[3] = PMLine( 6, 7 );
      lines[4] = PMLine( 8, 9 );
      lines[5] = PMLine( 10, 11 );
      lines[6] = PMLine( 12, 13 );
   }
   return s_pDefaultPointStructure;
}

PMViewStructure* PMLight::defaultSpotStructure( ) const
{
   if( !s_pDefaultSpotStructure )
   {
      s_pDefaultSpotStructure = new PMViewStructure( s_nSpotLines * 2 + 2, s_nSpotLines * 3 + 1 );
//      PMPointArray& points = s_pDefaultSpotStructure->points( );
      PMLineArray& lines = s_pDefaultSpotStructure->lines( );

      int i;
      for( i = 0; i < s_nSpotLines; i++ )
      {
         lines[i] = PMLine( 0, i+1 );
         lines[s_nSpotLines + i] = PMLine( i+1, i+2 );
         lines[2*s_nSpotLines + i] = PMLine( s_nSpotLines + i+1,
                                             s_nSpotLines + i+2 );
      }
      // fix for the last line
      lines[2*s_nSpotLines - 1] = PMLine( 1, s_nSpotLines );
      lines[3*s_nSpotLines - 1] = PMLine( s_nSpotLines + 1, s_nSpotLines*2 );
      lines[3*s_nSpotLines] = PMLine( 0, s_nSpotLines*2 + 1 );
   }
   return s_pDefaultSpotStructure;
}

PMViewStructure* PMLight::defaultCylindricalStructure( ) const
{
   if( !s_pDefaultCylindricalStructure )
   {
      s_pDefaultCylindricalStructure = new PMViewStructure( s_nCylinderLines * 4 + 2, s_nCylinderLines * 5 + 1 );
//      PMPointArray& points = s_pDefaultCylindricalStructure->points( );
      PMLineArray& lines = s_pDefaultCylindricalStructure->lines( );

      int i;
      for( i = 0; i < s_nCylinderLines; i++ )
      {
         lines[i] = PMLine( i, i+1 );
         lines[s_nCylinderLines + i] = PMLine( i + s_nCylinderLines,
                                               i + s_nCylinderLines + 1 );
         lines[2*s_nCylinderLines + i] = PMLine( i + 2*s_nCylinderLines,
                                                 i + 2*s_nCylinderLines + 1 );
         lines[3*s_nCylinderLines + i] = PMLine( i + 3*s_nCylinderLines,
                                                 i + 3*s_nCylinderLines + 1 );
         lines[4*s_nCylinderLines + i] = PMLine( i, i + s_nCylinderLines );
      }
      // fix for some lines
      lines[s_nCylinderLines-1] = PMLine( 0, s_nCylinderLines - 1 );
      lines[2*s_nCylinderLines-1] = PMLine( s_nCylinderLines,
                                            2*s_nCylinderLines - 1 );
      lines[3*s_nCylinderLines-1] = PMLine( 2*s_nCylinderLines,
                                            3*s_nCylinderLines - 1 );
      lines[4*s_nCylinderLines-1] = PMLine( 3*s_nCylinderLines,
                                            4*s_nCylinderLines - 1 );
      lines[5*s_nCylinderLines] = PMLine( 4*s_nCylinderLines,
                                          4*s_nCylinderLines + 1 );
   }
   return s_pDefaultCylindricalStructure;
}

void PMLight::controlPoints( PMControlPointList& list )
{
   list.append( new PM3DControlPoint( m_location, PMLocationID, i18n( "Location" ) ) );
   if( ( m_type == SpotLight ) || ( m_type == CylinderLight ) )
      list.append( new PM3DControlPoint( m_pointAt, PMPointAtID, i18n( "Point at" ) ) );
}

void PMLight::controlPointsChanged( PMControlPointList& list )
{
   PMControlPoint* p;

   for( p = list.first( ); p; p = list.next( ) )
   {
      if( p->changed( ) )
      {
         switch( p->id( ) )
         {
            case PMLocationID:
               setLocation( ( ( PM3DControlPoint* ) p )->point( ) );
               break;
            case PMPointAtID:
               setPointAt( ( ( PM3DControlPoint* ) p )->point( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMLight::controlPointsChanged\n";
               break;
         }
      }
   }
}

void PMLight::cleanUp( ) const
{
   if( s_pDefaultPointStructure )
      delete s_pDefaultPointStructure;
   s_pDefaultPointStructure = 0;
   if( s_pDefaultSpotStructure )
      delete s_pDefaultSpotStructure;
   s_pDefaultSpotStructure = 0;
   if( s_pDefaultCylindricalStructure )
      delete s_pDefaultCylindricalStructure;
   s_pDefaultCylindricalStructure = 0;
   Base::cleanUp( );
}
