Retour
Version Originale

./aip/1.8aipmod/include/IQ3Shader.h :


// Copyright (C) 2006-2011 Nikolaus Gebhardt / Thomas Alten

// This file is part of the "Irrlicht Engine".

// For conditions of distribution and use, see copyright notice in irrlicht.h


#ifndef __I_Q3_LEVEL_SHADER_H_INCLUDED__
#define __I_Q3_LEVEL_SHADER_H_INCLUDED__

#include "irrArray.h"
#include "fast_atof.h"
#include "IFileSystem.h"
#include "IVideoDriver.h"
#include "coreutil.h"

namespace irr
{
namespace scene
{
namespace quake3
{

	static core::stringc irrEmptyStringc("");

	//! Hold the different Mesh Types used for getMesh

	enum eQ3MeshIndex
	{
		E_Q3_MESH_GEOMETRY = 0,
		E_Q3_MESH_ITEMS,
		E_Q3_MESH_BILLBOARD,
		E_Q3_MESH_FOG,
		E_Q3_MESH_UNRESOLVED,
		E_Q3_MESH_SIZE
	};

	/*! used to customize Quake3 BSP Loader
	*/

	struct Q3LevelLoadParameter
	{
		Q3LevelLoadParameter ()
			:defaultLightMapMaterial ( video::EMT_LIGHTMAP_M4 ),
			defaultModulate ( video::EMFN_MODULATE_4X ),
			defaultFilter ( video::EMF_BILINEAR_FILTER ),
			patchTesselation ( 8 ),
			verbose ( 0 ),
			startTime ( 0 ), endTime ( 0 ),
			mergeShaderBuffer ( 1 ),
			cleanUnResolvedMeshes ( 1 ),
			loadAllShaders ( 0 ),
			loadSkyShader ( 0 ),
			alpharef ( 1 ),
			swapLump ( 0 ),
	#ifdef __BIG_ENDIAN__
			swapHeader ( 1 )
	#else
			swapHeader ( 0 )
	#endif
			{
				memcpy ( scriptDir, "scripts\x0", 8 );
			}

		video::E_MATERIAL_TYPE defaultLightMapMaterial;
		video::E_MODULATE_FUNC defaultModulate;
		video::E_MATERIAL_FLAG defaultFilter;
		s32 patchTesselation;
		s32 verbose;
		u32 startTime;
		u32 endTime;
		s32 mergeShaderBuffer;
		s32 cleanUnResolvedMeshes;
		s32 loadAllShaders;
		s32 loadSkyShader;
		s32 alpharef;
		s32 swapLump;
		s32 swapHeader;
		c8 scriptDir [ 64 ];
	};

	// some useful typedefs

	typedef core::array< core::stringc > tStringList;
	typedef core::array< video::ITexture* > tTexArray;

	// string helper.. TODO: move to generic files

	inline s16 isEqual ( const core::stringc &string, u32 &pos, const c8 *list[], u16 listSize )
	{
		const char * in = string.c_str () + pos;

		for ( u16 i = 0; i != listSize; ++i )
		{
			if (string.size() < pos)
				return -2;
			u32 len = (u32) strlen ( list[i] );
			if (string.size() < pos+len)
				continue;
			if ( in [len] != 0 && in [len] != ' ' )
				continue;
			if ( strncmp ( in, list[i], len ) )
				continue;

			pos += len + 1;
			return (s16) i;
		}
		return -2;
	}

	inline f32 getAsFloat ( const core::stringc &string, u32 &pos )
	{
		const char * in = string.c_str () + pos;

		f32 value = 0.f;
		pos += (u32) ( core::fast_atof_move ( in, value ) - in ) + 1;
		return value;
	}

	//! get a quake3 vector translated to irrlicht position (x,-z,y )

	inline core::vector3df getAsVector3df ( const core::stringc &string, u32 &pos )
	{
		core::vector3df v;

		v.X = getAsFloat ( string, pos );
		v.Z = getAsFloat ( string, pos );
		v.Y = getAsFloat ( string, pos );

		return v;
	}


