// TODO edgeとアンチエイリアスを優先、シェーダーはoffでいい、余裕があるならon
// カメラ調整、よくみえるように
// 800x600 aaで、重いなら640x480
// 口の位置をもう少し上に?
// 顔を少し細くした方がいい?
// 顔のエッジをモデリングで付けて、シェーダーoffで描画が速くなる
// FSAAx2、輪郭はモデリングで最小限顔だけにするのがいい?
import coneneko.all, sdl, opengl, std.math, rin, std.random;

void main()
{
	auto wnd = new WindowRp();
	auto scene = new RinScene(wnd);
	auto laplacian = new Laplacian(wnd.size * 2);
	auto fsaa = new Fsaa2(wnd.size);
	while (!wnd.isEnd)
	{
		scene.rin++;
		fsaa(scene.colorTree);
		wnd.draw(fsaa(laplacian(scene.edgeTree)));
		
		//wnd.draw(fsaa(scene.colorTree));
		
		//wnd.draw(scene.colorTree);
		//wnd.draw(laplacian(scene.edgeTree));
		wnd.flip();
		SDL_Delay(1);
		wnd.doEvents();
	}
}

class RinScene
{
	private SceneGraph colorTree_, edgeTree_;
	RinModel rin;
	private ModelShader defaultModelShader, normalModelShader;
	
	this(WindowRp wnd)
	{
		auto renderState = new RenderState();
		with (renderState)
		{
			depthTest = true;
			cullFace = true;
			cullFaceMode = GL_FRONT;
		}
		
		auto camera = wnd.createCamera();
		rin = new RinModel();
		defaultModelShader = rin.shader;
		normalModelShader = new NormalModelShader();
		//normalModelShader = new DepthModelShader();
		
		colorTree_ = new SceneGraph();
		colorTree_.root = colorTree_.link(
			renderState,
			colorTree_.linkParallel(
				camera,
					new Clear(Color.BLACK),
					rin,
					new BgModel()
			)
		);
		
		edgeTree_ = new SceneGraph();
		edgeTree_.root = edgeTree_.link(
			renderState,
			edgeTree_.linkParallel(
				camera,
					new Clear(Color.BLACK),
					rin
			)
		);
	}
	
	SceneGraph colorTree()
	{
		rin.shader = defaultModelShader;
		return colorTree_;
	}
	
	SceneGraph edgeTree()
	{
		rin.shader = normalModelShader;
		return edgeTree_;
	}
}

class RinModel : Model
{
	this()
	{
		super(new RinModelData());
		waitingMotion = createMotion(0);
		
		this[1].visible = true; // め
		this[1].waitingMotion = [0, 1, 0];
		this[1].waitingMotion.span = 20;
		
		this[2].visible = true; // まぶた
		this[2].motion = [0, 1, 0];
		this[2].motion.span = 15;
		this[2].waitingMotion = [0, 0];
		this[2].waitingMotion.span = 1;
		
		this[3].visible = true; // まゆ
		this[3].motion = [0, 1, 0];
		this[3].motion.span = 15;
		this[3].waitingMotion = [0, 0];
		this[3].waitingMotion.span = 1;
		
		this[4].visible = true; // くち
		this[4].motion = [0, 0];
		this[4].motion.span = 8;
		this[4].waitingMotion = [0, 0];
		this[4].waitingMotion.span = 1;
		
		this[5].visible = true; // ほ
		this[5].motion = [0, 0];
		this[5].motion.span = 1;
		this[5].waitingMotion = [0, 0];
		this[5].waitingMotion.span = 1;
		
		this[6].visible = true; // りぼん胸
		this[7].visible = true; // りぼん頭
		this[8].visible = true; // 髪
		//this[9].visible = true;
		
		//auto tms = new ToonModelShader();
		//tms.lightDirection = normalize(Vector(-0.5, -0.5, -1.0));
		//shader = tms;
		//auto htms = new HsvToonModelShader();
		//htms.lightDirection = normalize(Vector(-0.5, -0.5, -1.0));
		//shader = htms;
	}
	
	mixin Rin;
	
	override void opPostInc()
	{
		tick();
		super++;
	}
}

