|
|
|
/*
|
|
|
|
**************************************************************************
|
|
|
|
description
|
|
|
|
--------------------
|
|
|
|
copyright : (C) 2001-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 "pmtexturemapedit.h"
|
|
|
|
#include "pmtexturemap.h"
|
|
|
|
|
|
|
|
#include "pmxmlhelper.h"
|
|
|
|
#include "pmmapmemento.h"
|
|
|
|
|
|
|
|
#include <tqtextstream.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
|
|
|
|
class PMValueProperty : public PMPropertyBase
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
PMValueProperty( )
|
|
|
|
: PMPropertyBase( "mapValues", PMVariant::Double )
|
|
|
|
{
|
|
|
|
m_index = 0;
|
|
|
|
}
|
|
|
|
virtual int dimensions( ) const { return 1; }
|
|
|
|
virtual void setIndex( int /*dimension*/, int index )
|
|
|
|
{
|
|
|
|
m_index = index;
|
|
|
|
}
|
|
|
|
virtual int size( PMObject* object, int /*dimension*/ ) const
|
|
|
|
{
|
|
|
|
return ( ( PMTextureMapBase* ) object )->mapEntries( );
|
|
|
|
}
|
|
|
|
protected:
|
|
|
|
virtual bool setProtected( PMObject* obj, const PMVariant& var )
|
|
|
|
{
|
|
|
|
PMTextureMapBase* m = ( PMTextureMapBase* ) obj;
|
|
|
|
TQValueList<double> list = m->mapValues( );
|
|
|
|
TQValueList<double>::Iterator it = list.at( m_index );
|
|
|
|
|
|
|
|
if( it == list.end( ) )
|
|
|
|
{
|
|
|
|
kdError( PMArea ) << "Range error in PMTextureMapBase::ValueProperty::set" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*it = var.doubleData( );
|
|
|
|
|
|
|
|
m->setMapValues( list );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
virtual PMVariant getProtected( const PMObject* obj )
|
|
|
|
{
|
|
|
|
PMTextureMapBase* m = ( PMTextureMapBase* ) obj;
|
|
|
|
TQValueList<double> list = m->mapValues( );
|
|
|
|
TQValueList<double>::ConstIterator it = list.at( m_index );
|
|
|
|
|
|
|
|
if( it == list.end( ) )
|
|
|
|
{
|
|
|
|
kdError( PMArea ) << "Range error in PMTextureMapBase::ValueProperty::get" << endl;
|
|
|
|
return PMVariant( );
|
|
|
|
}
|
|
|
|
|
|
|
|
return PMVariant( *it );
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
int m_index;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMTextureMapBase::s_pMetaObject = 0;
|
|
|
|
|
|
|
|
PMTextureMapBase::PMTextureMapBase( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMTextureMapBase::PMTextureMapBase( const PMTextureMapBase& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMTextureMapBase::~PMTextureMapBase( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::serialize( TQDomElement& e, TQDomDocument& doc ) const
|
|
|
|
{
|
|
|
|
e.setAttribute( "map_values", valuesToString( ) );
|
|
|
|
Base::serialize( e, doc );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::readAttributes( const PMXMLHelper& h )
|
|
|
|
{
|
|
|
|
stringToValues( h.stringAttribute( "map_values", "" ) );
|
|
|
|
Base::readAttributes( h );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMTextureMapBase::valuesToString( ) const
|
|
|
|
{
|
|
|
|
TQString str;
|
|
|
|
TQValueList<double>::ConstIterator it;
|
|
|
|
|
|
|
|
it = m_mapValues.begin( );
|
|
|
|
if( it != m_mapValues.end( ) )
|
|
|
|
{
|
|
|
|
str.setNum( *it );
|
|
|
|
++it;
|
|
|
|
for( ; it != m_mapValues.end( ); ++it )
|
|
|
|
str += TQString( " %1" ).arg( *it );
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::stringToValues( const TQString& str )
|
|
|
|
{
|
|
|
|
m_mapValues.clear( );
|
|
|
|
TQString tstr = str;
|
|
|
|
TQTextIStream s( &tstr );
|
|
|
|
double d;
|
|
|
|
|
|
|
|
while( !s.atEnd( ) )
|
|
|
|
{
|
|
|
|
s >> d;
|
|
|
|
m_mapValues.append( d );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMTextureMapBase::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "TextureMapBase", Base::metaObject( ) );
|
|
|
|
s_pMetaObject->addProperty( new PMValueProperty( ) );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMDialogEditBase* PMTextureMapBase::editWidget( TQWidget* parent ) const
|
|
|
|
{
|
|
|
|
return new PMTextureMapEdit( parent );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::restoreMemento( PMMemento* s )
|
|
|
|
{
|
|
|
|
PMMapMemento* m = ( PMMapMemento* ) s;
|
|
|
|
if( m->mapValuesSaved( ) )
|
|
|
|
{
|
|
|
|
if( m_pMemento )
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setMapValues( m_mapValues );
|
|
|
|
m_mapValues = m->mapValues( );
|
|
|
|
}
|
|
|
|
if( m->removedValuesSaved( ) )
|
|
|
|
{
|
|
|
|
if( m_pMemento )
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setRemovedValues( m_removedValues );
|
|
|
|
m_removedValues = m->removedValues( );
|
|
|
|
}
|
|
|
|
|
|
|
|
Base::restoreMemento( s );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::createMemento( )
|
|
|
|
{
|
|
|
|
if( m_pMemento )
|
|
|
|
delete m_pMemento;
|
|
|
|
m_pMemento = new PMMapMemento( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQValueList<double>::Iterator PMTextureMapBase::valueForChild( PMObject* obj )
|
|
|
|
{
|
|
|
|
PMObject* o = firstChild( );
|
|
|
|
TQValueList<double>::Iterator it = m_mapValues.begin( );
|
|
|
|
|
|
|
|
while( o && ( o != obj ) )
|
|
|
|
{
|
|
|
|
if( o->type( ) == mapType( ) )
|
|
|
|
++it;
|
|
|
|
o = o->nextSibling( );
|
|
|
|
}
|
|
|
|
return it;
|
|
|
|
}
|
|
|
|
|
|
|
|
double PMTextureMapBase::mapValue( const PMObject* obj ) const
|
|
|
|
{
|
|
|
|
PMObject* o = firstChild( );
|
|
|
|
TQValueList<double>::ConstIterator it = m_mapValues.begin( );
|
|
|
|
|
|
|
|
while( o && ( o != obj ) )
|
|
|
|
{
|
|
|
|
if( o->type( ) == mapType( ) )
|
|
|
|
++it;
|
|
|
|
o = o->nextSibling( );
|
|
|
|
}
|
|
|
|
return *it;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::childAdded( PMObject* ao )
|
|
|
|
{
|
|
|
|
if( ( unsigned ) countChildren( ) <= m_mapValues.count( ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if( m_pMemento )
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setMapValues( m_mapValues );
|
|
|
|
if( m_removedValues.isEmpty( ) )
|
|
|
|
{
|
|
|
|
TQValueList<double>::Iterator it = valueForChild( ao );
|
|
|
|
if( it == m_mapValues.end( ) )
|
|
|
|
{
|
|
|
|
--it;
|
|
|
|
if( it == m_mapValues.end( ) )
|
|
|
|
m_mapValues.append( 0.0 );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
double v = *it + 0.1;
|
|
|
|
if( v > 1.0 )
|
|
|
|
v = 1.0;
|
|
|
|
m_mapValues.append( v );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( it == m_mapValues.begin( ) )
|
|
|
|
m_mapValues.prepend( 0.0 );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
double va = *it;
|
|
|
|
double vb = *( --it );
|
|
|
|
m_mapValues.insert( ++it, ( va + vb ) / 2.0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( m_pMemento )
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setRemovedValues( m_removedValues );
|
|
|
|
|
|
|
|
TQValueList<double>::Iterator it = m_mapValues.begin( );
|
|
|
|
bool stop = false;
|
|
|
|
double v = m_removedValues.last( );
|
|
|
|
m_removedValues.remove( m_removedValues.fromLast( ) );
|
|
|
|
|
|
|
|
while( ( it != m_mapValues.end( ) ) && !stop )
|
|
|
|
{
|
|
|
|
if( ( *it ) > v )
|
|
|
|
stop = true;
|
|
|
|
else
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
m_mapValues.insert( it, v );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PMTextureMapBase::takeChild( PMObject* o )
|
|
|
|
{
|
|
|
|
if( m_pMemento )
|
|
|
|
{
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setMapValues( m_mapValues );
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setRemovedValues( m_removedValues );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQValueList<double>::Iterator it = valueForChild( o );
|
|
|
|
if( it != m_mapValues.end( ) )
|
|
|
|
{
|
|
|
|
m_removedValues.append( *it );
|
|
|
|
m_mapValues.remove( it );
|
|
|
|
}
|
|
|
|
|
|
|
|
return Base::takeChild( o );
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMapBase::setMapValues( const TQValueList<double>& v )
|
|
|
|
{
|
|
|
|
if( m_pMemento )
|
|
|
|
{
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setMapValues( m_mapValues );
|
|
|
|
( ( PMMapMemento* ) m_pMemento )->setRemovedValues( m_removedValues );
|
|
|
|
}
|
|
|
|
m_removedValues.clear( );
|
|
|
|
m_mapValues = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
PMObject* PMTextureMapBase::nextMapEntry( PMObject* o )
|
|
|
|
{
|
|
|
|
bool stop = false;
|
|
|
|
PMObject* result = o;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if( result == 0 )
|
|
|
|
result = firstChild( );
|
|
|
|
else
|
|
|
|
result = result->nextSibling( );
|
|
|
|
|
|
|
|
if( !result )
|
|
|
|
stop = true;
|
|
|
|
else if( result->type( ) == mapType( ) )
|
|
|
|
stop = true;
|
|
|
|
}
|
|
|
|
while( !stop );
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMTextureMap::s_pMetaObject = 0;
|
|
|
|
PMObject* createNewTextureMap( PMPart* part )
|
|
|
|
{
|
|
|
|
return new PMTextureMap( part );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMTextureMap::PMTextureMap( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMTextureMap::PMTextureMap( const PMTextureMap& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMTextureMap::~PMTextureMap( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMTextureMap::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "TextureMap", Base::metaObject( ),
|
|
|
|
createNewTextureMap );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMTextureMap::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMTextureMap::description( ) const
|
|
|
|
{
|
|
|
|
return i18n( "texture map" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMPigmentMap::s_pMetaObject = 0;
|
|
|
|
PMObject* createNewPigmentMap( PMPart* part )
|
|
|
|
{
|
|
|
|
return new PMPigmentMap( part );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMPigmentMap::PMPigmentMap( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMPigmentMap::PMPigmentMap( const PMPigmentMap& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMPigmentMap::~PMPigmentMap( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMPigmentMap::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "PigmentMap", Base::metaObject( ),
|
|
|
|
createNewPigmentMap );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMPigmentMap::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMPigmentMap::description( ) const
|
|
|
|
{
|
|
|
|
return i18n( "pigment map" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMColorMap::s_pMetaObject = 0;
|
|
|
|
PMObject* createNewColorMap( PMPart* part )
|
|
|
|
{
|
|
|
|
return new PMColorMap( part );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMColorMap::PMColorMap( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMColorMap::PMColorMap( const PMColorMap& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMColorMap::~PMColorMap( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMColorMap::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "ColorMap", Base::metaObject( ),
|
|
|
|
createNewColorMap );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMColorMap::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMColorMap::description( ) const
|
|
|
|
{
|
|
|
|
return i18n( "color map" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMNormalMap::s_pMetaObject = 0;
|
|
|
|
PMObject* createNewNormalMap( PMPart* part )
|
|
|
|
{
|
|
|
|
return new PMNormalMap( part );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMNormalMap::PMNormalMap( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMNormalMap::PMNormalMap( const PMNormalMap& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMNormalMap::~PMNormalMap( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMNormalMap::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "NormalMap", Base::metaObject( ),
|
|
|
|
createNewNormalMap );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMNormalMap::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMNormalMap::description( ) const
|
|
|
|
{
|
|
|
|
return i18n( "normal map" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMSlopeMap::s_pMetaObject = 0;
|
|
|
|
PMObject* createNewSlopeMap( PMPart* part )
|
|
|
|
{
|
|
|
|
return new PMSlopeMap( part );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMSlopeMap::PMSlopeMap( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMSlopeMap::PMSlopeMap( const PMSlopeMap& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMSlopeMap::~PMSlopeMap( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMSlopeMap::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "SlopeMap", Base::metaObject( ),
|
|
|
|
createNewSlopeMap );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMSlopeMap::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMSlopeMap::description( ) const
|
|
|
|
{
|
|
|
|
return i18n( "slope map" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PMMetaObject* PMDensityMap::s_pMetaObject = 0;
|
|
|
|
PMObject* createNewDensityMap( PMPart* part )
|
|
|
|
{
|
|
|
|
return new PMDensityMap( part );
|
|
|
|
}
|
|
|
|
|
|
|
|
PMDensityMap::PMDensityMap( PMPart* part )
|
|
|
|
: Base( part )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMDensityMap::PMDensityMap( const PMDensityMap& m )
|
|
|
|
: Base( m )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMDensityMap::~PMDensityMap( )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
PMMetaObject* PMDensityMap::metaObject( ) const
|
|
|
|
{
|
|
|
|
if( !s_pMetaObject )
|
|
|
|
{
|
|
|
|
s_pMetaObject = new PMMetaObject( "DensityMap", Base::metaObject( ),
|
|
|
|
createNewDensityMap );
|
|
|
|
}
|
|
|
|
return s_pMetaObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PMDensityMap::cleanUp( ) const
|
|
|
|
{
|
|
|
|
if( s_pMetaObject )
|
|
|
|
{
|
|
|
|
delete s_pMetaObject;
|
|
|
|
s_pMetaObject = 0;
|
|
|
|
}
|
|
|
|
Base::cleanUp( );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString PMDensityMap::description( ) const
|
|
|
|
{
|
|
|
|
return i18n( "density map" );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|