#include "GameRenderer.h"
#include "gles.h"

#include "../../util/PerfTimer.h"

#include "LevelRenderer.h"
#include "ItemInHandRenderer.h"
#include "culling/AllowAllCuller.h"
#include "culling/FrustumCuller.h"
#include "entity/EntityRenderDispatcher.h"
#include "../Minecraft.h"
#include "../gamemode/GameMode.h"
#include "../particle/ParticleEngine.h"
#include "../player/LocalPlayer.h"
#include "../gui/Screen.h"
#include "../../world/level/Level.h"
#include "../../world/entity/Mob.h"
#include "../../world/level/chunk/ChunkCache.h"
#include "../../world/level/material/Material.h"
#include "../../world/Facing.h"
#include "../../platform/input/Controller.h"
#include "../../platform/input/Mouse.h"
#include "../../platform/input/Multitouch.h"
#include "../../NinecraftApp.h"
#include "../../world/level/tile/Tile.h"
#include "../player/input/IInputHolder.h"
#include "Textures.h"
#include "../gui/components/ImageButton.h"
#include "Tesselator.h"

static int _shTicks = -1;

GameRenderer::GameRenderer( Minecraft* mc )
:	mc(mc),
	renderDistance(0),
	_tick(0),
	_lastTickT(0),
	fovOffset(0),
	fovOffsetO(0),
	fov(1), oFov(1),
	_setupCameraFov(0),
	zoom(1), zoom_x(0), zoom_y(0),
	cameraRoll(0), cameraRollO(0),
	pickDirection(1, 0, 0),

	thirdDistance(4), thirdDistanceO(4),
	thirdRotation(0), thirdRotationO(0),
	thirdTilt(0), thirdTiltO(0),

	fogBr(0), fogBrO(0),

	fr(0), fg(0), fb(0),
	_rotX(0), _rotY(0),
	_rotXlast(0), _rotYlast(0),
	useScreenScissor(false)
{
	saveMatrices();

	itemInHandRenderer = new ItemInHandRenderer(mc);

	EntityRenderDispatcher* e = EntityRenderDispatcher::getInstance();
	e->itemInHandRenderer = itemInHandRenderer;
	e->textures = mc->textures;
}

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

void renderCursor(float x, float y, Minecraft* minecraft) {
	Tesselator& t = Tesselator::instance;

	minecraft->textures->loadAndBindTexture("gui/cursor.png");
	glEnable(GL_BLEND);

	const float s = 32;
	const float width = 16;
	const float height = 16;
	t.begin();
	t.color(0xffffffff);
	t.vertexUV(x, y + (float)height, 0, 0, 1);
	t.vertexUV(x + (float)width, y + (float)height, 0, 1, 1);
	t.vertexUV(x + (float)width, y, 0, 1, 0);
	t.vertexUV(x, y, 0, 0, 0);
	t.draw();

	glDisable(GL_BLEND);
}