	/*
		extract substrings
	*/
	inline void getAsStringList ( tStringList &list, s32 max, const core::stringc &string, u32 &startPos )
	{
		list.clear ();

		s32 finish = 0;
		s32 endPos;
		do
		{
			endPos = string.findNext ( ' ', startPos );
			if ( endPos == -1 )
			{
				finish = 1;
				endPos = string.size();
			}

			list.push_back ( string.subString ( startPos, endPos - startPos ) );
			startPos = endPos + 1;

			if ( list.size() >= (u32) max )
				finish = 1;

		} while ( !finish );

	}

	//! A blend function for a q3 shader.

	struct SBlendFunc
	{
		SBlendFunc ( video::E_MODULATE_FUNC mod )
			: type ( video::EMT_SOLID ), modulate ( mod ),
				param0( 0.f ),
			isTransparent ( 0 ) {}

		video::E_MATERIAL_TYPE type;
		video::E_MODULATE_FUNC modulate;

		f32 param0;
		u32 isTransparent;
	};

	// parses the content of Variable cull

	inline bool getCullingFunction ( const core::stringc &cull )
	{
		if ( cull.size() == 0 )
			return true;

		bool ret = true;
		static const c8 * funclist[] = { "none", "disable", "twosided" };

		u32 pos = 0;
		switch ( isEqual ( cull, pos, funclist, 3 ) )
		{
			case 0:
			case 1:
			case 2:
				ret = false;
				break;
		}
		return ret;
	}

	// parses the content of Variable depthfunc

	// return a z-test

	inline u8 getDepthFunction ( const core::stringc &string )
	{
		u8 ret = video::ECFN_LESSEQUAL;

		if ( string.size() == 0 )
			return ret;

		static const c8 * funclist[] = { "lequal","equal" };

		u32 pos = 0;
		switch ( isEqual ( string, pos, funclist, 2 ) )
		{
			case 0:
				ret = video::ECFN_LESSEQUAL;
			case 1:
				ret = video::ECFN_EQUAL;
				break;
		}
		return ret;
	}


	/*!
		parses the content of Variable blendfunc,alphafunc
		it also make a hint for rendering as transparent or solid node.

		we assume a typical quake scene would look like this..
		1) Big Static Mesh ( solid )
		2) static scene item ( may use transparency ) but rendered in the solid pass
		3) additional transparency item in the transparent pass

		it's not 100% accurate! it just empirical..
	*/
	inline static void getBlendFunc ( const core::stringc &string, SBlendFunc &blendfunc )
	{
		if ( string.size() == 0 )
			return;

		// maps to E_BLEND_FACTOR

		static const c8 * funclist[] =
		{
			"gl_zero",
			"gl_one",
			"gl_dst_color",
			"gl_one_minus_dst_color",
			"gl_src_color",
			"gl_one_minus_src_color",
			"gl_src_alpha",
			"gl_one_minus_src_alpha",
			"gl_dst_alpha",
			"gl_one_minus_dst_alpha",
			"gl_src_alpha_sat",

			"add",
			"filter",
			"blend",

			"ge128",
			"gt0",
		};


		u32 pos = 0;
		s32 srcFact = isEqual ( string, pos, funclist, 16 );

		if ( srcFact < 0 )
			return;

		u32 resolved = 0;
		s32 dstFact = isEqual ( string, pos, funclist, 16 );

		switch ( srcFact )
		{
			case video::EBF_ZERO:
				switch ( dstFact )
				{
					// gl_zero gl_src_color == gl_dst_color gl_zero

					case video::EBF_SRC_COLOR:
						blendfunc.type = video::EMT_ONETEXTURE_BLEND;
						blendfunc.param0 = video::pack_texureBlendFunc ( video::EBF_DST_COLOR, video::EBF_ZERO, blendfunc.modulate );
						blendfunc.isTransparent = 1;
						resolved = 1;
						break;
				} break;

			case video::EBF_ONE:
				switch ( dstFact )
				{
					// gl_one gl_zero

					case video::EBF_ZERO:
						blendfunc.type = video::EMT_SOLID;
						blendfunc.isTransparent = 0;
						resolved = 1;
						break;

					// gl_one gl_one

					case video::EBF_ONE:
						blendfunc.type = video::EMT_TRANSPARENT_ADD_COLOR;
						blendfunc.isTransparent = 1;
						resolved = 1;
						break;
				} break;

			case video::EBF_SRC_ALPHA:
				switch ( dstFact )
				{
					// gl_src_alpha gl_one_minus_src_alpha

					case video::EBF_ONE_MINUS_SRC_ALPHA:
						blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
						blendfunc.param0 = 1.f/255.f;
						blendfunc.isTransparent = 1;
						resolved = 1;
						break;
				} break;

			case 11:
				// add

				blendfunc.type = video::EMT_TRANSPARENT_ADD_COLOR;
				blendfunc.isTransparent = 1;
				resolved = 1;
				break;
			case 12:
				// filter = gl_dst_color gl_zero or gl_zero gl_src_color

				blendfunc.type = video::EMT_ONETEXTURE_BLEND;
				blendfunc.param0 = video::pack_texureBlendFunc ( video::EBF_DST_COLOR, video::EBF_ZERO, blendfunc.modulate );
				blendfunc.isTransparent = 1;
				resolved = 1;
				break;
			case 13:
				// blend = gl_src_alpha gl_one_minus_src_alpha

				blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
				blendfunc.param0 = 1.f/255.f;
				blendfunc.isTransparent = 1;
				resolved = 1;
				break;
			case 14:
				// alphafunc ge128

				blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
				blendfunc.param0 = 0.5f;
				blendfunc.isTransparent = 1;
				resolved = 1;
				break;
			case 15:
				// alphafunc gt0

				blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
				blendfunc.param0 = 1.f / 255.f;
				blendfunc.isTransparent = 1;
				resolved = 1;
				break;

		}

		// use the generic blender

		if ( 0 == resolved )
		{
			blendfunc.type = video::EMT_ONETEXTURE_BLEND;
			blendfunc.param0 = video::pack_texureBlendFunc (
					(video::E_BLEND_FACTOR) srcFact,
					(video::E_BLEND_FACTOR) dstFact,
					blendfunc.modulate);

			blendfunc.isTransparent = 1;
		}
	}

