/********************************************************************
	Minecraft: Pocket Edition - Decompilation Project
	Copyright (C) 2023 iProgramInCpp

	The following code is licensed under the BSD 1 clause license.
	SPDX-License-Identifier: BSD-1-Clause
 ********************************************************************/

#include "thirdparty/GL/GL.hpp"
#include "GameRenderer.hpp"
#include "client/app/Minecraft.hpp"
#include "client/player/input/Multitouch.hpp"
#include "Frustum.hpp"
#include "renderer/GL/GL.hpp"

static int t_keepHitResult; // that is its address in v0.1.1j
int t_keepPic;

void GameRenderer::_init()
{
	//ItemInHandRenderer* m_pItemInHandRenderer = nullptr;

	field_8 = 0.0f;
	field_C = 0;
	field_10 = nullptr;
	field_14 = 0.0f;
	field_18 = 0.0f;
	field_1C = 0.0f;
	field_20 = 0.0f;
	field_24 = 0.0f;
	field_28 = 0.0f;
	field_2C = 4.0f;
	field_30 = 4.0f;
	field_34 = 0.0f;
	field_38 = 0.0f;
	field_3C = 0.0f;
	field_40 = 0.0f;
	field_44 = 1.0f;
	field_48 = 0.0f;
	field_4C = 0.0f;
	field_50 = 0.0f;
	field_54 = 0.0f;
	field_58 = 0.0f;
	field_5C = 0.0f;
	field_60 = 0.0f;
	field_64 = 0.0f;
	field_68 = 0.0f;
	field_6C = 0.0f;
	field_70 = 0.0f;
	field_74 = 0.0f;
	field_78 = 0.0f;
	field_7C = 0.0f;
	field_80 = 0.0f;
	field_84 = 0.0f;
	m_lastUpdatedMS = 0;
	m_shownFPS = 0;
	m_shownChunkUpdates = 0;
}

GameRenderer::GameRenderer(Minecraft* pMinecraft) :
	m_pMinecraft(pMinecraft)
{
	_init();

	saveMatrices();

	m_pItemInHandRenderer = new ItemInHandRenderer(pMinecraft);
	EntityRenderDispatcher::getInstance()->m_pItemInHandRenderer = m_pItemInHandRenderer;
}

GameRenderer::~GameRenderer()
{
	delete m_pItemInHandRenderer;
}

void GameRenderer::zoomRegion(float a, float b, float c)
{
	field_44 = a;
	field_48 = b;
	field_4C = c;
}

void GameRenderer::unZoomRegion()
{
	field_44 = 1.0f;
}

void GameRenderer::setupCamera(float f, int i)
{
	field_8 = float(256 >> m_pMinecraft->getOptions()->m_iViewDistance);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	if (m_pMinecraft->getOptions()->m_bAnaglyphs)
	{
		glTranslatef(float(1 - 2 * i) * 0.07f, 0.0f, 0.0f);
	}

	if (field_44 != 1.0)
	{
		glTranslatef(field_48, -field_4C, 0.0);
		glScalef(field_44, field_44, 1.0);
	}

	float fov = getFov(f);
	gluPerspective(fov, float(Minecraft::width) / float(Minecraft::height), 0.05f, field_8);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	if (m_pMinecraft->getOptions()->m_bAnaglyphs)
	{
		glTranslatef(float(2 * i - 1) * 0.1f, 0.0f, 0.0f);
	}

	bobHurt(f);
	if (m_pMinecraft->getOptions()->m_bViewBobbing)
		bobView(f);

	moveCameraToPlayer(f);
}

