module vs.character.oku;



private import std.math;
private import std.c.stdlib;
private import gedo.audio.sound;
private import gedo.video.ggl;
private import gedo.video.color;
private import gedo.video.texture;
private import gedo.video.polyline;
private import gedo.video.particle;
private import gedo.video.trajectory;
private import gedo.video.staticmesh;
private import gedo.video.skinnedmesh;
private import gedo.video.alphameshlist;
private import gedo.input.joystick;
private import gedo.system.task;
private import vs.action.task;
private import vs.action.bullet;
private import vs.action.button;
private import vs.action.character;
private import vs.bullet.fist;
private import vs.effect.program;



private enum Motion
{
	//aiw begin ../../../lib/vs/oku/oku.gsa
	CHARGEHIT0,
	CHARGEHIT1,
	CHARGESHOT,
	COUNTER0,
	COUNTER1,
	DASH1,
	DASH2,
	DOWN,
	FALL0,
	FALL1,
	FOLLOW0,
	FOLLOW1,
	GUARD0,
	GUARD1,
	GUARD2,
	HIT0,
	HIT1,
	HIT2,
	HIT3,
	HOMINGSHOT,
	JUMP,
	KNOCKBACK,
	KO,
	LAND,
	RISE0,
	RISE1,
	RUN0,
	RUN1,
	RUN2,
	SINGLESHOT,
	SPECIAL0,
	SPECIAL1,
	SPECIAL2,
	SPECIALFOLLOW0,
	SPECIALFOLLOW1,
	SPECIALHIT0,
	SPECIALHIT1,
	SPECIALSHOT,
	SPELL,
	STAND,
	//aiw end
	INVALID
}



private enum TextureID
{
	DEFAULT,
	ATTACK,
	DAMAGE,
	TRAJECTORY,
	BEAM,
	HITBEAM,
	HOMINGTRAJECTORY,
	PARTICLE,
	CHARGETRAJECTORY,
	FLARE,
}



private enum SoundID
{
	SPECIAL,
	CHARGEHIT,
	FEATHERSHOT,
	SPELLCHARGE,
	SPELLSHOT,
}



private enum FistID
{
	COUNTER0,
	COUNTER1,
	HIT0,
	HIT1,
	SPECIALHIT0,
	SPECIALHIT1,
	CHARGEHIT0,
	CHARGEHIT1A,
	CHARGEHIT1B,
}



private enum TrajectoryID
{
	//tiw begin ../../../lib/vs/oku/oku.trj
	TRAJECTORY_HIT0_3_7,
	TRAJECTORY_CHARGEHIT0_2_7,
	TRAJECTORY_CHARGEHIT1_3_110,
	TRAJECTORY_SPECIALHIT0_1_11,
	TRAJECTORY_SPECIALHIT1_5_9,
	TRAJECTORY_HIT1_6_10,
	//tiw end
}



public class OkuFactory : CharacterFactory
{
public:
	this()
	{
		_textures[TextureID.DEFAULT] = Texture("$LIB/oku/oku_n.png");
		_textures[TextureID.ATTACK ] = Texture("$LIB/oku/oku_a.png");
		_textures[TextureID.DAMAGE ] = Texture("$LIB/oku/oku_d.png");
		_textures[TextureID.BEAM   ] = Texture("$LIB/oku/beam.png");
		_textures[TextureID.PARTICLE] = Texture("$LIB/oku/particle.png");
		_textures[TextureID.FLARE] = Texture("$LIB/oku/flare.png");

		_body = SkinnedMesh("$LIB/oku/oku.gsa");
		_wing = SkinnedMesh("$LIB/oku/beamwing.gsa");
		_sword = SkinnedMesh("$LIB/oku/sword.gsa");
		_beamsword = SkinnedMesh("$LIB/oku/beamsword.gsa");
		_shield = StaticMesh("$LIB/oku/shield.gsa");
		_feather = StaticMesh("$LIB/oku/feather.gsa");

		_trajectories = loadTrajectories("$LIB/oku/oku.trj");

		Color[256] pixels;
		for(auto x = 0; x < 16; ++x)
		{
			for(auto y = 0; y < 16; ++y)
			{
				auto k = y / 16.0;
				k = 127.0*(1.0 - k*k) + 128.0;

				auto l = (15.0 - x)/16.0;
				l = 1.0 - l*l;

				pixels[y*16 + x] = rgba(255, 255, 255, cast(int)(l * k));
			}
		}

		_textures[TextureID.TRAJECTORY] = new Texture(pixels, 16, 16);

		for(auto y = 0; y < 16; ++y)
		{
			auto l = 8 + ((y&2) - 1)*(rand()&3);

			for(auto x = 0; x < 16; ++x)
			{
				auto k = 0.0;

				if(x < l)
				{
					k = (x + 1.0) / l * 128.0;
				}
				else
				{
					k = 224.0 + 32.0*(x - l)/(16.0 - l);
				}
				pixels[y*16 + x] = rgba(64, 255, 64, cast(int)k);
			}
		}

		_textures[TextureID.CHARGETRAJECTORY] = new Texture(pixels, 16, 16);

		for(auto x = 0; x < 16; ++x)
		{
			int maxy = -(x - 8)*(x - 8)/8 + 16;

			auto maxa = 255.0;
			if(x & 2)
			{
				maxa = 64.0;
				maxy *= 0.5;
			}

			for(auto y = 0; y < 16; ++y)
			{
				int alpha = 0;
				if(y < maxy)
				{
					auto k = cast(double)y / maxy;
					k = maxa*(1.0 - k*k);

					alpha = cast(int)k;
				}
				pixels[(15-y)*16 + x] = rgba(0, 0, 128, alpha);
			}
		}

		_textures[TextureID.HOMINGTRAJECTORY] = new Texture(pixels, 16, 16);

		pixels[0] = WHITE;
		pixels[1] = rgba(160, 224, 255, 255);
		pixels[2] = rgba( 64, 192, 255, 128);
		pixels[3] = setA(pixels[2], 0);

		EffectProgram[2] programs;

		programs[0] = new Flash5(8.0, GGL_BlendMode.MIX, new Texture(pixels[0..4], 4, 1));
		programs[1] = new Ring(8.0, GGL_BlendMode.MIX, rgba(64, 192, 255, 255));

		_flashMain = new CompositeEffect(programs[0..2]);

		pixels[0] = pixels[7] = rgba(128, 255, 128, 0);
		pixels[1] = pixels[6] = rgba(128, 255, 128, 255);
		pixels[2] = pixels[5] = WHITE;
		pixels[3] = pixels[4] = WHITE;
		_textures[TextureID.HITBEAM] = new Texture(pixels[0..8], 8, 1);

		_hitBeamCircle = StaticMesh.createCircle(1.0, new Texture(pixels[4..8], 4, 1));

		pixels[0] = WHITE;
		pixels[1] = rgba(192, 255, 192, 255);
		pixels[2] = rgba(128, 255, 128, 128);
		pixels[3] = setA(pixels[2], 0);

		programs[0] = new Flash5(32.0, GGL_BlendMode.MIX, new Texture(pixels[0..4], 4, 1));
		programs[1] = new Ring(32.0, GGL_BlendMode.MIX, rgba(128, 255, 128, 255));

		_flashHit = new CompositeEffect(programs[0..2]);

		programs[0] = new Ray(2.0, GGL_BlendMode.ADD, WHITE);
		programs[1] = new Ring(8.0, GGL_BlendMode.ADD, WHITE);

		_flashFeather = new CompositeEffect(programs);

		pixels[0] = pixels[1] = WHITE;
		pixels[2] = rgba(64, 128, 255, 255);
		pixels[3] = setA(pixels[2], 0);

		_atomicCircle = StaticMesh.createCircle(1.0, new Texture(pixels[0..4], 4, 1));

		pixels[0] = WHITE;
		_atomicSphere = createSphere(1.0, new Texture(pixels[0..1], 1, 1));
		_atomicPlane  = createMirrorPlane(Texture("$LIB/oku/wave.png"));
		_atomicFlare  = createMirrorPlane(_textures[TextureID.FLARE]);

		pixels[1] = WHITE;
		pixels[0] = setA(pixels[1], 0);
		_atomicRing   = StaticMesh.createCircle(1.0, new Texture(pixels[0..2], 2, 1));
	}


