/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2000-2002 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 "pmxmlhelper.h"
#include "pmlightedit.h"
#include "pmmemento.h"
#include "pmviewstructure.h"
#include "pm3dcontrolpoint.h"
#include "pmmath.h"
#include "pmmatrix.h"
#include "pmenumproperty.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 bool parallelDefault = false;
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 orientDefault = false;
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 = 1.0;

PMDefinePropertyClass( PMLight, PMLightProperty );
PMDefineEnumPropertyClass( PMLight, PMLight::PMLightType, PMTypeProperty );
PMDefineEnumPropertyClass( PMLight, PMLight::PMAreaType, PMAreaProperty );

PMMetaObject* PMLight::s_pMetaObject = 0;
PMObject* createNewLight( PMPart* part )
{
   return new PMLight( part );
}

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

PMLight::PMLight( const PMLight& l )
      : Base( l )
{
   m_location = l.m_location;
   m_color = l.m_color;
   m_type = l.m_type;
   m_radius = l.m_radius;
   m_falloff = l.m_falloff;
   m_tightness = l.m_tightness;
   m_pointAt = l.m_pointAt;
   m_parallel = l.m_parallel;
   m_bAreaLight = l.m_bAreaLight;
   m_areaType = l.m_areaType;
   m_areaAxis1 = l.m_areaAxis1;
   m_areaAxis2 = l.m_areaAxis2;
   m_areaSize1 = l.m_areaSize1;
   m_areaSize2 = l.m_areaSize2;
   m_adaptive = l.m_adaptive;
   m_orient = l.m_orient;
   m_jitter = l.m_jitter;
   m_bFading = l.m_bFading;
   m_fadeDistance = l.m_fadeDistance;
   m_fadePower = l.m_fadePower;
   m_bMediaInteraction = l.m_bMediaInteraction;
   m_bMediaAttenuation = l.m_bMediaAttenuation;
}

PMLight::~PMLight( )
{
}

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

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_parallel )
      e.setAttribute( "parallel", "1" );
   else
      e.setAttribute( "parallel", "0" );

   if( m_bAreaLight )
   {
      if ( m_areaType == Rectangular )
         e.setAttribute( "areatype", "rectangular" );
      else
         e.setAttribute( "areatype", "circular" );

      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_orient )
         e.setAttribute( "orient", "1" );
      else
         e.setAttribute( "orient", "0" );

      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_parallel = h.boolAttribute( "parallel", parallelDefault );

   m_bAreaLight = h.boolAttribute( "area_light", false );
   if( m_bAreaLight )
   {
      str = h.stringAttribute( "areatype", "rectangular" );
      if ( str == "circular" )
         m_areaType = Circular;
      else
         m_areaType = Rectangular;

      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_orient = h.boolAttribute( "orient", orientDefault );
      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 );
}

PMMetaObject* PMLight::metaObject( ) const
{
   if( !s_pMetaObject )
   {
      s_pMetaObject = new PMMetaObject( "Light", Base::metaObject( ),
                                        createNewLight );
      PMTypeProperty* p = new PMTypeProperty( "lightType", &PMLight::setLightType,
                                              &PMLight::lightType );
      p->addEnumValue( "PointLight", PointLight );
      p->addEnumValue( "SpotLight", SpotLight );
      p->addEnumValue( "CylinderLight", CylinderLight );
      p->addEnumValue( "ShadowlessLight", ShadowlessLight );
      s_pMetaObject->addProperty( p );

      s_pMetaObject->addProperty(
         new PMLightProperty( "location", &PMLight::setLocation, &PMLight::location ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "color", &PMLight::setColor, &PMLight::color ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "radius", &PMLight::setRadius, &PMLight::radius ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "falloff", &PMLight::setFalloff, &PMLight::falloff ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "tightness", &PMLight::setTightness, &PMLight::tightness ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "pointAt", &PMLight::setPointAt, &PMLight::pointAt ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "parallel", &PMLight::setParallel, &PMLight::parallel ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "areaLight", &PMLight::setAreaLight, &PMLight::isAreaLight ) );

      PMAreaProperty* p2 = new PMAreaProperty( "areaType", &PMLight::setAreaType,
                                               &PMLight::areaType );
      p2->addEnumValue( "Rectangular", Rectangular );
      p2->addEnumValue( "Circular", Circular );
      s_pMetaObject->addProperty( p2 );

      s_pMetaObject->addProperty(
         new PMLightProperty( "axis1", &PMLight::setAxis1, &PMLight::axis1 ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "axis2", &PMLight::setAxis2, &PMLight::axis2 ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "adaptive", &PMLight::setAdaptive, &PMLight::adaptive ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "orient", &PMLight::setOrient, &PMLight::orient ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "jitter", &PMLight::setJitter, &PMLight::jitter ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "fading", &PMLight::setFading, &PMLight::fading ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "fadeDistance", &PMLight::setFadeDistance, &PMLight::fadeDistance ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "fadePower", &PMLight::setFadePower, &PMLight::fadePower ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "mediaInteraction", &PMLight::setMediaInteraction,
                         &PMLight::mediaInteraction ) );
      s_pMetaObject->addProperty(
         new PMLightProperty( "mediaAttenuation", &PMLight::setMediaAttenuation,
                         &PMLight::mediaAttenuation ) );
   }
   return s_pMetaObject;
}