	// random noise [-1;1]

	struct Noiser
	{
		static f32 get ()
		{
			static u32 RandomSeed = 0x69666966;
			RandomSeed = (RandomSeed * 3631 + 1);

			f32 value = ( (f32) (RandomSeed & 0x7FFF ) * (1.0f / (f32)(0x7FFF >> 1) ) ) - 1.f;
			return value;
		}
	};

	enum eQ3ModifierFunction
	{
		TCMOD				= 0,
		DEFORMVERTEXES		= 1,
		RGBGEN				= 2,
		TCGEN				= 3,
		MAP					= 4,
		ALPHAGEN			= 5,

		FUNCTION2			= 0x10,
		SCROLL				= FUNCTION2 + 1,
		SCALE				= FUNCTION2 + 2,
		ROTATE				= FUNCTION2 + 3,
		STRETCH				= FUNCTION2 + 4,
		TURBULENCE			= FUNCTION2 + 5,
		WAVE				= FUNCTION2 + 6,

		IDENTITY			= FUNCTION2 + 7,
		VERTEX				= FUNCTION2 + 8,
		TEXTURE				= FUNCTION2 + 9,
		LIGHTMAP			= FUNCTION2 + 10,
		ENVIRONMENT			= FUNCTION2 + 11,
		DOLLAR_LIGHTMAP		= FUNCTION2 + 12,
		BULGE				= FUNCTION2 + 13,
		AUTOSPRITE			= FUNCTION2 + 14,
		AUTOSPRITE2			= FUNCTION2 + 15,
		TRANSFORM			= FUNCTION2 + 16,
		EXACTVERTEX			= FUNCTION2 + 17,
		CONSTANT			= FUNCTION2 + 18,
		LIGHTINGSPECULAR	= FUNCTION2 + 19,
		MOVE				= FUNCTION2 + 20,
		NORMAL				= FUNCTION2 + 21,
		IDENTITYLIGHTING	= FUNCTION2 + 22,

		WAVE_MODIFIER_FUNCTION	= 0x30,
		SINUS				= WAVE_MODIFIER_FUNCTION + 1,
		COSINUS				= WAVE_MODIFIER_FUNCTION + 2,
		SQUARE				= WAVE_MODIFIER_FUNCTION + 3,
		TRIANGLE			= WAVE_MODIFIER_FUNCTION + 4,
		SAWTOOTH			= WAVE_MODIFIER_FUNCTION + 5,
		SAWTOOTH_INVERSE	= WAVE_MODIFIER_FUNCTION + 6,
		NOISE				= WAVE_MODIFIER_FUNCTION + 7,


