//
// Copyright(c) 2016-2017 benikabocha.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//

#include "GLMMDModelDrawer.h"

#include "GLMMDModelDrawContext.h"

#include <Saba/Base/Log.h>
#include <Saba/GL/GLShaderUtil.h>
#include <Saba/GL/GLTextureUtil.h>

#include <imgui.h>

namespace saba
{
	GLMMDModelDrawer::GLMMDModelDrawer(GLMMDModelDrawContext * ctxt, std::shared_ptr<GLMMDModel> mmdModel)
		: m_drawContext(ctxt)
		, m_mmdModel(mmdModel)
		, m_clipElapsed(true)
		, m_viewLocal(true)
		, m_selectedNode(nullptr)
	{
		SABA_ASSERT(ctxt != nullptr);
		SABA_ASSERT(mmdModel != nullptr);
	}

	GLMMDModelDrawer::~GLMMDModelDrawer()
	{
		Destroy();
	}

	bool GLMMDModelDrawer::Create()
	{
		int matIdx = 0;
		for (const auto& mat : m_mmdModel->GetMaterials())
		{
			GLSLDefine define;

			MaterialShader matShader;
			matShader.m_mmdMaterialIndex = matIdx;

			// MMD Shader
			matShader.m_mmdShaderIndex = m_drawContext->GetShaderIndex(define);
			if (matShader.m_mmdShaderIndex == -1)
			{
				SABA_ERROR("MMD Material Shader not found.");
				return false;
			}
			if (!matShader.m_mmdVao.Create())
			{
				SABA_ERROR("Vertex Array Object Create fail.");
				return false;
			}

			auto shader = m_drawContext->GetShader(matShader.m_mmdShaderIndex);

			glBindVertexArray(matShader.m_mmdVao);

			m_mmdModel->GetPositionBinder().Bind(shader->m_inPos, m_mmdModel->GetPositionVBO());
			glEnableVertexAttribArray(shader->m_inPos);

			m_mmdModel->GetNormalBinder().Bind(shader->m_inNor, m_mmdModel->GetNormalVBO());
			glEnableVertexAttribArray(shader->m_inNor);

			if (shader->m_inUV != -1)
			{
				m_mmdModel->GetUVBinder().Bind(shader->m_inUV, m_mmdModel->GetUVVBO());
				glEnableVertexAttribArray(shader->m_inUV);
			}

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mmdModel->GetIBO());

			glBindVertexArray(0);

			// MMD Edge Shaer
			matShader.m_mmdEdgeShaderIndex = m_drawContext->GetEdgeShaderIndex(define);
			if (matShader.m_mmdEdgeShaderIndex == -1)
			{
				SABA_ERROR("MMD Edge Material Shader not found.");
				return false;
			}
			if (!matShader.m_mmdEdgeVao.Create())
			{
				SABA_ERROR("Vertex Array Object Create fail.");
				return false;
			}

			auto edgeShader = m_drawContext->GetEdgeShader(matShader.m_mmdEdgeShaderIndex);

			glBindVertexArray(matShader.m_mmdEdgeVao);

			m_mmdModel->GetPositionBinder().Bind(edgeShader->m_inPos, m_mmdModel->GetPositionVBO());
			glEnableVertexAttribArray(edgeShader->m_inPos);

			m_mmdModel->GetNormalBinder().Bind(edgeShader->m_inNor, m_mmdModel->GetNormalVBO());
			glEnableVertexAttribArray(edgeShader->m_inNor);

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mmdModel->GetIBO());

			glBindVertexArray(0);

			// Shadow
			if (!matShader.m_shadowVao.Create())
			{
				SABA_ERROR("Vertex Array Object Create fail.");
				return false;
			}

			glBindVertexArray(matShader.m_shadowVao);
			auto shadowShader = m_drawContext->GetViewerContext()->GetShadowMap()->GetShader();