void PMLight::setLocation( const PMVector& p )
{
   if( p != m_location )
   {
      if( m_pMemento )
         m_pMemento->addData( s_pMetaObject, 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( s_pMetaObject, PMColorID, m_color );
      m_color = c;
   }
}

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

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

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

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

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

void PMLight::setParallel( bool p )
{
   if ( p != m_parallel )
   {
      if ( m_pMemento )
         m_pMemento->addData( s_pMetaObject, PMParallelID, m_parallel );
      m_parallel = p;
   }
}

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

void PMLight::setAreaType( PMAreaType at )
{
   if ( at != m_areaType )
   {
      if ( m_pMemento )
         m_pMemento->addData( s_pMetaObject, PMAreaTypeID, m_areaType );
      m_areaType = at;
      setViewStructureChanged( );
   }
}

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

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

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

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

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

void PMLight::setOrient( bool o )
{
   if( o != m_orient )
   {
      if ( m_pMemento )
         m_pMemento->addData( s_pMetaObject, PMOrientID, m_orient );
      m_orient = o;
      setViewStructureChanged( );
   }
}

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

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

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

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

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

void PMLight::setMediaAttenuation( bool y )
{
   if( y != m_bMediaAttenuation )
   {
      if( m_pMemento )
         m_pMemento->addData( s_pMetaObject, 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( ) == s_pMetaObject )
      {
         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 PMParallelID:
               setParallel( data->boolData( ) );
               break;
            case PMAreaLightID:
               setAreaLight( data->boolData( ) );
               break;
            case PMAreaTypeID:
               setAreaType( ( PMAreaType ) ( data->intData( ) ) );
               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 PMOrientID:
               setOrient( data->boolData( ) );
               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.0 );
      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;

            if ( m_areaType == Rectangular || s1 < 2 || s2 < 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) ) );
            }
            else
            {
               double stepX = ( 2.0 / (double)(s1-1) );
               double stepY = ( 2.0 / (double)(s2-1) );
               double doubleX, doubleY, xSqr, scaleFactor;

               for ( x = 0; x < s1; ++x )
               {
                  doubleX = ( (double)x * stepX ) - 1.0;
                  xSqr = doubleX * doubleX;
                  for ( y = 0; y < s2; ++y )
                  {
                     doubleY = ( (double)y * stepY ) - 1.0;

                     if ( doubleX == 0.0 && doubleY == 0.0 )
                        scaleFactor = 1.0;
                     else
                     {
                        if ( fabs( doubleX ) > fabs( doubleY ) )
                           scaleFactor = fabs( doubleX );
                        else
                           scaleFactor = fabs( doubleY );
                        scaleFactor /= sqrt( xSqr + doubleY * doubleY );
                     }

                     points[ps + y*s1 + x] =
                        PMPoint( bp + m_areaAxis1 *
                                 ( ( ( doubleX * scaleFactor ) / 2.0 ) + 0.5 )
                                 + m_areaAxis2 *
                                 ( ( ( doubleY * scaleFactor ) / 2.0 ) + 0.5 ) );
                  }
               }
            }

            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;
   if( s_pMetaObject )
   {
      delete s_pMetaObject;
      s_pMetaObject = 0;
   }
   Base::cleanUp( );
}