class RinModelData : ModelData
{
	this()
	{
		readFile("rin.3d", this);
	}
}

class WindowRp : Window
{
	this()
	{
		//super(800, 600, true);
		//super(800, 600);
		//super(640, 480); // 30fps
		//super(640, 480, true); // 60fps
		super(512, 512);
	}
	
	MqoCamera createCamera()
	{
		auto result = new MqoCamera(this, Vector(0, 0, 400));
		result.projection = Matrix.perspectiveFov(
			PI / 8.0, cast(float)width / cast(float)height, 50.0, 1000.0
		);
		return result;
	}
	
	bool isEnd()
	{
		return closed || key.pressing(SDLK_ESCAPE);
	}
}

class BgModelData : ModelData
{
	this()
	{
		translator = new MqoTranslator();
		readFile("bg.mqo", this);
	}
}

class BgModel : Model
{
	this()
	{
		super(new BgModelData());
	}
}




class NormalModelShader : ModelShader
{
	invariant()
	{
		assert(basic !is null);
		assert(morph !is null);
		assert(skinMesh !is null);
	}
	
	NormalShader basic;
	NormalMorphShader morph;
	NormalSkinMeshShader skinMesh;
	
	this()
	{
		basic = new NormalShader();
		morph = new NormalMorphShader();
		skinMesh = new NormalSkinMeshShader();
	}
	
	Shader getBasic() { return basic; }
	Shader getMorph() { return morph; }
	Shader getSkinMesh() { return skinMesh; }
}

class NormalShader : Shader
{
	protected this(string vertexSource)
	{
		super(
			vertexSource,
			"void main()
			{
				gl_FragColor = gl_Color;
			}"
		);
	}
	
	this()
	{
		this(
			"void main()
			{
				gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
				gl_FrontColor.xyz = gl_NormalMatrix * gl_Normal * 0.5 + 0.5;
			}"
		);
	}
}

class NormalMorphShader : NormalShader
{
	this()
	{
		super(
			"attribute vec3 nextPosition;
			uniform float interpolater;
			void main()
			{
				vec4 p0 = gl_ModelViewProjectionMatrix * gl_Vertex;
				vec4 p1 = gl_ModelViewProjectionMatrix * vec4(nextPosition, 1.0);
				gl_Position = p0 * (1.0 - interpolater) + p1 * interpolater;
				gl_FrontColor.xyz = gl_NormalMatrix * gl_Normal * 0.5 + 0.5;
			}"
		);
	}
}

class NormalSkinMeshShader : NormalShader
{
	this()
	{
		super(
			"attribute vec3 matrixIndexWeight;
			uniform mat4 boneMatrixArray[34];
			vec4 blend(vec4 p)
			{
				return boneMatrixArray[int(matrixIndexWeight.x)] * p * matrixIndexWeight.z
					+ boneMatrixArray[int(matrixIndexWeight.y)] * p * (1.0 - matrixIndexWeight.z);
			}
			vec3 nblend(vec3 n)
			{
				vec4 n2 = vec4(n, 0);
				vec4 result = boneMatrixArray[int(matrixIndexWeight.x)] * n2 * matrixIndexWeight.z
					+ boneMatrixArray[int(matrixIndexWeight.y)] * n2 * (1.0 - matrixIndexWeight.z);
				return vec3(result);
			}
			void main()
			{
				gl_Position = gl_ModelViewProjectionMatrix * blend(gl_Vertex);
				gl_FrontColor.xyz = gl_NormalMatrix * nblend(gl_Normal) * 0.5 + 0.5;
			}"
		);
	}
}


class DepthModelShader : ModelShader
{
	invariant()
	{
		assert(basic !is null);
		assert(morph !is null);
		assert(skinMesh !is null);
	}
	
	DepthShader basic;
	DepthMorphShader morph;
	DepthSkinMeshShader skinMesh;
	
	this()
	{
		basic = new DepthShader();
		morph = new DepthMorphShader();
		skinMesh = new DepthSkinMeshShader();
	}
	
	Shader getBasic() { return basic; }
	Shader getMorph() { return morph; }
	Shader getSkinMesh() { return skinMesh; }
}