void GameRenderer::moveCameraToPlayer(float f)
{
	Mob* pMob = m_pMinecraft->m_pMobPersp;

	float headHeightDiff = pMob->field_84 - 1.62f;

	float posX = Lerp(pMob->field_3C.x, pMob->m_pos.x, f);
	float posY = Lerp(pMob->field_3C.y, pMob->m_pos.y, f);
	float posZ = Lerp(pMob->field_3C.z, pMob->m_pos.z, f);

	glRotatef(field_5C + f * (field_58 - field_5C), 0.0f, 0.0f, 1.0f);

	if (m_pMinecraft->getOptions()->m_bThirdPerson)
	{
		float v11 = field_30 + (field_2C - field_30) * f;
		if (m_pMinecraft->getOptions()->field_241)
		{
			glTranslatef(0.0f, 0.0f, -v11);
			glRotatef(field_38 + (field_34 - field_38) * f, 1.0f, 0.0f, 0.0f);
			glRotatef(field_40 + (field_3C - field_40) * f, 0.0f, 1.0f, 0.0f);
		}
		else
		{
			float mob_yaw = pMob->m_yaw;
			float mob_pitch = pMob->m_pitch;

			float pitchRad = mob_pitch / 180.0f * float(M_PI);

			float aX = posX - (-(Mth::sin(mob_yaw / 180.0f * float(M_PI)) * Mth::cos(pitchRad)) * v11);
			float aY = posY + (Mth::sin(pitchRad) * v11);
			float aZ = posZ - ((Mth::cos(mob_yaw / 180.0f * float(M_PI)) * Mth::cos(pitchRad)) * v11);

			for (int i = 0; i < 8; i++)
			{
				float offsX = (2 * (i & 1) - 1) * 0.1f;
				float offsY = ((i & 2) - 1) * 0.1f;
				float offsZ = (2 * ((i >> 2) & 1) - 1) * 0.1f;

				HitResult hr = m_pMinecraft->m_pLevel->clip(
					Vec3(posX + offsX, posY + offsY, posZ + offsZ),
					Vec3(aX + offsX + offsZ, aY + offsY, aZ + offsZ) // @NOTE: Not sure why it adds offsZ to offsX.
				);

				if (hr.m_hitType != HitResult::NONE)
				{
					float dX = posX - hr.m_hitPos.x;
					float dY = posY - hr.m_hitPos.y;
					float dZ = posZ - hr.m_hitPos.z;
					float dist = sqrtf(dX * dX + dY * dY + dZ * dZ);
					if (v11 > dist)
						v11 = dist;
				}
			}

			// @HUH: Why the hell is it rotating by 0
			glRotatef(pMob->m_pitch - mob_pitch, 1.0f, 0.0f, 0.0f);
			glRotatef(pMob->m_yaw - mob_yaw, 0.0f, 1.0f, 0.0f);
			glTranslatef(0.0, 0.0, -v11);
			glRotatef(mob_yaw - pMob->m_yaw, 0.0f, 1.0f, 0.0f);
			glRotatef(mob_pitch - pMob->m_pitch, 1.0f, 0.0f, 0.0f);
		}
	}
	else
	{
		glTranslatef(0.0f, 0.0f, -0.1f);
	}

	if (!m_pMinecraft->getOptions()->field_241)
	{
		glRotatef(pMob->field_60 + f * (pMob->m_pitch - pMob->field_60), 1.0f, 0.0f, 0.0f);
		glRotatef(pMob->field_5C + f * (pMob->m_yaw   - pMob->field_5C) + 180.0f, 0.0f, 1.0f, 0.0f);
	}

	glTranslatef(0.0f, headHeightDiff, 0.0f);
}

void GameRenderer::saveMatrices()
{
	glGetFloatv(GL_PROJECTION_MATRIX, m_matrix_projection);
	glGetFloatv(GL_MODELVIEW_MATRIX,  m_matrix_model_view);
}