	override Character create(CharacterSystem sys)
	{
		_sounds[SoundID.SPECIAL] = Sound("$LIB/oku/special.ogg");
		_sounds[SoundID.FEATHERSHOT] = Sound("$LIB/oku/feathershot.ogg");
		_sounds[SoundID.CHARGEHIT] = Sound("$LIB/oku/chargehit.ogg");
		_sounds[SoundID.SPELLCHARGE] = Sound("$LIB/oku/spellcharge.ogg");
		_sounds[SoundID.SPELLSHOT] = Sound("$LIB/oku/spellshot.ogg");

		return new Oku(sys);
	}


private:
	Texture[TextureID.max+1] _textures;
	Sound[SoundID.max+1]     _sounds;
	EffectProgram            _flashMain;
	EffectProgram            _flashHit;
	EffectProgram            _flashFeather;
	Trajectory[]             _trajectories;
	SkinnedMesh              _body;
	SkinnedMesh              _sword;
	SkinnedMesh              _beamsword;
	SkinnedMesh              _wing;
	StaticMesh               _shield;
	StaticMesh               _feather;
	StaticMesh               _atomicCircle;
	StaticMesh               _atomicSphere;
	StaticMesh               _atomicPlane;
	StaticMesh               _atomicFlare;
	StaticMesh               _atomicRing;
	StaticMesh               _hitBeamCircle;



class Oku : Character
{
public:
	static this()
	{
		getDefaultAccelerations(_accelerations);

		_accelerations[Movement.RUN][1]     *= 0.625;
		_accelerations[Movement.FALL][1]    *= 0.625;
		_accelerations[Movement.STEP][0]    *= 1.375;
		_accelerations[Movement.STEP][1]    *= 1.375;
		_accelerations[Movement.FOLLOW][0]  *= 1.375;
		_accelerations[Movement.FOLLOW][1]  *= 1.375;
	}


	this(CharacterSystem sys)
	{
		super(_body, sys);

		_particleEmitter = new ParticleEmitter(_textures[TextureID.PARTICLE], 4.0, GGL_BlendMode.ADD, 64, 64, [0.0f, 0.0f, 1.0/64.0]);

		auto seCut = sys.sharedSound(SharedSoundID.CUT);
		auto veHit = sys.sharedEffect(SharedEffectID.HITMARK);
		_fists[FistID.HIT0] = new Fist(new Power(10, 1.0), seCut, veHit, _timer);
		_fists[FistID.HIT1] = new Fist(new Power(10, 1.9), seCut, veHit, _timer);
		_fists[FistID.COUNTER0] = new Fist(new Power(8, 1.0), sys.sharedSound(SharedSoundID.HITM), veHit, _timer);
		_fists[FistID.COUNTER1] = new Fist(new Power(12, 1.9), sys.sharedSound(SharedSoundID.HITH), veHit, _timer);
		_fists[FistID.SPECIALHIT0] = new Fist(new Power(12, 0.0), seCut, veHit, _timer, f2t(30));
		_fists[FistID.SPECIALHIT1] = new Fist(new Power(24, 2.5, [0.0f, -1.0f, 2.0f]), sys.sharedSound(SharedSoundID.FINISH), veHit, _timer, f2t(10));
		_fists[FistID.CHARGEHIT0] = new Fist(new Power(20, 0.5), seCut, veHit, _timer);
		_fists[FistID.CHARGEHIT0].onCollideOwner = &startRush;
		_fists[FistID.CHARGEHIT1A] = new Fist(new Power(1, 0.5), seCut, veHit, _timer);
		_fists[FistID.CHARGEHIT1B] = new Fist(new Power(32, 2.5), sys.sharedSound(SharedSoundID.FINISH), veHit, _timer);

		auto power = new Power(16, 1.0);

		foreach(inout b; _singleBeams)
		{
			b = new Beam(power);
		}

		power = new Power(10, 2.5);

		foreach(inout b; _hitBeams)
		{
			b = new HitBeam(power);
		}

		power = new Power(2, 1.0);
		foreach(i, inout b; _homingFeathers)
		{
			Vector offset;

			auto angle = PI * i / (_homingFeathers.length - 1);
			auto l     = 2.0 + (i & 1);

			offset[0] = l * cos(angle);
			offset[1] = 0.0;
			offset[2] = l * sin(angle);

			b = new HomingFeather(power, offset);
		}

		power = new Power(5, 1.5);

		foreach(inout b; _chargeBeams)
		{
			b = new ChargeBeam(power);
		}

		power = new Power(7, 1.0);
		_spell = new AtomicBomb(power);
	}