		UNKNOWN				= -2

	};

	struct SModifierFunction
	{
		SModifierFunction ()
			: masterfunc0 ( UNKNOWN ), masterfunc1( UNKNOWN ), func ( SINUS ),
			tcgen( TEXTURE ), rgbgen ( IDENTITY ), alphagen ( UNKNOWN ),
			base ( 0 ), amp ( 1 ), phase ( 0 ), frequency ( 1 ),
			wave ( 1 ),
			x ( 0 ), y ( 0 ), z( 0 ), count( 0 ) {}

		// "tcmod","deformvertexes","rgbgen", "tcgen"

		eQ3ModifierFunction masterfunc0;
		// depends

		eQ3ModifierFunction masterfunc1;
		// depends

		eQ3ModifierFunction func;

		eQ3ModifierFunction tcgen;
		eQ3ModifierFunction rgbgen;
		eQ3ModifierFunction alphagen;

		union
		{
			f32 base;
			f32 bulgewidth;
		};

		union
		{
			f32 amp;
			f32 bulgeheight;
		};

		f32 phase;

		union
		{
			f32 frequency;
			f32 bulgespeed;
		};

		union
		{
			f32 wave;
			f32 div;
		};

		f32 x;
		f32 y;
		f32 z;
		u32 count;

		f32 evaluate ( f32 dt ) const
		{
			// phase in 0 and 1..

			f32 x = core::fract( (dt + phase ) * frequency );
			f32 y = 0.f;

			switch ( func )
			{
				case SINUS:
					y = sinf ( x * core::PI * 2.f );
					break;
				case COSINUS:
					y = cosf ( x * core::PI * 2.f );
					break;
				case SQUARE:
					y = x < 0.5f ? 1.f : -1.f;
					break;
				case TRIANGLE:
					y = x < 0.5f ? ( 4.f * x ) - 1.f : ( -4.f * x ) + 3.f;
					break;
				case SAWTOOTH:
					y = x;
					break;
				case SAWTOOTH_INVERSE:
					y = 1.f - x;
					break;
				case NOISE:
					y = Noiser::get();
					break;
				default:
					break;
			}

			return base + ( y * amp );
		}


	};

	inline core::vector3df getMD3Normal ( u32 i, u32 j )
	{
		const f32 lng = i * 2.0f * core::PI / 255.0f;
		const f32 lat = j * 2.0f * core::PI / 255.0f;
		return core::vector3df(cosf ( lat ) * sinf ( lng ),
				sinf ( lat ) * sinf ( lng ),
				cosf ( lng ));
	}

	//

	inline void getModifierFunc ( SModifierFunction& fill, const core::stringc &string, u32 &pos )
	{
		if ( string.size() == 0 )
			return;

		static const c8 * funclist[] =
		{
			"sin","cos","square",
			"triangle", "sawtooth","inversesawtooth", "noise"
		};

		fill.func = (eQ3ModifierFunction) isEqual ( string,pos, funclist,7 );
		fill.func = fill.func == UNKNOWN ? SINUS : (eQ3ModifierFunction) ((u32) fill.func + WAVE_MODIFIER_FUNCTION + 1);

		fill.base = getAsFloat ( string, pos );
		fill.amp = getAsFloat ( string, pos );
		fill.phase = getAsFloat ( string, pos );
		fill.frequency = getAsFloat ( string, pos );
	}


	// name = "a b c .."

	struct SVariable
	{
		core::stringc name;
		core::stringc content;

		SVariable ( const c8 * n, const c8 *c = 0 ) : name ( n ), content (c) {}
		virtual ~SVariable () {}

		void clear ()
		{
			name = "";
			content = "";
		}

		s32 isValid () const
		{
			return name.size();
		}

		bool operator == ( const SVariable &other ) const
		{
			return 0 == strcmp ( name.c_str(), other.name.c_str () );
		}

		bool operator < ( const SVariable &other ) const
		{
			return 0 > strcmp ( name.c_str(), other.name.c_str () );
		}

	};