void GameRenderer::setupGuiScreen()
{
	float x = Gui::InvGuiScale * Minecraft::width;
	float y = Gui::InvGuiScale * Minecraft::height;
	glClear(GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	xglOrthof(0, x, y, 0, 2000.0f, 3000.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0f, 0.0f, -2000.0f);
}

void GameRenderer::bobHurt(float f)
{
	Mob* pMob = m_pMinecraft->m_pMobPersp;

	if (pMob->m_health <= 0)
		glRotatef(-8000.0f / (float(pMob->field_110) + f + 200.0f) + 40.0f, 0.0f, 0.0f, 1.0f);

	if (pMob->field_104 > 0)
	{
		float val = (pMob->field_104 - f) / pMob->field_108;

		glRotatef(-pMob->field_10C, 0.0f, 1.0f, 0.0f);
		glRotatef(Mth::sin(val * val * val * val * 3.1416f) * -14.0f, 0.0f, 0.0f, 1.0f);
		glRotatef(pMob->field_10C, 0.0f, 1.0f, 0.0f);
	}
}

void GameRenderer::bobView(float f)
{
	if (!m_pMinecraft->m_pMobPersp->isPlayer())
		return;

	Player* player = (Player*)m_pMinecraft->m_pMobPersp;
	float f1 = Lerp(player->field_B9C, player->field_BA0, f);
	float f2 = Lerp(player->field_118, player->field_11C, f);
	// @NOTE: Multiplying by M_PI inside of the paren makes it stuttery for some reason? Anyways it works now :)
	float f3 = -(player->field_94 + (player->field_94 - player->field_90) * f) * float(M_PI);
	float f4 = Mth::sin(f3);
	float f5 = Mth::cos(f3);
	glTranslatef((f4 * f1) * 0.5f, -fabsf(f5 * f1), 0.0f);
	float f6 = Mth::sin(f3);
	glRotatef((f6 * f1) * 3.0f, 0.0f, 0.0f, 1.0f);
	float f7 = Mth::cos(f3 - 0.2f);
	glRotatef(fabsf(f7 * f1) * 5.0f, 1.0f, 0.0f, 0.0f);
	glRotatef(f2, 1.0f, 0.0f, 0.0f);
}

void GameRenderer::setupClearColor(float f)
{
	Minecraft* pMC = m_pMinecraft;
	Level* pLevel = pMC->m_pLevel;
	Mob* pMob = pMC->m_pMobPersp;

	float x1 = 1.0f - powf(1.0f / float(4 - pMC->getOptions()->m_iViewDistance), 0.25f);

	Vec3 skyColor = pLevel->getSkyColor(pMob, f), fogColor = pLevel->getFogColor(f);

	//@BUG: double set to these?
	field_60 = fogColor.x;
	field_64 = fogColor.y;

	field_60 = fogColor.x + (skyColor.x - fogColor.x) * x1;
	field_64 = fogColor.y + (skyColor.y - fogColor.y) * x1;
	field_68 = fogColor.z + (skyColor.z - fogColor.z) * x1;

	if (pMob->isUnderLiquid(Material::water))
	{
		field_60 = 0.02f;
		field_64 = 0.02f;
		field_68 = 0.2f;
	}
	else if (pMob->isUnderLiquid(Material::lava))
	{
		field_60 = 0.6f;
		field_64 = 0.1f;
		field_68 = 0.0f;
	}

	float x2 = field_6C + (field_70 - field_6C) * f;

	field_60 *= x2;
	field_64 *= x2;
	field_68 *= x2;

	if (pMC->getOptions()->m_bAnaglyphs)
	{
		float r = (field_60 * 30.0f + field_64 * 59.0f + field_68 * 11.0f) / 100.0f;
		float g = (field_60 * 30.0f + field_64 * 70.0f) / 100.0f;
		float b = (field_60 * 30.0f + field_68 * 70.0f) / 100.0f;

		field_60 = r;
		field_64 = g;
		field_68 = b;
	}

	glClearColor(field_60, field_64, field_68, 1.0f);
}

#ifndef ORIGINAL_CODE
void GameRenderer::renderNoCamera()
{
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
#endif

void GameRenderer::setupFog(int i)
{
	float fog_color[4];
	fog_color[0] = field_60;
	fog_color[1] = field_64;
	fog_color[2] = field_68;
	fog_color[3] = 1.0f;

	glFogfv(GL_FOG_COLOR, fog_color);
	glNormal3f(0.0f, -1.0f, 0.0f);
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

	if (m_pMinecraft->m_pMobPersp->isUnderLiquid(Material::water))
	{
	#if defined(ORIGINAL_CODE) || defined(__ANDROID__)
		glFogx(GL_FOG_MODE, GL_EXP);
	#else
		glFogi(GL_FOG_MODE, GL_EXP);
	#endif

		glFogf(GL_FOG_DENSITY, 0.1f);
	}
	else if (m_pMinecraft->m_pMobPersp->isUnderLiquid(Material::lava))
	{
	#if defined(ORIGINAL_CODE) || defined(__ANDROID__)
		glFogx(GL_FOG_MODE, GL_EXP);
	#else
		glFogi(GL_FOG_MODE, GL_EXP);
	#endif

		glFogf(GL_FOG_DENSITY, 2.0f);
	}
	else
	{
	#if defined(ORIGINAL_CODE) || defined(__ANDROID__)
		glFogx(GL_FOG_MODE, GL_LINEAR);
	#else
		glFogi(GL_FOG_MODE, GL_LINEAR);
	#endif

		glFogf(GL_FOG_START, field_8 * 0.25f);
		glFogf(GL_FOG_END, field_8);
		if (i < 0)
		{
			glFogf(GL_FOG_START, 0.0f);
			glFogf(GL_FOG_END, field_8 * 0.8f);
		}

		if (m_pMinecraft->m_pLevel->m_pDimension->field_C)
		{
			glFogf(GL_FOG_START, 0.0f);
		}

	}

	glEnable(GL_COLOR_MATERIAL);
}

float GameRenderer::getFov(float f)
{
	Mob* pMob = m_pMinecraft->m_pMobPersp;

	float x1 = 70.0f;

	if (pMob->isUnderLiquid(Material::water))
		x1 = 60.0f;

	if (pMob->m_health <= 0)
	{
		float x2 = 1.0f + (-500.0f / ((pMob->field_110 + f) + 500.0f));
		x1 /= (1.0f + 2.0f * x2);
	}

	return field_54 + x1 + f * (field_50 - field_54);
}

void GameRenderer::renderLevel(float f)
{
	if (!m_pMinecraft->m_pMobPersp)
	{
		m_pMinecraft->m_pMobPersp = m_pMinecraft->m_pLocalPlayer;

		if (!m_pMinecraft->m_pMobPersp)
		{
		#ifndef ORIGINAL_CODE
			renderNoCamera();
		#endif
			return;
		}
	}

	pick(f);

	Mob* pMob = m_pMinecraft->m_pMobPersp;
	Vec3 fCamPos;

	fCamPos.x = pMob->field_98.x + (pMob->m_pos.x - pMob->field_98.x) * f;
	fCamPos.y = pMob->field_98.y + (pMob->m_pos.y - pMob->field_98.y) * f;
	fCamPos.z = pMob->field_98.z + (pMob->m_pos.z - pMob->field_98.z) * f;

	bool bAnaglyph = m_pMinecraft->getOptions()->m_bAnaglyphs;

	LevelRenderer* pLR = m_pMinecraft->m_pLevelRenderer;
	ParticleEngine* pPE = m_pMinecraft->m_pParticleEngine;

	for (int i = 0; i < 2; i++)
	{
		if (bAnaglyph)
		{
			if (i > 0)
				glColorMask(true, false, false, false);
			else
				glColorMask(false, true, true, false);
		}

		glViewport(0, 0, Minecraft::width, Minecraft::height);
		setupClearColor(f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glEnable(GL_CULL_FACE);
		setupCamera(f, i);
		saveMatrices();

		/*
		if (m_pMinecraft->getOptions()->m_iViewDistance <= 1)
		{
#ifndef ORIGINAL_CODE
			// @NOTE: For whatever reason, Minecraft doesn't enable GL_FOG right away.
			// It appears to work in bluestacks for whatever reason though...
			glEnable(GL_FOG);
#endif
			setupFog(-1);
			pLR->renderSky(f);
		}
		*/

		glEnable(GL_FOG);
		setupFog(1);

		if (m_pMinecraft->getOptions()->m_bAmbientOcclusion)
			glShadeModel(GL_SMOOTH);

		Frustum& frust = Frustum::frustum;
		Frustum::doOurJobInGameRenderer();

		FrustumCuller frustumCuller;
		frustumCuller.m_frustumData.x = frust;
		frustumCuller.prepare(fCamPos.x, fCamPos.y, fCamPos.z);

		pLR->cull(&frustumCuller, f);
		pLR->updateDirtyChunks(pMob, false);

		// TODO[v0.6.1]: what is (this+4)+63 (byte)?
		prepareAndRenderClouds(pLR, f);

		setupFog(0);
		glEnable(GL_FOG);

		m_pMinecraft->m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
		// render the opaque layer
		pLR->render(pMob, 0, f);

		glShadeModel(GL_FLAT);
		pLR->renderEntities(pMob->getPos(f), &frustumCuller, f);
		pPE->render(pMob, f);

		// @BUG: The original demo calls GL_BLEND. We really should be enabling GL_BLEND.
		//glEnable(GL_ALPHA);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		setupFog(0);

#ifndef ORIGINAL_CODE
		glShadeModel(GL_SMOOTH);
#endif

		glEnable(GL_BLEND);
		glDisable(GL_CULL_FACE);
		// glDepthMask(false); -- added in 0.1.1j. Introduces more issues than fixes

		// render the alpha layer
		m_pMinecraft->m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
		pLR->render(pMob, 1, f);

		glDepthMask(true);

#ifndef ORIGINAL_CODE
		glShadeModel(GL_FLAT);
#endif
		glEnable(GL_CULL_FACE);
		glDisable(GL_BLEND);

		if (field_44 == 1.0f && pMob->isPlayer() && m_pMinecraft->m_hitResult.m_hitType != HitResult::NONE && !pMob->isUnderLiquid(Material::water))
		{
			glDisable(GL_ALPHA_TEST);

			if (m_pMinecraft->getOptions()->m_bBlockOutlines)
				pLR->renderHitOutline((Player*)pMob, m_pMinecraft->m_hitResult, 0, nullptr, f);
			else
				pLR->renderHitSelect((Player*)pMob, m_pMinecraft->m_hitResult, 0, nullptr, f);

			// added by iProgramInCpp - renders the cracks
			pLR->renderHit((Player*)pMob, m_pMinecraft->m_hitResult, 0, nullptr, f);

			glEnable(GL_ALPHA_TEST);
		}

		// @BUG?
		glDisable(GL_FOG);

		if (field_44 == 1.0f)
		{
			glClear(GL_DEPTH_BUFFER_BIT);
			renderItemInHand(f, i);
		}

		if (!bAnaglyph)
			break;
	}

	if (bAnaglyph)
		glColorMask(true, true, true, false);
}

void GameRenderer::render(float f)
{
	if (m_pMinecraft->m_pLocalPlayer && m_pMinecraft->m_bGrabbedMouse)
	{
		Minecraft *pMC = m_pMinecraft;
		pMC->m_mouseHandler.poll();

		float multPitch, diff_field_84;
		if (pMC->m_mouseHandler.smoothTurning())
		{
			multPitch = -1.0f;
			float mult1 = 2.0f * (0.2f + pMC->getOptions()->field_8 * 0.6f);
			mult1 = mult1 * mult1 * mult1;

			float xd = 4.0f * mult1 * pMC->m_mouseHandler.m_delta.x;
			float yd = 4.0f * mult1 * pMC->m_mouseHandler.m_delta.y;

			float old_field_84 = field_84;
			field_84 = float(field_C) + f;
			diff_field_84 = field_84 - old_field_84;
			field_74 += xd;
			field_78 += yd;

			if (diff_field_84 > 3.0f)
				diff_field_84 = 3.0f;

			if (pMC->getOptions()->m_bInvertMouse)
				multPitch = 1.0f;

			if (!pMC->getOptions()->field_240)
			{
				// @TODO: untangle this code
				float v17 = xd + field_14;
				float v18 = field_18;
				float v19 = field_1C;
				field_14 = v17;
				float v20 = mult1 * 0.25f * (v17 - v18);
				float v21 = v19 + (v20 - v19) * 0.5f;
				field_1C = v21;
				if ((v20 <= 0.0 || v20 <= v21) && (v20 >= 0.0 || v20 >= v21))
					v21 = mult1 * 0.25f * (v17 - v18);
				float v22 = yd + field_20;
				field_18 = v18 + v21;
				float v23 = field_24;
				field_20 = v22;
				float v24 = mult1 * 0.15f * (v22 - v23);
				float v25 = field_28 + (v24 - field_28) * 0.5f;
				field_28 = v25;
				if ((v24 <= 0.0 || v24 <= v25) && (v24 >= 0.0 || v24 >= v25))
					v25 = v24;
				field_24 = v23 + v25;
			}
		}
		else
		{
			multPitch = -1.0f;
			if (pMC->getOptions()->m_bInvertMouse)
				multPitch = 1.0f;

			diff_field_84 = 1.0f;
			field_7C = pMC->m_mouseHandler.m_delta.x;
			field_80 = pMC->m_mouseHandler.m_delta.y;
		}

		pMC->m_pLocalPlayer->turn(diff_field_84 * field_7C, diff_field_84 * multPitch * field_80);
	}

	int mouseX = int(Mouse::getX() * Gui::InvGuiScale);
	int mouseY = int(Mouse::getY() * Gui::InvGuiScale);

	if (m_pMinecraft->isTouchscreen())
	{
		int pointerId = Multitouch::getFirstActivePointerIdExThisUpdate();
		if (pointerId < 0)
		{
			mouseX = -9999;
			mouseY = -9999;
		}
		else
		{
			mouseX = int(float(Multitouch::getX(pointerId)) * Gui::InvGuiScale);
			mouseY = int(float(Multitouch::getY(pointerId)) * Gui::InvGuiScale);
		}
	}

	if (m_pMinecraft->isLevelGenerated())
	{
		if (t_keepPic < 0)
		{
			renderLevel(f);
			if (m_pMinecraft->getOptions()->m_bDontRenderGui)
			{
				if (!m_pMinecraft->m_pScreen)
					return;
			}

			m_pMinecraft->m_gui.render(f, m_pMinecraft->m_pScreen != nullptr, mouseX, mouseY);
		}
	}
	else
	{
		glViewport(0, 0, Minecraft::width, Minecraft::height);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		setupGuiScreen();
	}

	if (m_pMinecraft->m_pLocalPlayer &&
		m_pMinecraft->m_pLocalPlayer->m_pMoveInput)
		m_pMinecraft->m_pLocalPlayer->m_pMoveInput->render(f);

	if (m_pMinecraft->m_pScreen)
	{
		glClear(GL_DEPTH_BUFFER_BIT);
		m_pMinecraft->m_pScreen->onRender(mouseX, mouseY, f);

		if (m_pMinecraft->m_pScreen && !m_pMinecraft->m_pScreen->isInGameScreen())
		{
#ifdef ORIGINAL_CODE
			// force some lag for some reason. I guess it's to make it spend more time actually generating the world?
			sleepMs(15);
#endif
		}
	}

	std::stringstream debugText;
	debugText << "ReMinecraftPE " << m_pMinecraft->getVersionString();
	debugText << "\n" << m_shownFPS << " fps, " << m_shownChunkUpdates << " chunk updates";

	if (m_pMinecraft->getOptions()->m_bDebugText)
	{
		if (m_pMinecraft->m_pLocalPlayer)
		{
			char posStr[96];
			Vec3 pos = m_pMinecraft->m_pLocalPlayer->getPos(f);
			sprintf(posStr, "%.2f, %.2f, %.2f", pos.x, pos.y, pos.z);

			debugText << "\npos: " << posStr;
			debugText << "\nentities: " << m_pMinecraft->m_pLevel->m_entities.size();
			debugText << "\n" << m_pMinecraft->m_pLevelRenderer->gatherStats1();
		}

		m_pMinecraft->m_pFont->drawShadow(debugText.str(), 2, 2, 0xFFFFFF);
	}

	int timeMs = getTimeMs();
	if (timeMs - m_lastUpdatedMS >= 1000)
	{
		m_lastUpdatedMS = timeMs;
		m_shownFPS = m_pMinecraft->getFpsIntlCounter();
		m_shownChunkUpdates = Chunk::updates;
		Chunk::updates = 0;
	}
}

void GameRenderer::tick()
{
	--t_keepPic;
#ifndef ORIGINAL_CODE
	// @BUG: If the game is left on for approximately 1,242 days, the counter will underflow,
	// causing the screen to appear frozen, and the level to not render.
	if (t_keepPic < -100)
		t_keepPic = -100;
#endif

	if (!m_pMinecraft->m_pLocalPlayer)
		return;
	
	if (--t_keepHitResult == 0)
		m_pMinecraft->m_hitResult.m_hitType = HitResult::NONE;

#ifndef ORIGINAL_CODE
	// Not harmless to let it underflow, but we won't anyway
	if (t_keepHitResult < -100)
		t_keepHitResult = -100;
#endif

	float x1 = powf(fabsf(field_74), 1.2f);
	field_7C = x1 * 0.4f;
	if (field_74 < 0.0f)
		field_7C = -field_7C;

	float x2 = powf(fabsf(field_78), 1.2f);
	field_80 = x2 * 0.4f;
	if (field_78 < 0.0f)
		field_80 = -field_80;

	field_74 = 0.0f;
	field_78 = 0.0f;
	field_6C = field_70;
	field_30 = field_2C;
	field_38 = field_34;
	field_40 = field_3C;
	field_54 = field_50;
	field_5C = field_58;

	Mob* pMob = m_pMinecraft->m_pMobPersp;
	if (!pMob)
	{
		pMob = m_pMinecraft->m_pMobPersp = m_pMinecraft->m_pLocalPlayer;
	}

	float bright = m_pMinecraft->m_pLevel->getBrightness(Mth::floor(pMob->m_pos.x), Mth::floor(pMob->m_pos.y), Mth::floor(pMob->m_pos.z));
	float x3 = float(3 - m_pMinecraft->getOptions()->m_iViewDistance);

	field_C++;

	float x4 = x3 / 3.0f;
	float x5 = (x4 + bright * (1.0f - x4) - field_70) * 0.1f;

	field_70 += x5;

	m_pItemInHandRenderer->tick();
}

void GameRenderer::renderItemInHand(float f, int i)
{
	glLoadIdentity();

	if (m_pMinecraft->getOptions()->m_bAnaglyphs)
		glTranslatef(float(2 * i - 1) * 0.1f, 0.0f, 0.0f);

	glPushMatrix();
	bobHurt(f);

	if (m_pMinecraft->getOptions()->m_bViewBobbing)
		bobView(f);

	if (!m_pMinecraft->getOptions()->m_bThirdPerson && !m_pMinecraft->getOptions()->m_bDontRenderGui)
		m_pItemInHandRenderer->render(f);

	glPopMatrix();

	if (!m_pMinecraft->getOptions()->m_bThirdPerson)
	{
		m_pItemInHandRenderer->renderScreenEffect(f);
		bobHurt(f);
	}

	if (m_pMinecraft->getOptions()->m_bViewBobbing)
		bobView(f);
}

void GameRenderer::prepareAndRenderClouds(LevelRenderer* pLR, float f)
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluPerspective(getFov(f), float(Minecraft::width) / float(Minecraft::height), 0.05f, field_8 * 512.0f);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	setupFog(0);
	glDepthMask(false);
	glEnable(GL_FOG);
	glFogf(GL_FOG_START, field_8 * 0.2f);
	glFogf(GL_FOG_END,   field_8 * 0.75f);
	pLR->renderSky(f);
	glFogf(GL_FOG_START, field_8 * 4.2f * 0.6f);
	glFogf(GL_FOG_END,   field_8 * 4.2f);
	pLR->renderClouds(f);
	glFogf(GL_FOG_START, field_8 * 0.6f);
	glFogf(GL_FOG_END,   field_8);
	glDisable(GL_FOG);
	glDepthMask(true);
	setupFog(1);
	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

void GameRenderer::onGraphicsReset()
{

}

void GameRenderer::pick(float f)
{
	if (!m_pMinecraft->m_pMobPersp || !m_pMinecraft->m_pLevel)
		return;

	Mob* pMob = m_pMinecraft->m_pMobPersp;
	HitResult& mchr = m_pMinecraft->m_hitResult;
	float dist = m_pMinecraft->m_pGameMode->getPickRange();
	bool isFirstPerson = !m_pMinecraft->getOptions()->m_bThirdPerson;

	if (!m_pMinecraft->useSplitControls())
	{
		Vec3 mobPos = pMob->getPos(f);
		Vec3 foundPosNear, foundPosFar;
		bool flag = true;
		float offset = isFirstPerson ? 6.0f : 12.0f;

		if (m_pMinecraft->m_pInputHolder->allowPicking())
		{
			int viewport[4] = { 0 };
			viewport[2] = Minecraft::width;
			viewport[3] = Minecraft::height;
			float obj_coord[3] = { 0 };

			if (glhUnProjectf(m_pMinecraft->m_pInputHolder->m_feedbackX,
				              Minecraft::height - m_pMinecraft->m_pInputHolder->m_feedbackY,
				              1.0f,
				              m_matrix_model_view,
				              m_matrix_projection,
				              viewport,
				              obj_coord))
			{
				foundPosFar = mobPos + Vec3(obj_coord[0], obj_coord[1], obj_coord[2]);

				glhUnProjectf(m_pMinecraft->m_pInputHolder->m_feedbackX,
				              Minecraft::height - m_pMinecraft->m_pInputHolder->m_feedbackY,
				              0.0f,
				              m_matrix_model_view,
				              m_matrix_projection,
				              viewport,
				              obj_coord);

				foundPosNear = mobPos + Vec3(obj_coord[0], obj_coord[1], obj_coord[2]);

				Vec3 diff = foundPosFar - foundPosNear;
				Vec3 normDiff = diff.normalize();
				Vec3 normScaledDiff = normDiff.scale(offset);
				
				mobPos = foundPosNear + normScaledDiff;

				foundPosFar = mobPos;
			}
			
			// keep the hit result forever
			t_keepHitResult = -1;
		}
		else
		{
			t_keepHitResult = 1; // keep the tick result for exactly one frame
			flag = false;
		}

		if (flag)
		{
			if (isFirstPerson)
			{
				mchr = m_pMinecraft->m_pLevel->clip(foundPosNear, foundPosFar, false);
			}
			else
			{
				HitResult hr = m_pMinecraft->m_pLevel->clip(foundPosNear, foundPosFar, false);

				float diffX = float(hr.m_tileX) - m_pMinecraft->m_pMobPersp->m_pos.x;
				float diffY = float(hr.m_tileY) - m_pMinecraft->m_pMobPersp->m_pos.y;
				float diffZ = float(hr.m_tileZ) - m_pMinecraft->m_pMobPersp->m_pos.z;

				if (hr.m_hitType == HitResult::NONE || diffX * diffX + diffY * diffY + diffZ * diffZ > offset * offset)
					mchr.m_hitType = HitResult::NONE;
				else
					mchr = hr;
			}
		}
	}
	else
	{
		// easy case: pick from the middle of the screen
		HitResult hrMob = pMob->pick(dist, f);
		mchr = hrMob;
	}

	Vec3 mobPos = pMob->getPos(f);

	if (m_pMinecraft->m_hitResult.m_hitType != HitResult::NONE)
		dist = mchr.m_hitPos.distanceTo(mobPos);

	if (m_pMinecraft->m_pGameMode->isCreativeType())
		dist = 32.0f;
	else if (dist > 3.0f)
		dist = 3.0f;

	Vec3 view = pMob->getViewVector(f);
	Vec3 exp  = view * dist;
	Vec3 limit = mobPos + view * dist;

	field_10 = nullptr;

	AABB scanAABB = pMob->m_hitbox;

	if (exp.x < 0) scanAABB.min.x += exp.x;
	if (exp.x > 0) scanAABB.max.x += exp.x;
	if (exp.y < 0) scanAABB.min.y += exp.y;
	if (exp.y > 0) scanAABB.max.y += exp.y;
	if (exp.z < 0) scanAABB.min.z += exp.z;
	if (exp.z > 0) scanAABB.max.z += exp.z;

	scanAABB.grow(1, 1, 1);

	EntityVector* pEnts = m_pMinecraft->m_pLevel->getEntities(pMob, scanAABB);

	float fDist = 0.0f;
	for (int i = 0; i < int(pEnts->size()); i++)
	{
		Entity *pEnt = (*pEnts)[i];
		if (!pEnt->isPickable())
			continue;

		AABB checkAABB = pEnt->m_hitbox;
		checkAABB.grow(pEnt->getPickRadius());

		HitResult hrMobChk = checkAABB.clip(mobPos, limit);

		if (checkAABB.contains(mobPos))
		{
			if (fDist >= 0.0f)
			{
				//this is it brother
				field_10 = pEnt;
				fDist = 0.0f;
			}
			continue;
		}

		if (hrMobChk.m_hitType != HitResult::NONE)
		{
			float dX = hrMobChk.m_hitPos.x - mobPos.x;
			float dY = hrMobChk.m_hitPos.y - mobPos.y;
			float dZ = hrMobChk.m_hitPos.z - mobPos.z;
			float fNewDist = sqrtf(dX * dX + dY * dY + dZ * dZ);

			if (fDist > fNewDist || fDist == 0.0f)
			{
				field_10 = pEnt;
				fDist = fNewDist;
			}
		}
	}

	// picked entities take priority over tiles (?!)
	if (field_10)
	{
		m_pMinecraft->m_hitResult = HitResult(field_10);
		return;
	}

	if (m_pMinecraft->m_hitResult.m_hitType != HitResult::NONE || view.y >= -0.7f)
		return;

	mobPos = pMob->getPos(f);
	Vec3 checkVec = mobPos;
	checkVec.translate(0, -2, 0);

	HitResult hrLevelChk = m_pMinecraft->m_pLevel->clip(mobPos, checkVec);

	if (hrLevelChk.m_hitType == HitResult::NONE)
		return;

	mchr = hrLevelChk;
	mchr.m_bUnk24 = true;

	if (fabsf(view.x) <= fabsf(view.z))
	{
		m_pMinecraft->m_hitResult.m_hitSide = view.z >= 0.0f ? HitResult::MAXZ : HitResult::MINZ;
	}
	else
	{
		m_pMinecraft->m_hitResult.m_hitSide = view.x >= 0.0f ? HitResult::MAXX : HitResult::MINX;
	}
}