	override float[] acceleration(Movement m)
	{
		return _accelerations[m];
	}


	override void onDraw()
	{
		uint t;
		uint i;

		_timer.get(i, t);

		Texture tex = null;
		if(_counts[Count.SAFETY] & 2)
		{
			tex = Texture.white;
		}
		else
		{
			switch(state)
			{
			case State.KNOCKBACK:
			case State.DOWN:
			case State.KO:
				tex = _textures[TextureID.DAMAGE];
				break;

			case State.SINGLESHOT:
			case State.CHARGESHOT:
			case State.HOMINGSHOT:
			case State.CHARGEHIT:
			case State.HIT:
			case State.SPECIAL:
			case State.SPELL:
				tex = _textures[TextureID.ATTACK];
				break;

			default:
				tex = _textures[TextureID.DEFAULT];
				break;
			}
		}

		_body.draw(i, t, tex);

		switch(i)
		{
		case Motion.HIT0:
		case Motion.HIT1:
		case Motion.FOLLOW0:
		case Motion.FOLLOW1:
		case Motion.SPECIALFOLLOW1:
		case Motion.SPECIALHIT0:
		case Motion.SPECIALHIT1:
		case Motion.CHARGEHIT0:
		case Motion.CHARGEHIT1:
			_sword.draw(i, t);
			if(auto am = system.alphaMeshList().get())
			{
				am.set(_beamsword, position.current, i, t, GGL_BlendMode.MIX, direction);
			}
			break;

		default:
			break;
		}

		switch(i)
		{
		case Motion.RISE0:
		case Motion.DASH1:
		case Motion.SPECIAL0:
		case Motion.SPECIAL1:
		case Motion.SPECIALFOLLOW0:
		case Motion.SPECIALFOLLOW1:
		case Motion.SPECIALHIT0:
		case Motion.SPECIALHIT1:
		case Motion.SPELL:
			if(auto am = system.alphaMeshList().get())
			{
				am.set(_wing, position.current, i, t, GGL_BlendMode.MIX, direction);
			}
			break;

		case Motion.GUARD1:
			if(auto am = system.alphaMeshList().get())
			{
				am.set(_shield, position.current, GGL_BlendMode.MIX, direction);
			}
			break;

		case Motion.COUNTER0:
			if(t <= 4)
			{
				if(auto am = system.alphaMeshList().get())
				{
					am.set(_shield, position.current, GGL_BlendMode.MIX, direction);
				}
			}
			break;

		default:
			break;
		}

		Trajectory trj = null;
		tex = _textures[TextureID.TRAJECTORY];
		auto blend = GGL_BlendMode.MIX;
		switch(i)
		{
		case Motion.HIT0:
			trj = _trajectories[TrajectoryID.TRAJECTORY_HIT0_3_7];
			break;

		case Motion.HIT1:
			trj = _trajectories[TrajectoryID.TRAJECTORY_HIT1_6_10];
			break;

		case Motion.SPECIALHIT0:
			trj = _trajectories[TrajectoryID.TRAJECTORY_SPECIALHIT0_1_11];
			break;

		case Motion.SPECIALHIT1:
			trj = _trajectories[TrajectoryID.TRAJECTORY_SPECIALHIT1_5_9];
			break;

		case Motion.CHARGEHIT0:
			trj = _trajectories[TrajectoryID.TRAJECTORY_CHARGEHIT0_2_7];
			break;

		case Motion.CHARGEHIT1:
			trj = _trajectories[TrajectoryID.TRAJECTORY_CHARGEHIT1_3_110];
			tex = _textures[TextureID.CHARGETRAJECTORY];
			blend = GGL_BlendMode.MIX;
			break;

		case Motion.SPELL:
			if(t >= f2t(12) && t < f2t(69))
			{
				if(auto am = system.alphaMeshList().get())
				{
					am.set(position.current, &drawSpell);
				}
			}
			break;

		default:
			break;
		}
		if(trj)
		{
			auto wt = tex.width;
			auto ht = tex.height;

			auto verts = trj.getVertices(t, _trjVertices, wt, ht);

			if(verts.length >= 4)
			{
				if(auto am = system.alphaMeshList().get())
				{
					am.set(verts, tex, position.current, direction, blend);
				}
			}
		}

		if(_particleEmitter.isRunning)
		{
			if(auto am = system.alphaMeshList().get())
			{
				am.set(position.current, &_particleEmitter.draw);
			}
		}
	}


	override void onUpdate(float axisLength, Vector axisDirection, Button[] buttons)
	{
		uint i, t;

		_timer.get(i, t);

		switch(i)
		{
		case Motion.STAND:
			if(state != State.STAND)
			{
				changeState(State.STAND);
			}
			break;

		case Motion.DASH1:
		case Motion.RISE0:
			if(t&1)
			{
				emitParticle();
			}
			break;

		case Motion.COUNTER0:
			if(t < f2t(7))
			{
				follow();
			}
			else if(t == f2t(7))
			{
				velocity[] = 0.0;
				shotFist(_fists[FistID.COUNTER0]);
			}
			else if(t >= f2t(11) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.COUNTER1, Motion.STAND);
			}
			break;

		case Motion.COUNTER1:
			if(t < f2t(7))
			{
				follow();
			}
			else if(t == f2t(7))
			{
				direction.toVector(0.0, -2.0, 0.0, velocity);
				shotFist(_fists[FistID.COUNTER1]);
				if(auto effectTask = _system.getEffectTask())
				{
					direction.toVector(5.0, 0.0, 0.0, effectTask.position.current);

					.gedo.math.vector.add(effectTask.position.current, position.current);

					effectTask.direction.copyFrom(direction);

					effectTask.start(_flashHit);
				}
			}
			else if(t >= f2t(11) && _fists[FistID.COUNTER1].isCollided && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT2, Motion.STAND);
			}
			break;