/*private*/
void GameRenderer::setupCamera(float a, int eye) {
    renderDistance = (float) (16 * 16 >> (mc->options.viewDistance));
#if defined(ANDROID)
    if (mc->isPowerVR() && mc->options.viewDistance <= 2)
		renderDistance *= 0.8f;
#endif

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity2();

    float stereoScale = 0.07f;
    if (mc->options.anaglyph3d) glTranslatef2(-(eye * 2 - 1) * stereoScale, 0, 0);
    if (zoom != 1) {
        glTranslatef2((float) zoom_x, (float) -zoom_y, 0);
		glScalef2(zoom, zoom, 1);
        gluPerspective(_setupCameraFov = getFov(a, true), mc->width / (float) mc->height, 0.05f, renderDistance);
    } else {
        gluPerspective(_setupCameraFov = getFov(a, true), mc->width / (float) mc->height, 0.05f, renderDistance);
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity2();
    if (mc->options.anaglyph3d) glTranslatef2((eye * 2 - 1) * 0.10f, 0, 0);

    bobHurt(a);
    if (mc->options.bobView) bobView(a);
	 
	moveCameraToPlayer(a);
}

extern int _t_keepPic;

/*public*/
void GameRenderer::render(float a) {
	TIMER_PUSH("mouse");
	if (mc->player && mc->mouseGrabbed) {
        mc->mouseHandler.poll();
        //printf("Controller.x,y : %f,%f\n", Controller::getX(0), Controller::getY(0));

        float ss = mc->options.sensitivity * 0.6f + 0.2f;
        float sens = (ss * ss * ss) * 8;
        float xo = mc->mouseHandler.xd * sens * 4.f;
        float yo = mc->mouseHandler.yd * sens * 4.f;

		const float now = _tick + a;
		float deltaT = now - _lastTickT;
		if (deltaT > 3.0f) deltaT = 3.0f;
		_lastTickT = now;

		_rotX += xo;
		_rotY += yo;

        int yAxis = -1;
        if (mc->options.invertYMouse) yAxis = 1;

		bool screenCovering = mc->screen && !mc->screen->passEvents;
		if (!screenCovering)
		{
			mc->player->turn(deltaT * _rotXlast, deltaT * _rotYlast * yAxis);
		}
    }

	int xMouse = (int)(Mouse::getX() * Gui::InvGuiScale);
	int yMouse = (int)(Mouse::getY() * Gui::InvGuiScale);
#ifndef PLATFORM_DESKTOP
    if (mc->useTouchscreen()) {
		const int pid = Multitouch::getFirstActivePointerIdExThisUpdate();
		if (pid >= 0) {
			xMouse = (int)(Multitouch::getX(pid) * Gui::InvGuiScale);
			yMouse = (int)(Multitouch::getY(pid) * Gui::InvGuiScale);
		} else {
			xMouse = -9999;
			yMouse = -9999;
		}
	}
#endif
	TIMER_POP();

	bool hasClearedColorBuffer = false;
	bool hasSetupGuiScreen = false;
	useScreenScissor = false;
	if (mc->isLevelGenerated()) {

		TIMER_PUSH("level");
		if (_t_keepPic < 0) {
		if (!(mc->screen && !mc->screen->renderGameBehind())) {

			if (mc->screen && mc->screen->hasClippingArea(screenScissorArea))
				useScreenScissor = true;

			renderLevel(a);
			hasClearedColorBuffer = true;

			if (!mc->options.hideGui) {
				TIMER_POP_PUSH("gui");
				setupGuiScreen(false);
				hasSetupGuiScreen = true;
				mc->gui.render(a, mc->screen != NULL, xMouse, yMouse);
			}
		}}
		TIMER_POP();

	} else {
        glViewport(0, 0, mc->width, mc->height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity2();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity2();
        setupGuiScreen(true);
		hasSetupGuiScreen = true;
		hasClearedColorBuffer = true;
    }
	//@todo
	if (!hasSetupGuiScreen)
		setupGuiScreen(!hasClearedColorBuffer);

	if (mc->player && mc->screen == NULL) {
		if (mc->inputHolder) mc->inputHolder->render(a);
		if (mc->player->input) mc->player->input->render(a);
	}

    if (mc->screen != NULL) {
		if (useScreenScissor)
			glDisable2(GL_SCISSOR_TEST);

		mc->screen->render(xMouse, yMouse, a);
#ifdef RPI
		renderCursor(xMouse, yMouse, mc);
#endif
		// Screen might have been removed, so check it again
		if (mc->screen && !mc->screen->isInGameScreen())
			sleepMs(15);
    }
}

/*public*/
void GameRenderer::renderLevel(float a) {

    if (mc->cameraTargetPlayer == NULL) {
		if (mc->player)
		{
			mc->cameraTargetPlayer = mc->player;
		}
		else
		{
			return;
		}
    }

	TIMER_PUSH("pick");
    pick(a);

    Mob* cameraEntity = mc->cameraTargetPlayer;
    LevelRenderer* levelRenderer = mc->levelRenderer;
    ParticleEngine* particleEngine = mc->particleEngine;
    float xOff = cameraEntity->xOld + (cameraEntity->x - cameraEntity->xOld) * a;
    float yOff = cameraEntity->yOld + (cameraEntity->y - cameraEntity->yOld) * a;
    float zOff = cameraEntity->zOld + (cameraEntity->z - cameraEntity->zOld) * a;

    for (int i = 0; i < 2; i++) {
        if (mc->options.anaglyph3d) {
            if (i == 0) glColorMask(false, true, true, false);
            else glColorMask(true, false, false, false);
        }

		TIMER_POP_PUSH("clear");
		glViewport(0, 0, mc->width, mc->height);
		setupClearColor(a);

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glEnable2(GL_CULL_FACE);

		TIMER_POP_PUSH("camera");
        setupCamera(a, i);
		saveMatrices();

		if (useScreenScissor) {
			glEnable2(GL_SCISSOR_TEST);
			glScissor(	screenScissorArea.x, screenScissorArea.y,
						screenScissorArea.w, screenScissorArea.h);
		}
		
        if(mc->options.fancyGraphics) {
			setupFog(-1);
			TIMER_POP_PUSH("sky");
			glFogf(GL_FOG_START, renderDistance  * 0.2f);
			glFogf(GL_FOG_END, renderDistance *0.75);
            levelRenderer->renderSky(a);
			glFogf(GL_FOG_START, renderDistance  * 0.6f);
			glFogf(GL_FOG_END, renderDistance);
        }
        glEnable2(GL_FOG);
        setupFog(1);

        if (mc->options.ambientOcclusion) {
            glShadeModel2(GL_SMOOTH);
		}
        
		TIMER_POP_PUSH("frustrum");
		FrustumCuller frustum;
        frustum.prepare(xOff, yOff, zOff);

		TIMER_POP_PUSH("culling");
        mc->levelRenderer->cull(&frustum, a);
        mc->levelRenderer->updateDirtyChunks(cameraEntity, false);

		if(mc->options.fancyGraphics) {
			prepareAndRenderClouds(levelRenderer, a);
		}

        setupFog(0);
        glEnable2(GL_FOG);

		mc->textures->loadAndBindTexture("terrain.png");
        glDisable2(GL_ALPHA_TEST);
        glDisable2(GL_BLEND);
        glEnable2(GL_CULL_FACE);
		TIMER_POP_PUSH("terrain-0");
        levelRenderer->render(cameraEntity, 0, a);

		TIMER_POP_PUSH("terrain-1");
        glEnable2(GL_ALPHA_TEST);
        levelRenderer->render(cameraEntity, 1, a);
        
        glShadeModel2(GL_FLAT);
		TIMER_POP_PUSH("entities");
		mc->levelRenderer->renderEntities(cameraEntity->getPos(a), &frustum, a);
//        setupFog(0);
		TIMER_POP_PUSH("particles");
        particleEngine->render(cameraEntity, a);

		glDisable2(GL_BLEND);
        glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        setupFog(0);
        glEnable2(GL_BLEND);
        glDisable2(GL_CULL_FACE);
		glDepthMask(GL_FALSE);
        glDisable2(GL_ALPHA_TEST);
		mc->textures->loadAndBindTexture("terrain.png");
        //if (mc->options.fancyGraphics) {
        //    glColorMask(false, false, false, false);
        //    int visibleWaterChunks = levelRenderer->render(cameraEntity, 1, a);
        //    glColorMask(true, true, true, true);
        //    if (mc->options.anaglyph3d) {
        //        if (i == 0) glColorMask(false, true, true, false);
        //        else glColorMask(true, false, false, false);
        //    }
        //    if (visibleWaterChunks > 0) {
        //        levelRenderer->renderSameAsLast(1, a);
        //    }
        //} else
		{
			//glDepthRangef(0.1f, 1.0f);
			//glDepthMask(GL_FALSE);
			TIMER_POP_PUSH("terrain-water");
			glEnable2(GL_DEPTH_TEST);
            levelRenderer->render(cameraEntity, 2, a);
			//glDepthRangef(0, 1);

        }
        
		glDepthMask(GL_TRUE);
        glEnable2(GL_CULL_FACE);
        glDisable2(GL_BLEND);
        glEnable2(GL_ALPHA_TEST);

		if (/*!Minecraft::FLYBY_MODE &&*/ zoom == 1 && cameraEntity->isPlayer()) {
			if (mc->hitResult.isHit() && !cameraEntity->isUnderLiquid(Material::water)) {
				TIMER_POP_PUSH("select");
				Player* player = (Player*) cameraEntity;
				if (mc->useTouchscreen()) {
					levelRenderer->renderHitSelect(player, mc->hitResult, 0, NULL, a); //player.inventory->getSelected(), a);
				}
				levelRenderer->renderHit(player, mc->hitResult, 0, NULL, a);//player->inventory.getSelected(), a);
			}
		}

		glDisable2(GL_FOG);
//
//        setupFog(0);
//        glEnable2(GL_FOG);
////        levelRenderer->renderClouds(a);
//        glDisable2(GL_FOG);
        setupFog(1);

        if (zoom == 1) {
			TIMER_POP_PUSH("hand");
            glClear(GL_DEPTH_BUFFER_BIT);
            renderItemInHand(a, i);
        }

        if (!mc->options.anaglyph3d) {
			TIMER_POP();
            return;
        }
    }
    glColorMask(true, true, true, false);
	TIMER_POP();
}

void GameRenderer::tickFov() {
	if (mc->cameraTargetPlayer != mc->player)
		return;

	oFov = fov;
	fov += (mc->player->getFieldOfViewModifier() - fov) * 0.5f;
}

/*private*/
float GameRenderer::getFov(float a, bool applyEffects) {
    Mob* player = mc->cameraTargetPlayer;
    float fov = 70;

	if (applyEffects)
		fov *= this->oFov + (this->fov - this->oFov) * a;

    if (player->isUnderLiquid(Material::water)) fov = 60;
    if (player->health <= 0) {
        float duration = player->deathTime + a;

        fov /= ((1 - 500 / (duration + 500)) * 2.0f + 1);
    }
    return fov + fovOffsetO + (fovOffset - fovOffsetO) * a;
}

/*private*/
void GameRenderer::moveCameraToPlayer(float a) {
    Entity* player = mc->cameraTargetPlayer;

    float heightOffset = player->heightOffset - 1.62f;

    float x = player->xo + (player->x - player->xo) * a;
    float y = player->yo + (player->y - player->yo) * a - heightOffset;
	//printf("camera y: %f\n", y);
    float z = player->zo + (player->z - player->zo) * a;

	//printf("rot: %f %f\n", cameraRollO, cameraRoll);
    glRotatef2(cameraRollO + (cameraRoll - cameraRollO) * a, 0, 0, 1);

	//LOGI("player. alive, removed: %d, %d\n", player->isAlive(), player->removed);
	if(player->isPlayer() && ((Player*)player)->isSleeping()) {
		heightOffset += 1.0;
		glTranslatef(0.0f, 0.3f, 0);
		if (!mc->options.fixedCamera) {
			int t = mc->level->getTile(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
			if (t == Tile::bed->id) {
				int data = mc->level->getData(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));

				int direction = data & 3;
				glRotatef(float(direction * 90), 0, 1, 0);
			}
			glRotatef(player->yRotO + (player->yRot - player->yRotO) * a + 180, 0, -1, 0);
			glRotatef(player->xRotO + (player->xRot - player->xRotO) * a, -1, 0, 0);
		}
	} else if (mc->options.thirdPersonView/* || (player->isPlayer() && !player->isAlive())*/) {
        float cameraDist = thirdDistanceO + (thirdDistance - thirdDistanceO) * a;

        if (mc->options.fixedCamera) {

            float rotationY = thirdRotationO + (thirdRotation - thirdRotationO) * a;
            float xRot = thirdTiltO + (thirdTilt - thirdTiltO) * a;

            glTranslatef2(0, 0, (float) -cameraDist);
            glRotatef2(xRot, 1, 0, 0);
            glRotatef2(rotationY, 0, 1, 0);
        } else {
            float yRot = player->yRot;
            float xRot = player->xRot/* + 180.0f*/;
            float xd = -Mth::sin(yRot / 180 * Mth::PI) * Mth::cos(xRot / 180 * Mth::PI) * cameraDist;
            float zd = Mth::cos(yRot / 180 * Mth::PI) * Mth::cos(xRot / 180 * Mth::PI) * cameraDist;
            float yd = -Mth::sin(xRot / 180 * Mth::PI) * cameraDist;

            for (int i = 0; i < 8; i++) {
                float xo = (float)((i & 1) * 2 - 1);
                float yo = (float)(((i >> 1) & 1) * 2 - 1);
                float zo = (float)(((i >> 2) & 1) * 2 - 1);

                xo *= 0.1f;
                yo *= 0.1f;
                zo *= 0.1f;

                HitResult hr = mc->level->clip(Vec3(x + xo, y + yo, z + zo), Vec3(x - xd + xo + zo, y - yd + yo, z - zd + zo)); // newTemp
				if (hr.type != NO_HIT) {
                    float dist = hr.pos.distanceTo(Vec3(x, y, z)); // newTemp
                    if (dist < cameraDist) cameraDist = dist;
                }
            }

			//glRotatef2(180, 0, 1, 0);

			glRotatef2(player->xRot - xRot, 1, 0, 0);
            glRotatef2(player->yRot - yRot, 0, 1, 0);
            glTranslatef2(0, 0, (float) -cameraDist);
            glRotatef2(yRot - player->yRot, 0, 1, 0);
            glRotatef2(xRot - player->xRot, 1, 0, 0);
        }
    } else {
        glTranslatef2(0, 0, -0.1f);
    }

    if (!mc->options.fixedCamera) {
        glRotatef2(player->xRotO + (player->xRot - player->xRotO) * a, 1.0f, 0.0f, 0.0f);
        glRotatef2(player->yRotO + (player->yRot - player->yRotO) * a + 180, 0, 1, 0);
		//if (_t_keepPic > 0)
	}
    glTranslatef2(0, heightOffset, 0);
}

/*private*/
void GameRenderer::bobHurt(float a) {
    Mob* player = mc->cameraTargetPlayer;

    float hurt = player->hurtTime - a;

    if (player->health <= 0) {
        float duration = player->deathTime + a;
        glRotatef2(40 - (40 * 200) / (duration + 200), 0, 0, 1);
    }

    if (player->hurtTime <= 0) return;

	hurt /= player->hurtDuration;
    hurt = (float) Mth::sin(hurt * hurt * hurt * hurt * Mth::PI);

    float rr = player->hurtDir;

    glRotatef2(-rr, 0, 1, 0);
    glRotatef2(-hurt * 14, 0, 0, 1);
    glRotatef2(+rr, 0, 1, 0);
}

/*private*/
void GameRenderer::bobView(float a) {
    //if (mc->options.thirdPersonView) return;
	if (!(mc->cameraTargetPlayer->isPlayer())) {
        return;
    }
    Player* player = (Player*) mc->cameraTargetPlayer;

    float wda = player->walkDist - player->walkDistO;
    float b = -(player->walkDist + wda * a);
    float bob = player->oBob + (player->bob - player->oBob) * a;
    float tilt = player->oTilt + (player->tilt - player->oTilt) * a;
    glTranslatef2((float) Mth::sin(b * Mth::PI) * bob * 0.5f, -(float) std::abs(Mth::cos(b * Mth::PI) * bob), 0);
    glRotatef2((float) Mth::sin(b * Mth::PI) * bob * 3, 0, 0, 1);
    glRotatef2((float) std::abs(Mth::cos(b * Mth::PI - 0.2f) * bob) * 5, 1, 0, 0);
    glRotatef2((float) tilt, 1, 0, 0);
}

/*private*/
void GameRenderer::setupFog(int i) {
    Mob* player = mc->cameraTargetPlayer;
	float fogBuffer[4] = {fr, fg, fb, 1};

    glFogfv(GL_FOG_COLOR, (GLfloat*)fogBuffer);
    glColor4f2(1, 1, 1, 1);

    if (player->isUnderLiquid(Material::water)) {
        glFogx(GL_FOG_MODE, GL_EXP);
        glFogf(GL_FOG_DENSITY, 0.1f); // was 0.06

//        float rr = 0.4f;
//        float gg = 0.4f;
//        float bb = 0.9f;
//
//        if (mc->options.anaglyph3d) {
//            float rrr = (rr * 30 + gg * 59 + bb * 11) / 100;
//            float ggg = (rr * 30 + gg * 70) / (100);
//            float bbb = (rr * 30 + bb * 70) / (100);
//
//            rr = rrr;
//            gg = ggg;
//            bb = bbb;
//        }
    } else if (player->isUnderLiquid(Material::lava)) {
        glFogx(GL_FOG_MODE, GL_EXP);
        glFogf(GL_FOG_DENSITY, 2.f); // was 0.06
//        float rr = 0.4f;
//        float gg = 0.3f;
//        float bb = 0.3f;
//
//        if (mc->options.anaglyph3d) {
//            float rrr = (rr * 30 + gg * 59 + bb * 11) / 100;
//            float ggg = (rr * 30 + gg * 70) / (100);
//            float bbb = (rr * 30 + bb * 70) / (100);
//
//            rr = rrr;
//            gg = ggg;
//            bb = bbb;
//        }
    } else {
        glFogx(GL_FOG_MODE, GL_LINEAR);
        glFogf(GL_FOG_START, renderDistance * 0.6f);
        glFogf(GL_FOG_END, renderDistance);
        if (i < 0) {
            glFogf(GL_FOG_START, 0);
            glFogf(GL_FOG_END, renderDistance * 1.0f);
        }

        if (mc->level->dimension->foggy) {
            glFogf(GL_FOG_START, 0);
        }
    }

    glEnable2(GL_COLOR_MATERIAL);
    //glColorMaterial(GL_FRONT, GL_AMBIENT);
}

void GameRenderer::updateAllChunks() {
    mc->levelRenderer->updateDirtyChunks(mc->cameraTargetPlayer, true);
}

bool GameRenderer::updateFreeformPickDirection(float a, Vec3& outDir) {

    if (!mc->inputHolder->allowPicking()) {
        _shTicks = 1;
        return false;
    }

    Vec3 c = mc->cameraTargetPlayer->getPos(a);

    bool firstPerson = !mc->options.thirdPersonView;
    const float PickingDistance = firstPerson? 6.0f : 12.0f;

    _shTicks = -1;

    int vp[4] = {0, 0, mc->width, mc->height};
    float pt[3];
    float x = mc->inputHolder->mousex;
    float y = mc->height - mc->inputHolder->mousey;

    //sw.start();

    if (!glhUnProjectf(x, y, 1, lastModelMatrix, lastProjMatrix, vp, pt)) {
        return false;
    }
    Vec3 p1(pt[0] + c.x, pt[1] + c.y, pt[2] + c.z);

    glhUnProjectf(x, y, 0, lastModelMatrix, lastProjMatrix, vp, pt);
    Vec3 p0(pt[0] + c.x, pt[1] + c.y, pt[2] + c.z);

    outDir = (p1 - p0).normalized();
    p1 = p0 + outDir * PickingDistance;

    //sw.stop();
    //sw.printEvery(30, "unproject ");

    const HitResult& hit = mc->hitResult = mc->level->clip(p0, p1, false);

    // If in 3rd person view - verify that the hit target is within range
    if (!firstPerson && hit.isHit()) {
        const float MaxSqrDist = PickingDistance*PickingDistance;
        if (mc->cameraTargetPlayer->distanceToSqr((float)hit.x, (float)hit.y, (float)hit.z) > MaxSqrDist)
            mc->hitResult.type = NO_HIT;
    }
    return true;
}

/*public*/
void GameRenderer::pick(float a) {
	if (mc->level == NULL) return;
    if (mc->cameraTargetPlayer == NULL) return;
	if (!mc->cameraTargetPlayer->isAlive()) return;

    float range = mc->gameMode->getPickRange();
    bool isPicking = true;
#ifndef PLATFORM_DESKTOP
    bool freeform = mc->useTouchscreen()  && !mc->options.isJoyTouchArea;
#else 
    bool freeform = false;
#endif
    if (freeform) {
        isPicking = updateFreeformPickDirection(a, pickDirection);
    } else {
        mc->hitResult = mc->cameraTargetPlayer->pick(range, a);
        pickDirection = mc->cameraTargetPlayer->getViewVector(a);
    }

    Vec3  from = mc->cameraTargetPlayer->getPos(a);
    float dist = range;
	if (mc->hitResult.isHit()) {
        dist = mc->hitResult.pos.distanceTo(from);
    }

	if (mc->gameMode->isCreativeType()) {
        /*dist =*/ range = 12;
    } else {
        if (dist > 3) dist = 3;
        range = dist;
    }

    Vec3 pv = (pickDirection * range);
    Vec3 to  = from + pv;
    mc->cameraTargetPlayer->aimDirection = pickDirection;

    Entity* hovered = NULL;
    const float g = 1;
    AABB aabb = mc->cameraTargetPlayer->bb.expand(pv.x, pv.y, pv.z).grow(g, g, g);
	EntityList& objects = mc->level->getEntities(mc->cameraTargetPlayer, aabb);
    float nearest = 0;
    for (unsigned int i = 0; i < objects.size(); i++) {
        Entity* e = objects[i];
        if (!e->isPickable()) continue;

        float rr = e->getPickRadius();
        AABB bb = e->bb.grow(rr, rr, rr);
        HitResult p = bb.clip(from, to);
		//printf("Clip Hitresult %d (%d)\n", p.type, p.isHit());

        if (bb.contains(from)) {
            //@todo: hovered = e; break; ?
            if (nearest >= 0) {
                hovered = e;
                nearest = 0;
            }
		} else if (p.isHit()) {
            float dd = from.distanceTo(p.pos);
            if (dd < nearest || nearest == 0) {
                hovered = e;
                nearest = dd;
            }
        }
    }

    if (hovered != NULL) {
		if(nearest < dist) {
			mc->hitResult = HitResult(hovered);
		}
    }
	else if (isPicking && !mc->hitResult.isHit()) {
		// if we don't have a hit result, attempt to hit the edge of the block we are standing on
		// (this is an pocket edition simplification to help building floors)
        //LOGI("hovered : %d (%f)\n", mc->hitResult.type, viewVec.y);
		if (pickDirection.y < -.7f) {
			// looking down by more than roughly 45 degrees, fetch a hit to the block standing on
			Vec3 to = from.add(0, -2.0f, 0);

			HitResult downHitResult = mc->level->clip(from, to);
			if (downHitResult.isHit()) {
				mc->hitResult = downHitResult;
				mc->hitResult.indirectHit = true;
				// change face (not up)
				if (std::abs(pickDirection.x) > std::abs(pickDirection.z)) {
                    mc->hitResult.f = (pickDirection.x < 0)? 4 : 5;
				} else {
                    mc->hitResult.f = (pickDirection.z < 0)? 2 : 3;
				}
			}
		}
	}
}
/*public*/
void GameRenderer::tick(int nTick, int maxTick) {
	--_t_keepPic;

	if (!mc->player)
	{
		return;
	}

	if (--_shTicks == 0)
		mc->hitResult.type = NO_HIT;

	//_rotXlast = _rotX;
	//_rotYlast = _rotY;

	//LOGI("x: %f\n", _rotX);

    if (nTick == maxTick) {
        const float tickMult = 1.0f / (float)(1 + maxTick);
        _rotXlast = 0.4f * std::pow(std::abs(_rotX), 1.2f) * tickMult;
        if (_rotX < 0) _rotXlast = -_rotXlast;

        _rotYlast = 0.4f * std::pow(std::abs(_rotY), 1.2f) * tickMult;
        if (_rotY < 0) _rotYlast = -_rotYlast;

        _rotX = 0;
        _rotY = 0;
    }

    fogBrO = fogBr;
    thirdDistanceO = thirdDistance;
    thirdRotationO = thirdRotation;
    thirdTiltO = thirdTilt;
    fovOffsetO = fovOffset;
    cameraRollO = cameraRoll;

    if (mc->cameraTargetPlayer == NULL) {
        mc->cameraTargetPlayer = mc->player;
    }

	tickFov();

    float brr = mc->level->getBrightness(	Mth::floor(mc->cameraTargetPlayer->x),
											Mth::floor(mc->cameraTargetPlayer->y),
											Mth::floor(mc->cameraTargetPlayer->z));

	float whiteness = (3 - mc->options.viewDistance) / 3.0f;
    float fogBrT = brr * (1 - whiteness) + whiteness;
    fogBr += (fogBrT - fogBr) * 0.1f;

    _tick++;

    itemInHandRenderer->tick();
//    if (mc->isRaining) tickRain();
}

/*private*/
void GameRenderer::setupClearColor(float a) {
    Level* level = mc->level;
    Mob* player = mc->cameraTargetPlayer;

    float whiteness = 1.0f / (4 - mc->options.viewDistance);
    whiteness = 1 - (float) pow(whiteness, 0.25f);

    Vec3 skyColor = level->getSkyColor(mc->cameraTargetPlayer, a);
    float sr = (float) skyColor.x;
    float sg = (float) skyColor.y;
    float sb = (float) skyColor.z;

    Vec3 fogColor = level->getFogColor(a);
    fr = (float) fogColor.x;
    fg = (float) fogColor.y;
    fb = (float) fogColor.z;

    fr += (sr - fr) * whiteness;
    fg += (sg - fg) * whiteness;
    fb += (sb - fb) * whiteness;

    if (player->isUnderLiquid(Material::water)) {
        fr = 0.02f;
        fg = 0.02f;
        fb = 0.2f;
    } else if (player->isUnderLiquid(Material::lava)) {
        fr = 0.6f;
        fg = 0.1f;
        fb = 0.00f;
    }

    float brr = fogBrO + (fogBr - fogBrO) * a;
    fr *= brr;
    fg *= brr;
    fb *= brr;

    if (mc->options.anaglyph3d) {
        float frr = (fr * 30 + fg * 59 + fb * 11) / 100;
        float fgg = (fr * 30 + fg * 70) / (100);
        float fbb = (fr * 30 + fb * 70) / (100);

        fr = frr;
        fg = fgg;
        fb = fbb;
    }

    glClearColor(fr, fg, fb, 1.0f);
}

void GameRenderer::zoomRegion( float zoom, float xa, float ya )
{
	this->zoom = zoom;
	this->zoom_x = xa;
	this->zoom_y = ya;
}

void GameRenderer::unZoomRegion()
{
	zoom = 1;
}

void GameRenderer::setupGuiScreen( bool clearColorBuffer )
{
	int screenWidth = (int)(mc->width * Gui::InvGuiScale);
	int screenHeight = (int)(mc->height * Gui::InvGuiScale);

	// Setup GUI render mode
	GLbitfield clearBits = clearColorBuffer?
			GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT
		:	GL_DEPTH_BUFFER_BIT;
	glClear(clearBits);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity2();
	glOrthof(0, (GLfloat)screenWidth, (GLfloat)screenHeight, 0, 2000, 3000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity2();
	glTranslatef2(0, 0, -2000);
}

/*private*/
void GameRenderer::renderItemInHand(float a, int eye) {
    glLoadIdentity2();
    if (mc->options.anaglyph3d) glTranslatef2((eye * 2 - 1) * 0.10f, 0, 0);

    glPushMatrix2();
    bobHurt(a);
    if (mc->options.bobView) bobView(a);

    if (!mc->options.thirdPersonView && (mc->cameraTargetPlayer->isPlayer() && !((Player*)mc->cameraTargetPlayer)->isSleeping())) {
        if (!mc->options.hideGui) {
			float fov = getFov(a, false);
			if (fov != _setupCameraFov) {
				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				gluPerspective(fov, mc->width / (float) mc->height, 0.05f, renderDistance);
				glMatrixMode(GL_MODELVIEW);
			}
            itemInHandRenderer->render(a);
        }
    }

    glPopMatrix2();
    if (!mc->options.thirdPersonView && (mc->cameraTargetPlayer->isPlayer() && !((Player*)mc->cameraTargetPlayer)->isSleeping())) {
        itemInHandRenderer->renderScreenEffect(a);
        bobHurt(a);
    }
    if (mc->options.bobView) bobView(a);
}

void GameRenderer::onGraphicsReset()
{
	if (itemInHandRenderer) itemInHandRenderer->onGraphicsReset();
}

void GameRenderer::saveMatrices()
{
	#if defined(RPI)
		return;
	#endif

	static bool saved = false;
	//if (saved) return;

	saved = true;

	glGetFloatv(GL_PROJECTION_MATRIX, lastProjMatrix);
	glGetFloatv(GL_MODELVIEW_MATRIX, lastModelMatrix);
}

void GameRenderer::prepareAndRenderClouds( LevelRenderer* levelRenderer, float a ) {
	//if(mc->options.isCloudsOn()) {
	TIMER_PUSH("clouds");
	glMatrixMode(GL_PROJECTION);
	glPushMatrix2();
	glLoadIdentity2();
	gluPerspective(_setupCameraFov = getFov(a, true), mc->width / (float) mc->height, 2, renderDistance * 512);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix2();
	setupFog(0);
	glDepthMask(false);
	glEnable2(GL_FOG);
	glFogf(GL_FOG_START, renderDistance  * 0.2f);
	glFogf(GL_FOG_END, renderDistance * 0.75f);
	levelRenderer->renderSky(a);
	glFogf(GL_FOG_START, renderDistance * 4.2f * 0.6f);
	glFogf(GL_FOG_END, renderDistance * 4.2f);
	levelRenderer->renderClouds(a);
	glFogf(GL_FOG_START, renderDistance  * 0.6f);
	glFogf(GL_FOG_END, renderDistance);
	glDisable2(GL_FOG);
	glDepthMask(true);
	setupFog(1);
	glPopMatrix2();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix2();
	glMatrixMode(GL_MODELVIEW);
	TIMER_POP();
	//}
}