			m_mmdModel->GetPositionBinder().Bind(shadowShader->m_inPos, m_mmdModel->GetPositionVBO());
			glEnableVertexAttribArray(shadowShader->m_inPos);

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mmdModel->GetIBO());

			glBindVertexArray(0);

			// Ground Shadow
			matShader.m_mmdGroundShadowShaderIndex = m_drawContext->GetGroundShadowShaderIndex(define);
			if (matShader.m_mmdGroundShadowShaderIndex == -1)
			{
				SABA_ERROR("MMD Ground Shadow Material Shader not found.");
				return false;
			}
			if (!matShader.m_mmdGroundShadowVao.Create())
			{
				SABA_ERROR("Vertex Array Object Create fail.");
				return false;
			}

			auto groundShadowShader = m_drawContext->GetGroundShadowShader(
				matShader.m_mmdGroundShadowShaderIndex
			);

			glBindVertexArray(matShader.m_mmdGroundShadowVao);

			m_mmdModel->GetPositionBinder().Bind(edgeShader->m_inPos, m_mmdModel->GetPositionVBO());
			glEnableVertexAttribArray(groundShadowShader->m_inPos);

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_mmdModel->GetIBO());

			glBindVertexArray(0);

			// Add
			m_materialShaders.emplace_back(std::move(matShader));

			matIdx++;
		}
		return true;
	}

	void GLMMDModelDrawer::Destroy()
	{
		m_materialShaders.clear();
		m_selectedNode = nullptr;
	}

	void GLMMDModelDrawer::DrawUI(ViewerContext * ctxt)
	{
		if (ImGui::TreeNode("Bone"))
		{
			std::string name = "";
			if (m_selectedNode != nullptr)
			{
				name = m_selectedNode->GetName();
			}
			ImGui::Text("name:%s", name.c_str());

			if (ImGui::RadioButton("Local", m_viewLocal))
			{
				m_viewLocal = true;
			}
			if (ImGui::RadioButton("Global", !m_viewLocal))
			{
				m_viewLocal = false;
			}
			glm::vec3 t(0);
			glm::quat q;
			if (m_selectedNode != nullptr)
			{
				if (m_viewLocal)
				{
					auto local = m_selectedNode->GetLocalTransform();
					t = glm::vec3(local[3]);
					q = glm::quat_cast(local);
				}
				else
				{
					auto global = m_selectedNode->GetGlobalTransform();
					t = glm::vec3(global[3]);
					q = glm::quat_cast(global);
				}
			}
			ImGui::InputFloat3("T", &t[0], ImGuiInputTextFlags_ReadOnly);
			ImGui::InputFloat4("Q", &q[0], ImGuiInputTextFlags_ReadOnly);
			auto model = m_mmdModel->GetMMDModel();
			auto nodeMan = model->GetNodeManager();
			MMDNode* clickNode = nullptr;
			for (size_t nodeIdx = 0; nodeIdx < nodeMan->GetNodeCount(); nodeIdx++)
			{
				std::function<void(MMDNode*)> ViewNodes = [&ViewNodes, &clickNode, this](saba::MMDNode* node)
				{
					ImGuiTreeNodeFlags node_flags =
						ImGuiTreeNodeFlags_OpenOnArrow |
						ImGuiTreeNodeFlags_OpenOnDoubleClick |
						((this->m_selectedNode == node) ? ImGuiTreeNodeFlags_Selected : 0);
					if (node->GetChild() != nullptr)
					{
						if (ImGui::TreeNodeEx(node->GetName().c_str(), node_flags))
						{
							auto child = node->GetChild();
							while (child != nullptr)
							{
								ViewNodes(child);
								child = child->GetNext();
							}
							ImGui::TreePop();
						}
					}
					else
					{
						node_flags |=
							ImGuiTreeNodeFlags_Leaf |
							ImGuiTreeNodeFlags_NoTreePushOnOpen;
						ImGui::TreeNodeEx(node->GetName().c_str(), node_flags);
					}
					if (clickNode == nullptr && ImGui::IsItemClicked())
					{
						clickNode = node;
					}
				};
				auto node = nodeMan->GetMMDNode(nodeIdx);
				if (node->GetParent() == nullptr)
				{
					ViewNodes(node);
				}
			}

			ImGui::TreePop();
			if (clickNode != nullptr)
			{
				m_selectedNode = clickNode;
			}
		}
		if (ImGui::TreeNode("Physics"))
		{
			bool enabledPhysics = m_mmdModel->IsEnabledPhysics();
			if (ImGui::Checkbox("Enable", &enabledPhysics))
			{
				m_mmdModel->EnablePhysics(enabledPhysics);
			}
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("Morph"))
		{
			auto model = m_mmdModel->GetMMDModel();
			auto morphMan = model->GetMorphManager();
			size_t morphCount = morphMan->GetMorphCount();
			for (size_t morphIdx = 0; morphIdx < morphCount; morphIdx++)
			{
				auto morph = morphMan->GetMorph(morphIdx);
				float weight = morph->GetWeight();
				if (ImGui::SliderFloat(morph->GetName().c_str(), &weight, 0.0f, 1.0f))
				{
					morph->SetWeight(weight);
					auto animTime = ctxt->GetAnimationTime();
					m_mmdModel->UpdateMorph();
				}
			}
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("Edge"))
		{
			bool enableEdge = m_mmdModel->IsEnabledEdge();
			if (ImGui::Checkbox("Enable", &enableEdge))
			{
				m_mmdModel->EnableEdge(enableEdge);
			}
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("GraoundShadow"))
		{
			bool enableGroundShadow = m_mmdModel->IsEnableGroundShadow();
			if (ImGui::Checkbox("Enable", &enableGroundShadow))
			{
				m_mmdModel->EnableGroundShadow(enableGroundShadow);
			}
			ImGui::TreePop();
		}
	}

	void GLMMDModelDrawer::DrawShadowMap(ViewerContext * ctxt, size_t csmIdx)
	{
		const auto shadowMap = ctxt->GetShadowMap();
		const auto shader = shadowMap->GetShader();
		const auto& clipSpace = shadowMap->GetClipSpace(csmIdx);

		const auto& world = GetTransform();
		const auto& view = shadowMap->GetShadowViewMatrix();
		const auto& proj = clipSpace.m_projection;
		auto wvp = proj * view * world;

		glUseProgram(shader->m_prog);
		SetUniform(shader->m_uWVP, wvp);

		for (const auto& subMesh : m_mmdModel->GetSubMeshes())
		{
			int matID = subMesh.m_materialID;
			const auto& matShader = m_materialShaders[matID];
			const auto& mmdMat = m_mmdModel->GetMaterials()[matID];
			if (!mmdMat.m_shadowCaster)
			{
				continue;
			}

			glBindVertexArray(matShader.m_shadowVao);

			if (mmdMat.m_bothFace)
			{
				glDisable(GL_CULL_FACE);
			}
			else
			{
				glEnable(GL_CULL_FACE);
				glCullFace(GL_BACK);
			}

			size_t offset = subMesh.m_beginIndex * m_mmdModel->GetIndexTypeSize();
			glDrawElements(
				GL_TRIANGLES,
				subMesh.m_vertexCount,
				m_mmdModel->GetIndexType(),
				(GLvoid*)offset
			);

			glBindVertexArray(0);
		}

		glUseProgram(0);
	}

	void GLMMDModelDrawer::Play()
	{
	}

	void GLMMDModelDrawer::Stop()
	{
	}

	void GLMMDModelDrawer::ResetAnimation(ViewerContext * ctxt)
	{
		m_mmdModel->ResetAnimation();
	}

	void GLMMDModelDrawer::Update(ViewerContext * ctxt)
	{
		m_mmdModel->ClearPerfInfo();

		double animTime = ctxt->GetAnimationTime();
		double elapsed = ctxt->GetElapsed();
		if (ctxt->GetPlayMode() != ViewerContext::PlayMode::Stop)
		{
			m_mmdModel->UpdateAnimation(animTime, elapsed);
		}
		else
		{
			m_mmdModel->UpdateAnimationIgnoreVMD(elapsed);
		}

		m_mmdModel->Update();
	}


	void GLMMDModelDrawer::Draw(ViewerContext * ctxt)
	{
		const auto& view = ctxt->GetCamera()->GetViewMatrix();
		const auto& proj = ctxt->GetCamera()->GetProjectionMatrix();

		const auto& world = GetTransform();
		auto wv = view * world;
		auto wvp = proj * view * world;
		auto wvit = glm::mat3(view * world);
		wvit = glm::inverse(wvit);
		wvit = glm::transpose(wvit);

		const static size_t MaxShadowMap = 4;
		GLint shadowMapTexs[MaxShadowMap] = { 0 };
		glm::mat4 shadowMapVPs[MaxShadowMap];
		auto shadowMap = ctxt->GetShadowMap();
		size_t numShadowMap = glm::min(MaxShadowMap, shadowMap->GetShadowMapCount());
		const float* shadowMapSplitPositions = shadowMap->GetSplitPositions();
		size_t numShadowMapSplitPosition = glm::max(MaxShadowMap + 1, shadowMap->GetSplitPositionCount());
		if (ctxt->IsShadowEnabled())
		{
			for (size_t i = 0; i < numShadowMap; i++)
			{
				const auto& clipSpace = shadowMap->GetClipSpace(i);
				GLint texIdx = GLint(i + 3);
				glActiveTexture(GL_TEXTURE0 + texIdx);
				glBindTexture(GL_TEXTURE_2D, clipSpace.m_shadomap);

				glm::mat4 offset;
				offset[0] = glm::vec4(0.5f, 0.0f, 0.0f, 0.0f);
				offset[1] = glm::vec4(0.0f, 0.5f, 0.0f, 0.0f);
				offset[2] = glm::vec4(0.0f, 0.0f, 0.5f, 0.0f);
				offset[3] = glm::vec4(0.5f, 0.5f, 0.5f, 1.0f);
				glm::mat4 bias;
				bias[3][2] = -shadowMap->GetBias();
				shadowMapTexs[i] = texIdx;
				shadowMapVPs[i] = bias * offset * clipSpace.m_projection * shadowMap->GetShadowViewMatrix() * world;
			}
		}
		else
		{
			for (size_t i = 0; i < numShadowMap; i++)
			{
				const auto& clipSpace = shadowMap->GetClipSpace(i);
				GLint texIdx = GLint(i + 3);
				glActiveTexture(GL_TEXTURE0 + texIdx);
				glBindTexture(GL_TEXTURE_2D, ctxt->GetDummyShadowDepthTexture());
				shadowMapTexs[i] = texIdx;
			}
		}

		for (const auto& subMesh : m_mmdModel->GetSubMeshes())
		{
			int matID = subMesh.m_materialID;
			const auto& matShader = m_materialShaders[matID];
			const auto& mmdMat = m_mmdModel->GetMaterials()[matID];
			auto shader = m_drawContext->GetShader(matShader.m_mmdShaderIndex);

			if (mmdMat.m_alpha == 0.0f)
			{
				continue;
			}

			glUseProgram(shader->m_prog);
			glBindVertexArray(matShader.m_mmdVao);

			SetUniform(shader->m_uWV, wv);
			SetUniform(shader->m_uWVP, wvp);

			bool alphaBlend = true;

			SetUniform(shader->m_uAmbinet, mmdMat.m_ambient);
			SetUniform(shader->m_uDiffuse, mmdMat.m_diffuse);
			SetUniform(shader->m_uSpecular, mmdMat.m_specular);
			SetUniform(shader->m_uSpecularPower, mmdMat.m_specularPower);
			SetUniform(shader->m_uAlpha, mmdMat.m_alpha);

			glActiveTexture(GL_TEXTURE0 + 0);
			SetUniform(shader->m_uTex, (GLint)0);
			if (mmdMat.m_texture != 0)
			{
				if (!mmdMat.m_textureHaveAlpha)
				{
					// Use Material Alpha
					SetUniform(shader->m_uTexMode, (GLint)1);
				}
				else
				{
					// Use Material Alpha * Texture Alpha
					SetUniform(shader->m_uTexMode, (GLint)2);
				}
				SetUniform(shader->m_uTexMulFactor, mmdMat.m_textureMulFactor);
				SetUniform(shader->m_uTexAddFactor, mmdMat.m_textureAddFactor);
				glBindTexture(GL_TEXTURE_2D, mmdMat.m_texture);
			}
			else
			{
				SetUniform(shader->m_uTexMode, (GLint)0);
				glBindTexture(GL_TEXTURE_2D, ctxt->GetDummyColorTexture());
			}

			glActiveTexture(GL_TEXTURE0 + 1);
			SetUniform(shader->m_uSphereTex, (GLint)1);
			if (mmdMat.m_spTexture != 0)
			{
				if (mmdMat.m_spTextureMode == MMDMaterial::SphereTextureMode::Mul)
				{
					SetUniform(shader->m_uSphereTexMode, (GLint)1);
				}
				else if (mmdMat.m_spTextureMode == MMDMaterial::SphereTextureMode::Add)
				{
					SetUniform(shader->m_uSphereTexMode, (GLint)2);
				}
				SetUniform(shader->m_uSphereTexMulFactor, mmdMat.m_spTextureMulFactor);
				SetUniform(shader->m_uSphereTexAddFactor, mmdMat.m_spTextureAddFactor);
				glBindTexture(GL_TEXTURE_2D, mmdMat.m_spTexture);
			}
			else
			{
				SetUniform(shader->m_uSphereTexMode, (GLint)0);
				glBindTexture(GL_TEXTURE_2D, ctxt->GetDummyColorTexture());
			}

			glActiveTexture(GL_TEXTURE0 + 2);
			SetUniform(shader->m_uToonTex, 2);
			if (mmdMat.m_toonTexture != 0)
			{
				SetUniform(shader->m_uToonTexMulFactor, mmdMat.m_toonTextureMulFactor);
				SetUniform(shader->m_uToonTexAddFactor, mmdMat.m_toonTextureAddFactor);
				SetUniform(shader->m_uToonTexMode, (GLint)1);
				glBindTexture(GL_TEXTURE_2D, mmdMat.m_toonTexture);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			}
			else
			{
				SetUniform(shader->m_uToonTexMode, (GLint)0);
				glBindTexture(GL_TEXTURE_2D, ctxt->GetDummyColorTexture());
			}

			glm::vec3 lightColor = ctxt->GetLight()->GetLightColor();
			glm::vec3 lightDir = ctxt->GetLight()->GetLightDirection();
			glm::mat3 viewMat = glm::mat3(ctxt->GetCamera()->GetViewMatrix());
			lightDir = viewMat * lightDir;
			SetUniform(shader->m_uLightDir, lightDir);
			SetUniform(shader->m_uLightColor, lightColor);

			if (mmdMat.m_bothFace)
			{
				glDisable(GL_CULL_FACE);
			}
			else
			{
				glEnable(GL_CULL_FACE);
				glCullFace(GL_BACK);
			}

			if (alphaBlend)
			{
				glEnable(GL_BLEND);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			}
			else
			{
				glDisable(GL_BLEND);
			}

			if (ctxt->IsShadowEnabled() && mmdMat.m_shadowReceiver)
			{
				SetUniform(shader->m_uShadowMapEnabled, 1);
				SetUniform(shader->m_uShadowMap0, shadowMapTexs[0]);
				SetUniform(shader->m_uShadowMap1, shadowMapTexs[1]);
				SetUniform(shader->m_uShadowMap2, shadowMapTexs[2]);
				SetUniform(shader->m_uShadowMap3, shadowMapTexs[3]);
				SetUniform(shader->m_uShadowMapSplitPositions, shadowMapSplitPositions, numShadowMapSplitPosition);
				SetUniform(shader->m_uLightVP, shadowMapVPs, numShadowMap);
			}
			else
			{
				SetUniform(shader->m_uShadowMapEnabled, 0);
				SetUniform(shader->m_uShadowMap0, shadowMapTexs[0]);
				SetUniform(shader->m_uShadowMap1, shadowMapTexs[1]);
				SetUniform(shader->m_uShadowMap2, shadowMapTexs[2]);
				SetUniform(shader->m_uShadowMap3, shadowMapTexs[3]);
			}

			size_t offset = subMesh.m_beginIndex * m_mmdModel->GetIndexTypeSize();
			glDrawElements(
				GL_TRIANGLES,
				subMesh.m_vertexCount,
				m_mmdModel->GetIndexType(),
				(GLvoid*)offset
			);

			glActiveTexture(GL_TEXTURE0 + 2);
			glBindTexture(GL_TEXTURE_2D, 0);
			glActiveTexture(GL_TEXTURE0 + 1);
			glBindTexture(GL_TEXTURE_2D, 0);
			glActiveTexture(GL_TEXTURE0 + 0);
			glBindTexture(GL_TEXTURE_2D, 0);

			glBindVertexArray(0);
			glUseProgram(0);
		}

		for (size_t i = 0; i < MaxShadowMap; i++)
		{
			GLint texIdx = GLint(i + 3);
			glActiveTexture(GL_TEXTURE0 + texIdx);
			glBindTexture(GL_TEXTURE_2D, 0);
		}

		if (m_mmdModel->IsEnabledEdge())
		{
			glm::vec2 screenSize(ctxt->GetFrameBufferWidth(), ctxt->GetFrameBufferHeight());
			for (const auto& subMesh : m_mmdModel->GetSubMeshes())
			{
				int matID = subMesh.m_materialID;
				const auto& matShader = m_materialShaders[matID];
				const auto& mmdMat = m_mmdModel->GetMaterials()[matID];
				auto shader = m_drawContext->GetEdgeShader(matShader.m_mmdEdgeShaderIndex);

				if (!mmdMat.m_edgeFlag)
				{
					continue;
				}
				if (mmdMat.m_alpha == 0.0f)
				{
					continue;
				}

				glUseProgram(shader->m_prog);
				glBindVertexArray(matShader.m_mmdEdgeVao);

				SetUniform(shader->m_uWV, wv);
				SetUniform(shader->m_uWVP, wvp);
				SetUniform(shader->m_uScreenSize, screenSize);
				SetUniform(shader->m_uEdgeSize, mmdMat.m_edgeSize);
				SetUniform(shader->m_uEdgeColor, mmdMat.m_edgeColor);

				bool alphaBlend = true;

				glEnable(GL_CULL_FACE);
				glCullFace(GL_FRONT);

				if (alphaBlend)
				{
					glEnable(GL_BLEND);
					glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				}
				else
				{
					glDisable(GL_BLEND);
				}

				size_t offset = subMesh.m_beginIndex * m_mmdModel->GetIndexTypeSize();
				glDrawElements(
					GL_TRIANGLES,
					subMesh.m_vertexCount,
					m_mmdModel->GetIndexType(),
					(GLvoid*)offset
				);

				glBindVertexArray(0);
				glUseProgram(0);
			}
		}

		if (m_mmdModel->IsEnableGroundShadow())
		{
			glEnable(GL_POLYGON_OFFSET_FILL);
			glPolygonOffset(-1, -1);
			auto plane = glm::vec4(0, 1, 0, 0);
			auto light = -ctxt->GetLight()->GetLightDirection();
			auto shadow = glm::mat4();

			shadow[0][0] = plane.y * light.y + plane.z * light.z;
			shadow[0][1] = -plane.x * light.y;
			shadow[0][2] = -plane.x * light.z;
			shadow[0][3] = 0;

			shadow[1][0] = -plane.y * light.x;
			shadow[1][1] = plane.x * light.x + plane.z * light.z;
			shadow[1][2] = -plane.y * light.z;
			shadow[1][3] = 0;

			shadow[2][0] = -plane.z * light.x;
			shadow[2][1] = -plane.z * light.y;
			shadow[2][2] = plane.x * light.x + plane.y * light.y;
			shadow[2][3] = 0;

			shadow[3][0] = -plane.w * light.x;
			shadow[3][1] = -plane.w * light.y;
			shadow[3][2] = -plane.w * light.z;
			shadow[3][3] = plane.x * light.x + plane.y * light.y + plane.z * light.z;

			auto wsvp = proj * view * shadow * world;

			auto shadowColor = ctxt->GetMMDGroundShadowColor();
			if (shadowColor.a < 1.0f)
			{
				glEnable(GL_BLEND);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

				glStencilFuncSeparate(GL_FRONT_AND_BACK, GL_NOTEQUAL, 1, 1);
				glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
				glEnable(GL_STENCIL_TEST);
			}
			else
			{
				glDisable(GL_BLEND);
			}
			glDisable(GL_CULL_FACE);

			for (const auto& subMesh : m_mmdModel->GetSubMeshes())
			{
				int matID = subMesh.m_materialID;
				const auto& matShader = m_materialShaders[matID];
				const auto& mmdMat = m_mmdModel->GetMaterials()[matID];
				if (!mmdMat.m_groundShadow)
				{
					continue;
				}
				if (mmdMat.m_alpha == 0.0f)
				{
					continue;
				}

				auto shader = m_drawContext->GetGroundShadowShader(matShader.m_mmdGroundShadowShaderIndex);

				glUseProgram(shader->m_prog);
				glBindVertexArray(matShader.m_mmdGroundShadowVao);

				SetUniform(shader->m_uWVP, wsvp);
				SetUniform(shader->m_uShadowColor, shadowColor);

				size_t offset = subMesh.m_beginIndex * m_mmdModel->GetIndexTypeSize();
				glDrawElements(
					GL_TRIANGLES,
					subMesh.m_vertexCount,
					m_mmdModel->GetIndexType(),
					(GLvoid*)offset
				);

				glBindVertexArray(0);
				glUseProgram(0);
			}

			glDisable(GL_POLYGON_OFFSET_FILL);
			glDisable(GL_STENCIL_TEST);
			glDisable(GL_BLEND);
		}

		glBindVertexArray(0);
		glUseProgram(0);

		glDisable(GL_BLEND);
		glDisable(GL_CULL_FACE);
	}
}