	// string database. "a" = "Hello", "b" = "1234.6"

	struct SVarGroup
	{
		SVarGroup () { Variable.setAllocStrategy ( core::ALLOC_STRATEGY_SAFE ); }
		virtual ~SVarGroup () {}

		u32 isDefined ( const c8 * name, const c8 * content = 0 ) const
		{
			for ( u32 i = 0; i != Variable.size (); ++i )
			{
				if ( 0 == strcmp ( Variable[i].name.c_str(), name ) &&
					(  0 == content || strstr ( Variable[i].content.c_str(), content ) )
					)
				{
					return i + 1;
				}
			}
			return 0;
		}

		// searches for Variable name and returns is content

		// if Variable is not found a reference to an Empty String is returned

		const core::stringc &get( const c8 * name ) const
		{
			SVariable search ( name );
			s32 index = Variable.linear_search ( search );
			if ( index < 0 )
				return irrEmptyStringc;

			return Variable [ index ].content;
		}

		// set the Variable name

		void set ( const c8 * name, const c8 * content = 0 )
		{
			u32 index = isDefined ( name, 0 );
			if ( 0 == index )
			{
				Variable.push_back ( SVariable ( name, content ) );
			}
			else
			{
				Variable [ index ].content = content;
			}
		}


		core::array < SVariable > Variable;
	};

	//! holding a group a variable

	struct SVarGroupList: public IReferenceCounted
	{
		SVarGroupList ()
		{
			VariableGroup.setAllocStrategy ( core::ALLOC_STRATEGY_SAFE );
		}
		virtual ~SVarGroupList () {}

		core::array < SVarGroup > VariableGroup;
	};


	//! A Parsed Shader Holding Variables ordered in Groups

	struct IShader
	{
		IShader ()
			: ID ( 0 ), VarGroup ( 0 )  {}
		virtual ~IShader () {}

		void operator = (const IShader &other )
		{
			ID = other.ID;
			VarGroup = other.VarGroup;
			name = other.name;
		}

		bool operator == (const IShader &other ) const
		{
			return 0 == strcmp ( name.c_str(), other.name.c_str () );
			//return name == other.name;

		}

		bool operator < (const IShader &other ) const
		{
			return strcmp ( name.c_str(), other.name.c_str () ) < 0;
			//return name < other.name;

		}

		u32 getGroupSize () const
		{
			if ( 0 == VarGroup )
				return 0;
			return VarGroup->VariableGroup.size ();
		}

		const SVarGroup * getGroup ( u32 stage ) const
		{
			if ( 0 == VarGroup || stage >= VarGroup->VariableGroup.size () )
				return 0;

			return &VarGroup->VariableGroup [ stage ];
		}

		// id

		s32 ID;
		SVarGroupList *VarGroup; // reference


		// Shader: shader name ( also first variable in first Vargroup )

		// Entity: classname ( variable in Group(1) )

		core::stringc name;
	};

	typedef IShader IEntity;

	typedef core::array < IEntity > tQ3EntityList;

	/*
		dump shader like original layout, regardless of internal data holding
		no recursive folding..
	*/
	inline void dumpVarGroup ( core::stringc &dest, const SVarGroup * group, s32 stack )
	{
		core::stringc buf;
		s32 i;


		if ( stack > 0 )
		{
			buf = "";
			for ( i = 0; i < stack - 1; ++i )
				buf += '\t';

			buf += "{\n";
			dest.append ( buf );
		}

		for ( u32 g = 0; g != group->Variable.size(); ++g )
		{
			buf = "";
			for ( i = 0; i < stack; ++i )
				buf += '\t';

			buf += group->Variable[g].name;
			buf += " ";
			buf += group->Variable[g].content;
			buf += "\n";
			dest.append ( buf );
		}

		if ( stack > 1 )
		{
			buf = "";
			for ( i = 0; i < stack - 1; ++i )
				buf += '\t';

			buf += "}\n";
			dest.append ( buf );
		}

	}