		case Motion.HIT0:
			if(t <= f2t(6))
			{
				follow();
				if(t == f2t(6))
				{
					shotFist(_fists[FistID.HIT0]);
				}
			}
			else if(t >= f2t(10) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT1, Motion.STAND);
			}
			else
			{
				.gedo.math.vector.lerp(velocity, [0.0f, 0.0f, 0.0f], _accelerations[Movement.FOLLOW][1]/8.0);
			}
			break;

		case Motion.HIT1:
			if(t < f2t(9))
			{
				follow();
			}
			else if(t == f2t(9))
			{
				direction.toVector(0.0, -1.0, 2.0, velocity);
				shotFist(_fists[FistID.HIT1]);
			}
			else if(t >= f2t(15) && _fists[FistID.HIT1].isCollided && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT2, Motion.STAND);
			}
			else
			{
				brake();
			}
			break;

		case Motion.HIT2:
			if(t < f2t(9))
			{
				turnToTarget();
			}
			else if(t == f2t(9))
			{
				turnToTarget();
				if(auto b = cast(Bullet)findFreeTask(_hitBeams))
				{
					Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
					shot(b);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.previous[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashHit);
					}
				}
				direction.toVector(0.0, -2.0, 0.0, velocity);
			}
			else if(t >= f2t(17) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.HIT3, Motion.STAND);
			}
			else
			{
				brake();
			}
			break;

		case Motion.HIT3:
			if(t < f2t(7))
			{
				turnToTarget();
			}
			else if(t == f2t(7))
			{
				turnToTarget();
				if(auto b = cast(Bullet)findFreeTask(_hitBeams))
				{
					Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
					shot(b);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.previous[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashHit);
					}
				}
				direction.toVector(0.0, -2.0, 0.0, velocity);
			}
			else
			{
				brake();
			}
			break;

		case Motion.SINGLESHOT:
			if(t == f2t(3))
			{
				turnToTarget();
				if(auto b = cast(Bullet)findFreeTask(_singleBeams))
				{
					Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
					shot(b);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashMain);
					}
				}
			}
			break;

		case Motion.CHARGESHOT:
			switch(t)
			{
			case f2t(10):
			case f2t(13):
			case f2t(16):
			case f2t(19):
			case f2t(22):
			case f2t(25):
				if(auto b = cast(Bullet)findFreeTask(_chargeBeams))
				{
					Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
					shot(b);

					auto angle = (t - f2t(10))/36.0 * 2.0 * PI;

					Vector a;
					b.direction.toVector(cos(angle), 1.0f, sin(angle)+0.75, a);
					.gedo.math.vector.normalize(a);
					b.direction.homing(a, PI, true);

					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = b.position.current[];

						effectTask.direction.copyFrom(b.direction);

						effectTask.start(_flashMain);
					}
				}
				break;

			default:
				break;
			}
			break;

		case Motion.HOMINGSHOT:
			switch(t)
			{
			case f2t(11):
				for(auto a = 0; a < _homingFeathers.length; ++a)
				{
					if(auto feather = cast(Bullet)findFreeTask(_homingFeathers))
					{
						shot(feather, [0.0f, -3.0f, 0.0f]);
					}
				}
				if(auto effectTask = _system.getEffectTask())
				{
					direction.toVector(0.0f, -3.0f, 0.0f, effectTask.position.current);

					.gedo.math.vector.add(effectTask.position.current, position.current);

					effectTask.direction.copyFrom(direction);

					effectTask.start(_flashFeather);
				}

				Source().play(_sounds[SoundID.FEATHERSHOT]);
				break;

			default:
				break;
			}
			break;

		case Motion.SPECIAL0:
			velocity[0] = velocity[1] = 0.0;
			velocity[2] = t * 1.0/24.0;
			emitParticle();
			break;

		case Motion.SPECIAL1:
			if(_command.isReceived(Command.SPECIAL))
			{
				changeState(State.RECOVER);
			}
			else if(_command.isReceived(Command.HIT))
			{
				_timer.set(Motion.SPECIALFOLLOW0, Motion.SPECIALFOLLOW1);
				_counts[Count.ACTION] = 60;
			}
			else if(_command.isReceived(Command.SINGLESHOT))
			{
				_timer.set(Motion.SPECIALSHOT, Motion.STAND);
			}
			else
			{
				direction.homing(axisDirection, axisLength * PI / 256.0, true);

				if(buttons[ActionButton.DEFEND].isPushed)
				{
					direction.homing(Direction.UP, PI / 128.0, true);
				}
				else
				{
					direction.homing([0.0f, 0.0f, -1.0f], PI / 256.0, true);
				}

				Vector a;
				direction.getFront(a);
				.gedo.math.vector.multiply(a, _accelerations[Movement.STEP][1]);
				.gedo.math.vector.lerp(velocity, a, _accelerations[Movement.RUN][0]*2.0);

				if(t&1)
				{
					emitParticle();
				}
			}
			break;

		case Motion.SPECIALSHOT:
			if(t <= f2t(10))
			{
				turnToTarget();
				if(t == f2t(10))
				{
					if(auto b = cast(Bullet)findFreeTask(_singleBeams))
					{
						Source().play(_system.sharedSound(SharedSoundID.SHOTBEAM));
						shot(b);
						if(auto effectTask = _system.getEffectTask())
						{
							effectTask.position.current[] = b.position.current[];

							effectTask.direction.copyFrom(b.direction);

							effectTask.start(_flashMain);
						}
					}
				}
			}
			brake();
			break;

		case Motion.SPECIALFOLLOW1:
			if(!_fists[FistID.SPECIALHIT0].parent)
			{
				shotFist(_fists[FistID.SPECIALHIT0], [-5.0f, 0.0f, 0.0f]);
			}
			else if(through(5.0, -1.0) || _fists[FistID.SPECIALHIT0].isCollided || _counts[Count.ACTION] == 0)
			{
				_timer.set(Motion.SPECIALHIT0, Motion.STAND);
			}
			break;

		case Motion.SPECIALHIT0:
			if(t == f2t(3))
			{
				.gedo.math.vector.multiply(velocity, 2.0);
			}
			else if(t >= f2t(10) && _command.isReceived(Command.HIT))
			{
				_timer.set(Motion.SPECIALHIT1, Motion.STAND);
				through(5.0, -1.0);
				shotFist(_fists[FistID.SPECIALHIT1], [-5.0f, 0.0f, 0.0f]);
			}
			else
			{
				direction.toVector(0.0, _accelerations[Movement.FOLLOW][1], 0.0, velocity);
			}
			emitParticle();
			break;

		case Motion.SPECIALHIT1:
			if(t <= f2t(8))
			{
				if(t == f2t(7))
				{
					.gedo.math.vector.multiply(velocity, 2.0);
				}
				else if(!_fists[FistID.SPECIALHIT1].isCollided)
				{
					through(5.0, -1.0);
				}
			}
			emitParticle();
			break;

		case Motion.CHARGEHIT0:
			if(t <= f2t(5))
			{
				follow((f2t(5) - t) / 10.0);
				if(t == f2t(5))
				{
					shotFist(_fists[FistID.CHARGEHIT0]);

					if(auto effectTask = _system.getEffectTask())
					{
						direction.toVector(0.0f, 3.0f, 0.0f, effectTask.position.current);

						.gedo.math.vector.add(effectTask.position.current, position.current);

						effectTask.direction.copyFrom(direction);

						effectTask.start(_flashFeather);
					}

					Source().play(_sounds[SoundID.CHARGEHIT]);
				}
			}
			break;

		case Motion.CHARGEHIT1:
			if(t <= f2t(110) && t > f2t(108))
			{
				if(t == f2t(110))
				{
					shotFist(_fists[FistID.CHARGEHIT1B]);
					velocity[] = 0.0;
				}
				else
				{
					follow((f2t(110) - t)/2.0);
				}
			}
			else
			{
				const uint[] hitTimings = [f2t(6), f2t(14), f2t(22), f2t(30), f2t(38), f2t(46), f2t(54), f2t(62), f2t(70), f2t(78), f2t(86), f2t(90), f2t(94), f2t(98)];

				foreach(hitTiming; hitTimings)
				{
					if(hitTiming >= t)
					{
						if(t == hitTiming)
						{
							shotFist(_fists[FistID.CHARGEHIT1A]);
							velocity[] = 0.0;
						}
						else if(t + 4 >= hitTiming)
						{
							emitChargeParticle();
							follow((hitTiming - t)/4.0);
						}
						break;
					}
				}
			}
			break;

		case Motion.SPELL:
			if(t == f2t(69))
			{
				if(!_spell.parent)
				{
					shot(_spell);
					if(auto effectTask = _system.getEffectTask())
					{
						effectTask.position.current[] = _spell.position.current[];

						effectTask.direction.copyFrom(_spell.direction);

						effectTask.start(_flashMain);
					}
					Source().play(_sounds[SoundID.SPELLSHOT]);
				}
			}
			else if(t == f2t(12))
			{
				Source().play(_sounds[SoundID.SPELLCHARGE]);
			}
			brake();
			break;

		default:
			break;
		}

		_particleEmitter.update();
	}


	override void onLand()
	{
		switch(_timer.index)
		{
		case Motion.SPECIAL1:
			changeState(State.RECOVER);
			break;

		default:
			break;
		}
	}


	override void onChangeState(State from, State to)
	{
		Motion[2] motion;

		motion[1] = Motion.INVALID;

		switch(to)
		{
		case State.RESSURECT:
			motion[0] = Motion.FALL0;
			motion[1] = Motion.FALL1;
			break;

		case State.STAND:
			motion[0] = Motion.STAND;
			break;

		case State.FALL:
			if(velocity[2] > 0.0)
			{
				motion[0] = Motion.RISE0;
				motion[1] = Motion.RISE1;
			}
			else
			{
				motion[0] = Motion.FALL0;
				motion[1] = Motion.FALL1;
			}
			break;

		case State.RECOVER:
			switch(_timer.index)
			{
			default:
			case Motion.GUARD1:
				motion[0] = Motion.GUARD2;
				break;

			case Motion.RUN1:
				motion[0] = Motion.RUN2;
				break;

			case Motion.FALL1:
				motion[0] = Motion.LAND;
				break;

			case Motion.DASH1:
				motion[0] = Motion.DASH2;
				break;

			case Motion.SPECIAL1:
				motion[0] = Motion.SPECIAL2;
				break;
			}
			motion[1] = motion[0];
			break;

		case State.RUN:
			motion[0] = Motion.RUN0;
			motion[1] = Motion.RUN1;
			break;

		case State.STEP:
			motion[0] = Motion.DASH1;
			break;

		case State.JUMP:
			motion[0] = Motion.JUMP;
			motion[1] = Motion.STAND;
			break;

		case State.LAND:
			motion[0] = Motion.LAND;
			motion[1] = Motion.STAND;
			break;

		case State.GUARD:
			motion[0] = Motion.GUARD0;
			motion[1] = Motion.GUARD1;
			break;

		case State.SINGLESHOT:
			motion[0] = Motion.SINGLESHOT;
			motion[1] = Motion.STAND;
			break;

		case State.CHARGESHOT:
			motion[0] = Motion.CHARGESHOT;
			motion[1] = Motion.STAND;
			break;

		case State.HOMINGSHOT:
			motion[0] = Motion.HOMINGSHOT;
			motion[1] = Motion.STAND;
			break;

		case State.SPECIAL:
			motion[0] = Motion.SPECIAL0;
			motion[1] = Motion.SPECIAL1;
			Source().play(_sounds[SoundID.SPECIAL]);
			break;

		case State.SPELL:
			motion[0] = Motion.SPELL;
			motion[1] = Motion.STAND;
			break;

		case State.FOLLOW:
			motion[0] = Motion.FOLLOW0;
			motion[1] = Motion.FOLLOW1;
			break;

		case State.HIT:
			if(from == State.GUARD)
			{
				motion[0] = Motion.COUNTER0;
				motion[1] = Motion.STAND;
			}
			else
			{
				motion[0] = Motion.HIT0;
				motion[1] = Motion.STAND;
			}
			break;

		case State.CHARGEHIT:
			motion[0] = Motion.CHARGEHIT0;
			motion[1] = Motion.STAND;
			break;

		case State.KNOCKBACK:
			motion[0] = Motion.KNOCKBACK;
			motion[1] = Motion.STAND;
			break;

		case State.DOWN:
			motion[0] = Motion.DOWN;
			motion[1] = Motion.STAND;
			break;

		case State.KO:
			motion[0] = Motion.KO;
			break;
		}

		if(motion[1] == Motion.INVALID)
		{
			_timer.set(motion[0]);
		}
		else
		{
			_timer.set(motion[0], motion[1]);
		}
	}


	override void getEyeVector(Vector d)
	{
		switch(_timer.index)
		{
		case Motion.HIT0:
		case Motion.HIT1:
		case Motion.COUNTER0:
		case Motion.COUNTER1:
		case Motion.CHARGEHIT1:
			direction.toVector(-1.0, 0.0, 0.0, d);
			return;

		case Motion.SPECIALHIT0:
		case Motion.SPECIALHIT1:
			direction.toVector(-SQRT1_2, 0.0, -SQRT1_2, d);
			return;

		case Motion.SPELL:
			if(_timer.time < f2t(30))
			{
				direction.getRight(d);
				return;
			}
			break;

		default:
			break;
		}
		super.getEyeVector(d);
	}


	override string voiceDir()
	{
		return "$LIB/oku/voice";
	}