class DepthShader : Shader // 名前重複してる
{
	protected this(string vertexSource)
	{
		super(
			vertexSource,
			"void main()
			{
				gl_FragColor = vec4(gl_FragCoord.z, gl_FragCoord.z, gl_FragCoord.z, 1.0);
			}"
		);
	}
	
	this()
	{
		this(
			"void main()
			{
				gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
				gl_FrontColor.xyz = gl_NormalMatrix * gl_Normal * 0.5 + 0.5;
			}"
		);
	}
}

class DepthMorphShader : DepthShader
{
	this()
	{
		super(
			"attribute vec3 nextPosition;
			uniform float interpolater;
			void main()
			{
				vec4 p0 = gl_ModelViewProjectionMatrix * gl_Vertex;
				vec4 p1 = gl_ModelViewProjectionMatrix * vec4(nextPosition, 1.0);
				gl_Position = p0 * (1.0 - interpolater) + p1 * interpolater;
				gl_FrontColor.xyz = gl_NormalMatrix * gl_Normal * 0.5 + 0.5;
			}"
		);
	}
}

class DepthSkinMeshShader : DepthShader
{
	this()
	{
		super(
			"attribute vec3 matrixIndexWeight;
			uniform mat4 boneMatrixArray[34];
			vec4 blend(vec4 p)
			{
				return boneMatrixArray[int(matrixIndexWeight.x)] * p * matrixIndexWeight.z
					+ boneMatrixArray[int(matrixIndexWeight.y)] * p * (1.0 - matrixIndexWeight.z);
			}
			vec3 nblend(vec3 n)
			{
				vec4 n2 = vec4(n, 0);
				vec4 result = boneMatrixArray[int(matrixIndexWeight.x)] * n2 * matrixIndexWeight.z
					+ boneMatrixArray[int(matrixIndexWeight.y)] * n2 * (1.0 - matrixIndexWeight.z);
				return vec3(result);
			}
			void main()
			{
				gl_Position = gl_ModelViewProjectionMatrix * blend(gl_Vertex);
				gl_FrontColor.xyz = gl_NormalMatrix * nblend(gl_Normal) * 0.5 + 0.5;
			}"
		);
	}
}


class Laplacian : SceneGraph
{
	private FrameBufferObject fbo;
	
	this(Vector fboSize)
	{
		fbo = new FrameBufferObject(
		//fbo = new FrameBufferObjectRgba32f(
			cast(uint)fboSize.x,
			cast(uint)fboSize.y,
			0, true
		);
		root = linkSerial(
			(new RenderState).blend(true), new LaplacianShader(fboSize), fbo, new FboBoard()
		);
	}
	
	SceneGraph opCall(SceneGraph tree)
	{
		fbo.draw(tree);
		return this;
	}
}

class LaplacianShader : Shader
{
	this(Vector textureSize)
	{
		super(
			"void main()
			{
				gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
			}",
			"uniform vec4 textureSize;
			uniform sampler2D texture0;
			void main()
			{
				vec2 p = gl_FragCoord.xy / textureSize.xy;
				float w = 1.0 / textureSize.x;
				float h = 1.0 / textureSize.y;
				vec4 tc = vec4(0.0, 0.0, 0.0, 1.0);
				tc += texture2D(texture0, p + vec2(-w, -h));
				tc += texture2D(texture0, p + vec2(-w, 0.0));
				tc += texture2D(texture0, p + vec2(-w, h));
				tc += texture2D(texture0, p + vec2(0.0, -h));
				tc += texture2D(texture0, p + vec2(0.0, h));
				tc += texture2D(texture0, p + vec2(w, -h));
				tc += texture2D(texture0, p + vec2(w, 0.0));
				tc += texture2D(texture0, p + vec2(w, h));
				tc -= texture2D(texture0, p) * 8.0;
				
				const float n = 0.45;
				gl_FragColor = any(greaterThan(tc.xyz, vec3(n, n, n)))
					? vec4(0.0, 0.0, 0.0, 1.0) : vec4(1.0, 1.0, 1.0, 0.0);
			}"
		);
		this["texture0"] = 0;
		this["textureSize"] = textureSize;
	}
}