	/*!
		dump a Shader or an Entity
	*/
	inline core::stringc & dumpShader ( core::stringc &dest, const IShader * shader, bool entity = false )
	{
		if ( 0 == shader )
			return dest;

		const SVarGroup * group;

		const u32 size = shader->VarGroup->VariableGroup.size ();
		for ( u32 i = 0; i != size; ++i )
		{
			group = &shader->VarGroup->VariableGroup[ i ];
			dumpVarGroup ( dest, group, core::clamp( (int)i, 0, 2 ) );
		}

		if ( !entity )
		{
			if ( size <= 1 )
			{
				dest.append ( "{\n" );
			}
			dest.append ( "}\n" );
		}
		return dest;
	}


	/*
		quake3 doesn't care much about tga & jpg
		load one or multiple files stored in name started at startPos to the texture array textures
		if texture is not loaded 0 will be added ( to find missing textures easier)
	*/
	inline void getTextures(tTexArray &textures,
				const core::stringc &name, u32 &startPos,
				io::IFileSystem *fileSystem,
				video::IVideoDriver* driver)
	{
		static const char * extension[2] =
		{
			".jpg",
			".tga"
		};

		tStringList stringList;
		getAsStringList ( stringList, -1, name, startPos );

		textures.clear();

		io::path loadFile;
		for ( u32 i = 0; i!= stringList.size (); ++i )
		{
			video::ITexture* texture = 0;
			for ( u32 g = 0; g != 2 ; ++g )
			{
				core::cutFilenameExtension ( loadFile, stringList[i] );

				if ( loadFile == "$whiteimage" )
				{
					texture = driver->getTexture( "$whiteimage" );
					if ( 0 == texture )
					{
						core::dimension2du s ( 2, 2 );
						u32 image[4] = { 0xFFFFFFFF, 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF };
						video::IImage* w = driver->createImageFromData ( video::ECF_A8R8G8B8, s,&image );
						texture = driver->addTexture( "$whiteimage", w );
						w->drop ();
					}

				}
				else
				if ( loadFile == "$redimage" )
				{
					texture = driver->getTexture( "$redimage" );
					if ( 0 == texture )
					{
						core::dimension2du s ( 2, 2 );
						u32 image[4] = { 0xFFFF0000, 0xFFFF0000,0xFFFF0000,0xFFFF0000 };
						video::IImage* w = driver->createImageFromData ( video::ECF_A8R8G8B8, s,&image );
						texture = driver->addTexture( "$redimage", w );
						w->drop ();
					}
				}
				else
				if ( loadFile == "$blueimage" )
				{
					texture = driver->getTexture( "$blueimage" );
					if ( 0 == texture )
					{
						core::dimension2du s ( 2, 2 );
						u32 image[4] = { 0xFF0000FF, 0xFF0000FF,0xFF0000FF,0xFF0000FF };
						video::IImage* w = driver->createImageFromData ( video::ECF_A8R8G8B8, s,&image );
						texture = driver->addTexture( "$blueimage", w );
						w->drop ();
					}
				}
				else
				if ( loadFile == "$checkerimage" )
				{
					texture = driver->getTexture( "$checkerimage" );
					if ( 0 == texture )
					{
						core::dimension2du s ( 2, 2 );
						u32 image[4] = { 0xFFFFFFFF, 0xFF000000,0xFF000000,0xFFFFFFFF };
						video::IImage* w = driver->createImageFromData ( video::ECF_A8R8G8B8, s,&image );
						texture = driver->addTexture( "$checkerimage", w );
						w->drop ();
					}
				}
				else
				if ( loadFile == "$lightmap" )
				{
					texture = 0;
				}
				else
				{
					loadFile.append ( extension[g] );
				}

				if ( fileSystem->existFile ( loadFile ) )
				{
					texture = driver->getTexture( loadFile );
					if ( texture )
						break;
					texture = 0;
				}
			}
			// take 0 Texture

			textures.push_back(texture);
		}
	}


	//! Manages various Quake3 Shader Styles

	class IShaderManager : public IReferenceCounted
	{
	};

} // end namespace quake3

} // end namespace scene

} // end namespace irr


#endif

Options Liens officiels Caractéristiques Statistiques Communauté
Corrections
irrlicht
irrklang
irredit
irrxml
xhtml 1.0
css 2.1
Propulsé par FluxBB
Traduit par FluxBB.fr
Analysé par
880 membres
1424 sujets
11113 messages
Dernier membre inscrit: mandrifidy
19 invités en ligne
Aucun membre connecté
RSS Feed