private:
	static float[2][Movement.max+1] _accelerations;

	Bullet[FistID.max+1] _fists;
	HomingFeather[8]     _homingFeathers;
	ParticleEmitter      _particleEmitter;
	GGL_Vertex[32]       _trjVertices;
	ChargeBeam[6]        _chargeBeams;
	HitBeam[2]           _hitBeams;
	AtomicBomb           _spell;
	Beam[4]              _singleBeams;


	void emitParticle()
	{
		Vector p;

		p[0] = 3.0 + ((rand()&255) - 128)/256.0;
		p[1] = -1.0 + ((rand()&255) - 128)/256.0;
		p[2] = 0.0 + ((rand()&255) - 128)/256.0;

		Vector v;

		v[0] = 0.0;
		v[1] = -(rand()&255)/512.0;
		v[2] = (rand()&255)/512.0;

		if(_particleEmitter.count&1)
		{
			p[0] = -p[0];
			v[0] = -v[0];
		}

		direction.toVector(p[0], p[1], p[2], p);
		.gedo.math.vector.add(p, position.current);

		direction.toVector(v[0], v[1], v[2], v);
		.gedo.math.vector.add(v, velocity);

		_particleEmitter.add(p, v);
	}


	void emitChargeParticle()
	{
		Vector p;

		p[0] = 0.0 + ((rand()&255) - 128)/256.0;
		p[1] = 3.0 + ((rand()&255) - 128)/256.0;
		p[2] = 0.0 + ((rand()&255) - 128)/256.0;

		Vector v;

		v[] = 0.0;

		direction.toVector(p[0], p[1], p[2], p);
		.gedo.math.vector.add(p, position.current);

		_particleEmitter.add(p, v);
	}


	void startRush(VisualTask t, Vector p)
	{
		_timer.set(Motion.CHARGEHIT1, Motion.STAND);
	}


	void drawSpell()
	{
		direction.transform(position.current);

		GGL_translate(0.0, 6.0, 0.0);

		GGL_enterBillBoardMode();

		GGL_setBlendMode(GGL_BlendMode.MIX);

		auto scale = (_timer.time&3)/8.0 + (_timer.time - f2t(12))*(4.0/(f2t(69) - f2t(12)));

		GGL_scale(scale, scale, scale);

		_atomicCircle.draw();

		scale = (8 - (_timer.time&7))*(2.0)/scale;

		GGL_scale(scale, scale, scale);

		_atomicPlane.draw(rgba(255, 255, 255, ((_timer.time&7) + 1)*31));
	}
}



private class Beam : Bullet
{
public:
	this(Power p)
	{
		_power    = p;
		_polyLine = new PolyLine(2);

		_polyLine.count  = 2;
		_polyLine.radius = 0.75;
	}


	override float radius()
	{
		return _polyLine.radius;
	}


	override Vector[] points()
	{
		return _polyLine.points[length-1 .. length];
	}


	override void onStart()
	{
		foreach(inout point; _polyLine.points)
		{
			point[] = position.current[];
		}
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			.gedo.math.vector.lerp(_polyLine.points[0], _polyLine.points[1], SPEED);
			if(_polyLine.points[1] == _polyLine.points[0])
			{
				stop();
			}
		}
		else
		{
			//homing(.std.math.PI / 1024.0);

			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, SPEED);

			.gedo.math.vector.add(position.current, v);

			_polyLine.points[length-1][] = position.current[];

			Vector d;

			.gedo.math.vector.subtract(d, _polyLine.points[0], _polyLine.points[1]);

			auto l = .gedo.math.vector.magnitude(d);
			if(l > 128.0)
			{
				.gedo.math.vector.multiply(d, 128.0/l);

				.gedo.math.vector.add(_polyLine.points[0], _polyLine.points[1], d);
			}

			if((cast(Character)parent).system.field.isSinked(position.current))
			{
				_time = LIFE_TIME;
			}
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.vertices, _textures[TextureID.BEAM], GGL_BlendMode.MIX);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return (cast(Character)parent).system.sharedSound(SharedSoundID.HITH);
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	const float SPEED = 5.0;
	const int   LIFE_TIME = 128;

	PolyLine   _polyLine;
	Power      _power;
}



private class HitBeam : Bullet
{
public:
	this(Power p)
	{
		_power = p;

		_polyLine = new PolyLine(2);
		_polyLine.count  = 2;
		_polyLine.radius = 0.75;
	}


	override float radius()
	{
		return _polyLine.radius;
	}


	override void onStart()
	{
		Vector f;
		direction.getFront(f);
		.gedo.math.vector.multiply(f, 256.0);

		.gedo.math.vector.add(position.current, f);

		if((cast(Character)parent).system.field.isSinked(position.previous, position.current, f))
		{
			position.current[] = f[];
		}

		_polyLine.points[0][] = position.previous[];
		_polyLine.points[1][] = position.current[];
	}


	override void onUpdate()
	{
		if(_time < LIFE_TIME)
		{
			position.previous[] = _polyLine.points[0][];

			auto k = _time * 1.0 / LIFE_TIME;

			_polyLine.radius = 8.0*(1.0 - k*k);
		}
		else
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.points[0], &drawAlpha);
		}
	}


	override BulletType type()
	{
		return BulletType.HIT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return (cast(Character)parent).system.sharedSound(SharedSoundID.HITH);
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	const int LIFE_TIME = 32;

	PolyLine _polyLine;
	Power    _power;


	void drawAlpha()
	{
		GGL_setBlendMode(GGL_BlendMode.MIX);
		_textures[TextureID.HITBEAM].draw(_polyLine.vertices);

		GGL_setBlendMode(GGL_BlendMode.ADD);
		translate(_polyLine.points[0]);
		GGL_enterBillBoardMode();
		auto s = _polyLine.radius * 1.25;
		GGL_scale(s, s, s);
		_hitBeamCircle.draw();
	}
}



private class HomingFeather : Bullet
{
public:
	this(Power p, Vector offset)
	{
		_power = p;

		_polyLine = new PolyLine(32);

		_polyLine.count  = 32;
		_polyLine.radius = 0.4;

		_offset[] = offset[];

		_velocity = .gedo.math.vector.magnitude(offset);

		.gedo.math.vector.normalize(_offset);
	}


	override float radius()
	{
		return _polyLine.radius;
	}


	override void onStart()
	{
		turn(PI);

		foreach(inout point; _polyLine.points)
		{
			point[] = position.current[];
		}
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			stop();
		}
		else if((cast(Character)parent).system.field.isSinked(position.current))
		{
			_time = LIFE_TIME;
		}
		else if((cast(Character)parent).getBalance() > 0)
		{
			_time = LIFE_TIME;
		}
		else
		{
			auto v = _velocity;

			if(_time < 64)
			{
				v *= (64-_time)/128.0;
			}
			else if(!isCollided)
			{
				homing(PI);

				turn(PI/8.0);
			}

			advance(v);

			_polyLine.record(position.current);
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.vertices, _textures[TextureID.HOMINGTRAJECTORY], GGL_BlendMode.MIX);
		}

		GGL_pushMatrix();
		scope(exit)
		{
			GGL_popMatrix();
		}

		direction.transform(position.current);

		GGL_rotate(_time * .std.math.PI / 4.0, 0, 1, 0);

		_feather.draw();
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return (cast(Character)parent).system.sharedSound(SharedSoundID.CUT);
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	int LIFE_TIME = 256;

	PolyLine _polyLine;
	Vector   _offset;
	float    _velocity;
	Power    _power;


	void turn(float v)
	{
		Vector a;

		direction.toVector(_offset[0], _offset[1], _offset[2], a);

		direction.homing(a, v, true);
	}


	void advance(float vl)
	{
		Vector v;

		direction.toVector(0.0f, vl, 0.0f, v);

		.gedo.math.vector.add(position.current, v);
	}
}



private class ChargeBeam : Bullet
{
public:
	this(Power p)
	{
		_power    = p;
		_polyLine = new PolyLine(3);

		_polyLine.count  = 3;
		_polyLine.radius = 1.5;
	}


	override float radius()
	{
		return _polyLine.radius;
	}


	override Vector[] points()
	{
		return _polyLine.points[length-1 .. length];
	}


	override void onStart()
	{
		foreach(inout point; _polyLine.points)
		{
			point[] = position.current[];
		}
		_polyLine.radius = 1.5;
	}


	override void onUpdate()
	{
		if(_time >= LIFE_TIME)
		{
			_polyLine.radius = _polyLine.radius*0.75;

			if(_polyLine.radius <= 0.1)
			{
				stop();
			}
		}
		else
		{
			auto speed = SPEED;
			if(_time < 16)
			{
				speed *= 0.25;
			}
			else if(_time == 16)
			{
				homing(PI);

				_polyLine.points[1][] = _polyLine.points[2][];
			}

			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, speed);

			.gedo.math.vector.add(position.current, v);

			_polyLine.points[$-1][] = position.current[];

			if((cast(Character)parent).system.field.isSinked(position.current))
			{
				_time = LIFE_TIME;
			}
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(_polyLine.vertices, _textures[TextureID.BEAM], GGL_BlendMode.MIX);
		}
	}


	override BulletType type()
	{
		return BulletType.SHOT;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return (cast(Character)parent).system.sharedSound(SharedSoundID.HITH);
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


private:
	const float SPEED = 6.0;
	const int   LIFE_TIME = 64;

	PolyLine _polyLine;
	Power    _power;
}



private class AtomicBomb : Bullet
{
public:
	this(Power p)
	{
		_power = p;
	}


	override float radius()
	{
		if(_time <= LIFE_TIME0)
		{
			return 3.0;
		}
		else
		{
			return (_time - LIFE_TIME0)*(128.0/LIFE_TIME1);
		}
	}


	override void onUpdate()
	{
		if(_time < LIFE_TIME0)
		{
			Vector v;

			direction.getFront(v);

			.gedo.math.vector.multiply(v, SPEED);

			.gedo.math.vector.add(position.current, v);

			if((cast(Character)parent).system.field.isSinked(position.current))
			{
				_time = LIFE_TIME0;
			}
		}
		else if(_time < LIFE_TIME0 + LIFE_TIME1)
		{
			if((_time & 7) == 0)
			{
				clearDones();
			}
		}
		else
		{
			stop();
		}
	}


	override void draw()
	{
		if(auto am = (cast(Character)parent).system.alphaMeshList().get())
		{
			am.set(position.current, &drawAlpha);
		}
	}


	override BulletType type()
	{
		return BulletType.HOLD;
	}


	override Power power()
	{
		return _power;
	}


	override Sound hitSE()
	{
		return (cast(Character)parent).system.sharedSound(SharedSoundID.HITH);
	}


	override EffectProgram hitEffect()
	{
		return (cast(Character)parent).system.sharedEffect(SharedEffectID.HITMARK);
	}


	override void onCollide(Vector center)
	{
		if(_time < LIFE_TIME0)
		{
			_time = LIFE_TIME0;
		}
	}


private:
	const float SPEED = 3.0;
	const int   LIFE_TIME0 = 120;
	const int   LIFE_TIME1 = 240;

	Power    _power;


	void drawAlpha()
	{
		if(_time < LIFE_TIME0)
		{
			direction.transform(position.current);

			GGL_translate(0.0, 6.0, 0.0);

			GGL_enterBillBoardMode();

			GGL_setBlendMode(GGL_BlendMode.MIX);

			auto scale = (_time&3)/8.0 + 4.0;

			GGL_scale(scale, scale, scale);

			_atomicCircle.draw();

			scale = ((_time&7) + 1)*(2.0)/scale;

			GGL_scale(scale, scale, scale);

			_atomicPlane.draw(rgba(255, 255, 255, (8 - (_time&7))*31));
		}
		else
		{
			GGL_setBlendMode(GGL_BlendMode.MIX);

			translate(position.current);

			GGL_pushMatrix();

			auto r = radius;

			GGL_scale(r, r, r);

			_atomicSphere.draw(WHITE);

			GGL_popMatrix();

			GGL_pushMatrix();

			r = radius*((_time&7)+1.0)/8.0;

			GGL_scale(r, r, r);

			_atomicRing.draw(rgba(128, 160, 255, 160));

			GGL_popMatrix();

			GGL_enterBillBoardMode();

			GGL_pushMatrix();

			GGL_rotate(_time*(PI/16.0), 0, 0, 1);

			r = (radius + (_time&3))*2.0;

			GGL_scale(r, r, r);

			_atomicFlare.draw();

			GGL_popMatrix();

			void drawRing(int t)
			{
				GGL_pushMatrix();
				scope(exit)
				{
					GGL_popMatrix();
				}

				auto k = ((t&15)+1.0)/16.0;

				r = radius*k;

				GGL_scale(r, r, r);

				_atomicRing.draw(rgba(16, 32, 255, cast(int)(255*(1.0-k))));
			}

			drawRing(_time);
			drawRing(_time + 8);
		}
	}
}
}



private StaticMesh createSphere(float radius, Texture texture)
{
	const int N = 16;

	GGL_Vertex[N*(N/2 - 1)+2] vertices;

	vertices[0].position[] = [0.0f, 0.0f, radius];
	vertices[0].texCoord[] = [0.0f, 0.5f];
	vertices[$-1].position[] = [0.0f, 0.0f, -radius];
	vertices[$-1].texCoord[] = [1.0f, 0.5f];

	for(auto i = 1; i < N/2; ++i)
	{
		auto vangle = i * PI/(N/2);

		auto z = radius * cos(vangle);

		auto hr = radius * sin(vangle);

		auto ringVertices = vertices[1 + N*(i-1) .. 1 + N*i];
		foreach(j, inout vertex; ringVertices)
		{
			auto hangle = j * 2.0 * PI / N;

			auto x = hr * cos(hangle);
			auto y = hr * sin(hangle);

			vertex.position[0] = x;
			vertex.position[1] = y;
			vertex.position[2] = z;

			vertex.texCoord[0] = i / (N/2.0);
			vertex.texCoord[1] = j & 1;
		}
	}

	GGL_Face[N + N*2*(N/2 - 2) + N] faces;

	foreach(i, inout face; faces[0 .. N])
	{
		face.vertexIndices[0] = 0;
		face.vertexIndices[1] = i+1;
		face.vertexIndices[2] = (i+1)&(N-1) + 1;
	}

	for(auto i=1;i<N/2-1;++i)
	{
		foreach(j, inout face; faces[N+N*2*(i-1) .. N+N*2*i])
		{
			face.vertexIndices[0] = 1 + N*(i-1+(j&1)) + (j>>1);
			face.vertexIndices[1] = face.vertexIndices[0] + 1;
			face.vertexIndices[2] = face.vertexIndices[0] + N*(1 - 2*(j&1)) + (j&1);
		}
	}

	foreach(i, inout face; faces[$-N .. $])
	{
		face.vertexIndices[0] = vertices.length-1;
		face.vertexIndices[1] = vertices.length-1-N+i;
		face.vertexIndices[2] = vertices.length-1-N+((i+1)&(N-1));
	}

	return new StaticMesh(vertices, faces, texture);
}



private StaticMesh createMirrorPlane(Texture texture)
{
	GGL_Vertex[9] vertices;

	for(auto i=0;i<3;++i)
	{
		for(auto j=0;j<3;++j)
		{
			auto k = 3*i+j;

			auto x = j - 1.0;
			auto y = i - 1.0;

			vertices[k].position[0] = x;
			vertices[k].position[1] = y;
			vertices[k].position[2] = 0.0;

			vertices[k].texCoord[0] = abs(x);
			vertices[k].texCoord[1] = abs(y);
		}
	}

	GGL_Face[8] faces;

	faces[0].vertexIndices[0] = 4;
	faces[0].vertexIndices[1] = 3;
	faces[0].vertexIndices[2] = 0;
	faces[1].vertexIndices[0] = 4;
	faces[1].vertexIndices[1] = 0;
	faces[1].vertexIndices[2] = 1;
	faces[2].vertexIndices[0] = 4;
	faces[2].vertexIndices[1] = 1;
	faces[2].vertexIndices[2] = 2;
	faces[3].vertexIndices[0] = 4;
	faces[3].vertexIndices[1] = 2;
	faces[3].vertexIndices[2] = 5;
	faces[4].vertexIndices[0] = 4;
	faces[4].vertexIndices[1] = 5;
	faces[4].vertexIndices[2] = 8;
	faces[5].vertexIndices[0] = 4;
	faces[5].vertexIndices[1] = 8;
	faces[5].vertexIndices[2] = 7;
	faces[6].vertexIndices[0] = 4;
	faces[6].vertexIndices[1] = 7;
	faces[6].vertexIndices[2] = 6;
	faces[7].vertexIndices[0] = 4;
	faces[7].vertexIndices[1] = 6;
	faces[7].vertexIndices[2] = 3;

	return new StaticMesh(vertices, faces, texture);
}
