/*
	olcPGEX_RayCastWorld.h

	+-------------------------------------------------------------+
	|         OneLoneCoder Pixel Game Engine Extension            |
	|                   Ray Cast World v1.02                      |
	+-------------------------------------------------------------+

	NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES

	What is this?
	~~~~~~~~~~~~~
	This is an extension to the olcPixelGameEngine, which provides
	a quick and easy to use, flexible, skinnable, ray-cast 3D world
	engine, which handles all graphics and collisions within a
	pseudo 3D world. 

	It is designed to be implementation independent. Please see example
	files for usage instructions.

	Video: https://youtu.be/Vij_obgv9h4

	License (OLC-3)
	~~~~~~~~~~~~~~~

	Copyright 2018 - 2020 OneLoneCoder.com

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions
	are met:

	1. Redistributions or derivations of source code must retain the above
	copyright notice, this list of conditions and the following disclaimer.

	2. Redistributions or derivative works in binary form must reproduce
	the above copyright notice. This list of conditions and the following
	disclaimer must be reproduced in the documentation and/or other
	materials provided with the distribution.

	3. Neither the name of the copyright holder nor the names of its
	contributors may be used to endorse or promote products derived
	from this software without specific prior written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
	A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
	HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	Links
	~~~~~
	YouTube:	https://www.youtube.com/javidx9
	Discord:	https://discord.gg/WhwHUMV
	Twitter:	https://www.twitter.com/javidx9
	Twitch:		https://www.twitch.tv/javidx9
	GitHub:		https://www.github.com/onelonecoder
	Homepage:	https://www.onelonecoder.com

	Author
	~~~~~~
	David Barr, aka javidx9, ŠOneLoneCoder 2019, 2020

	Revisions:
	1.00:	Initial Release
	1.01:	Fix NaN check on overlap distance (Thanks Dandistine)
	1.02:	Added dynamic step size for collisions
*/

#ifndef OLC_PGEX_RAYCASTWORLD_H
#define OLC_PGEX_RAYCASTWORLD_H

#include <unordered_map>
#include <algorithm>

namespace olc
{
	namespace rcw
	{
		// Base class for objects that exist in world
		class Object 
		{
		public:
			// Linkage to user object description
			uint32_t nGenericID = 0;
			// Position in tile/world space
			olc::vf2d pos;
			// Velocity in tile/world space
			olc::vf2d vel;
			// Speed of object
			float fSpeed = 0.0f;
			// Angular direction of object
			float fHeading = 0.0f;
			// Collision radius of object
			float fRadius = 0.5f;
			// Is drawn?
			bool bVisible = true;
			// Flag to be removed form world
			bool bRemove = false;
			// Can collide with scenery?
			bool bCollideWithScenery = true;
			// Notify scenery collision?
			bool bNotifySceneryCollision = false;
			// Can collide with other objects?
			bool bCollideWithObjects = false;
			// Notify object collisions?
			bool bNotifyObjectCollision = false;
			// Can this object be moved by another object?
			bool bCanBeMoved = true;
			// Has physics/collisions applied
			bool bIsActive = true;

			void Walk(const float fWalkSpeed);
			void Strafe(const float fStrafeSpeed);
			void Turn(const float fTurnSpeed);
			void Stop();
		};

		// The RayCastWorld Engine - Inherit from this, implement abstract
		// methods, call Update() and Render() when required
		class Engine : public olc::PGEX
		{
		public:
			// Identifies side of cell
			enum class CellSide
			{
				North,
				East,
				South,
				West,
				Top,
				Bottom,
			};

		public:
			// Construct world rednering parameters
			Engine(const int screen_w, const int screen_h, const float fov);

		protected:
			// ABSTRACT - User must return a suitable olc::Pixel depending on world location information provided
			virtual olc::Pixel SelectSceneryPixel(const int tile_x, const int tile_y, const olc::rcw::Engine::CellSide side, const float sample_x, const float sample_y, const float distance) = 0;

			// ABSTRACT - User must return a boolean indicating if the tile is solid or not
			virtual bool IsLocationSolid(const float tile_x, const float tile_y) = 0;

			// ABSTRACT - User must return sizes of requested objects in Unit Cell Size
			virtual float GetObjectWidth(const uint32_t id) = 0;
			virtual float GetObjectHeight(const uint32_t id) = 0;

			// ABSTRACT - User must return suitable olc::Pixel for object sprite sample location
			virtual olc::Pixel SelectObjectPixel(const uint32_t id, const float sample_x, const float sample_y, const float distance, const float angle) = 0;
	
			// OPTIONAL - User can handle collsiion response with scenery should they choose to
			virtual void HandleObjectVsScenery(std::shared_ptr<olc::rcw::Object> object, const int tile_x, const int tile_y, const olc::rcw::Engine::CellSide side, const float offset_x, const float offset_y);

			// OPTIONAL - User can handle collsiion response with objects should they choose to
			virtual void HandleObjectVsObject(std::shared_ptr<olc::rcw::Object> object1, std::shared_ptr<olc::rcw::Object> object2);



		public:
			// Sets In-Game Camera position
			void SetCamera(const olc::vf2d& pos, const float heading);

			// Called to update world state
			virtual void Update(float fElapsedTime);

			// Called to draw the world and its contents
			void Render();

		public:
			std::unordered_map<uint32_t, std::shared_ptr<olc::rcw::Object>> mapObjects;

		private:
			// A convenient utility struct to store all the info required to understand how a ray
			// has hit a specific tile
			struct sTileHit
			{
				olc::vi2d vTilePos = { 0,0 };
				olc::vf2d vHitPos = { 0,0 };
				float fLength = 0.0f;
				float fSampleX = 0.0f;
				Engine::CellSide eSide = Engine::CellSide::North;
			};

			// Cast ray into tile world, and return info about what it hits (if anything)
			bool CastRayDDA(const olc::vf2d& vOrigin, const olc::vf2d& vDirection, sTileHit& hit);
			
			// Convenient constants in algorithms
			const olc::vi2d vScreenSize;
			const olc::vi2d vHalfScreenSize;
			const olc::vf2d vFloatScreenSize;
			float fFieldOfView = 0.0f;

			// A depth buffer used to sort pixels in Z-Axis
			std::unique_ptr<float[]> pDepthBuffer;

			// Local store of camera position and direction
			olc::vf2d vCameraPos = { 5.0f, 5.0f };
			float fCameraHeading = 0.0f;
		};		
	}
}


#ifdef OLC_PGEX_RAYCASTWORLD
#undef OLC_PGEX_RAYCASTWORLD

#undef min
#undef max

void olc::rcw::Object::Walk(const float fWalkSpeed)
{
	fSpeed = fWalkSpeed;
	vel = olc::vf2d(std::cos(fHeading), std::sin(fHeading)) * fSpeed;
}

void olc::rcw::Object::Strafe(const float fStrafeSpeed)
{
	fSpeed = fStrafeSpeed;
	vel = olc::vf2d(std::cos(fHeading), std::sin(fHeading)).perp() * fSpeed;
}

void olc::rcw::Object::Turn(const float fTurnSpeed)
{
	fHeading += fTurnSpeed;

	// Wrap heading to sensible angle
	if (fHeading < -3.14159f) fHeading += 2.0f * 3.14159f;
	if (fHeading > 3.14159f) fHeading -= 2.0f * 3.14159f;
}


void olc::rcw::Object::Stop()
{
	fSpeed = 0;
	vel = { 0,0 };
}

olc::rcw::Engine::Engine(const int screen_w, const int screen_h, const float fov) :
	vScreenSize(screen_w, screen_h),
	vHalfScreenSize(screen_w / 2, screen_h / 2),
	vFloatScreenSize(float(screen_w), float(screen_h))
{
	fFieldOfView = fov;
	pDepthBuffer.reset(new float[vScreenSize.x * vScreenSize.y]);
}


void olc::rcw::Engine::SetCamera(const olc::vf2d& pos, const float heading)
{
	vCameraPos = pos;
	fCameraHeading = heading;
}


void olc::rcw::Engine::Update(float fElapsedTime)
{
	// Update the position and statically resolve for collisions against the map
	for (auto& ob : mapObjects)
	{
		std::shared_ptr<olc::rcw::Object> object = ob.second;
		if (!object->bIsActive) continue;

		int nSteps = 1;
		float fDelta = fElapsedTime;
		float fTotalTravel = (object->vel * fElapsedTime).mag2();
		float fTotalRadius = (object->fRadius * object->fRadius);

		if(fTotalTravel >= fTotalRadius)
		{
			float fSteps = std::ceil(fTotalTravel / fTotalRadius);
			nSteps = int(fSteps);
			fDelta = fElapsedTime / fSteps;
		}

		for (int nStep = 0; nStep < nSteps; nStep++)
		{
			// Determine where object is trying to be
			olc::vf2d vPotentialPosition = object->pos + object->vel * fDelta;

			// If the object can collide with other objects
			if (object->bCollideWithObjects)
			{
				// Iterate through all other objects (this can be costly)
				for (auto& ob2 : mapObjects)
				{
					std::shared_ptr<olc::rcw::Object> target = ob2.second;

					// Ignore if target object cant interact
					if (!target->bCollideWithObjects) continue;

					// Don't test against self
					if (target == object) continue;

					// Quick check to see if objects overlap...
					if ((target->pos - object->pos).mag2() <= (target->fRadius + object->fRadius) * (target->fRadius + object->fRadius))
					{
						// ..they do. Calculate displacement required
						float fDistance = (target->pos - object->pos).mag();
						float fOverlap = 1.0f * (fDistance - object->fRadius - target->fRadius);

						// Object will always give way to target
						vPotentialPosition -= (object->pos - target->pos) / fDistance * fOverlap;

						if (target->bCanBeMoved)
							target->pos += (object->pos - target->pos) / fDistance * fOverlap;

						if (object->bNotifyObjectCollision)
							HandleObjectVsObject(object, target);
					}


				}
			}

			// If the object can collide with scenery...
			if (object->bCollideWithScenery)
			{
				// ...Determine an area of cells to check for collision. We use a region
				// to account for diagonal collisions, and corner collisions.
				olc::vi2d vCurrentCell = object->pos;
				olc::vi2d vTargetCell = vPotentialPosition;
				olc::vi2d vAreaTL = { std::min(vCurrentCell.x, vTargetCell.x) - 1, std::min(vCurrentCell.y, vTargetCell.y) - 1 };
				olc::vi2d vAreaBR = { std::max(vCurrentCell.x, vTargetCell.x) + 1, std::max(vCurrentCell.y, vTargetCell.y) + 1 };




				// Iterate through each cell in test area
				olc::vi2d vCell;
				for (vCell.y = vAreaTL.y; vCell.y <= vAreaBR.y; vCell.y++)
				{
					for (vCell.x = vAreaTL.x; vCell.x <= vAreaBR.x; vCell.x++)
					{
						// Check if the cell is actually solid...
						olc::vf2d vCellMiddle = olc::vf2d(float(vCell.x) + 0.5f, float(vCell.y) + 0.5f);
						if (IsLocationSolid(vCellMiddle.x, vCellMiddle.y))
						{
							// ...it is! So work out nearest point to future player position, around perimeter
							// of cell rectangle. We can test the distance to this point to see if we have
							// collided.

							olc::vf2d vNearestPoint;
							// Inspired by this (very clever btw) 
							// https://stackoverflow.com/questions/45370692/circle-rectangle-collision-response
							vNearestPoint.x = std::max(float(vCell.x), std::min(vPotentialPosition.x, float(vCell.x + 1)));
							vNearestPoint.y = std::max(float(vCell.y), std::min(vPotentialPosition.y, float(vCell.y + 1)));

							// But modified to work :P
							olc::vf2d vRayToNearest = vNearestPoint - vPotentialPosition;
							float fOverlap = object->fRadius - vRayToNearest.mag();
							if (std::isnan(fOverlap)) fOverlap = 0;// Thanks Dandistine!

							// If overlap is positive, then a collision has occurred, so we displace backwards by the 
							// overlap amount. The potential position is then tested against other tiles in the area
							// therefore "statically" resolving the collision
							if (fOverlap > 0)
							{
								// Statically resolve the collision
								vPotentialPosition = vPotentialPosition - vRayToNearest.norm() * fOverlap;

								// Notify system that a collision has occurred
								if (object->bNotifySceneryCollision)
								{
									olc::rcw::Engine::CellSide side = olc::rcw::Engine::CellSide::Bottom;
									if (vNearestPoint.x == float(vCell.x)) side = olc::rcw::Engine::CellSide::West;
									if (vNearestPoint.x == float(vCell.x + 1)) side = olc::rcw::Engine::CellSide::East;
									if (vNearestPoint.y == float(vCell.y)) side = olc::rcw::Engine::CellSide::North;
									if (vNearestPoint.y == float(vCell.y + 1)) side = olc::rcw::Engine::CellSide::South;

									HandleObjectVsScenery(object, vCell.x, vCell.y, side, vNearestPoint.x - float(vCell.x), vNearestPoint.y - float(vCell.y));
								}
							}
						}
					}
				}
			}

			// Set the objects new position to the allowed potential position
			object->pos = vPotentialPosition;
		}
	}
}

void olc::rcw::Engine::Render()
{
	// Utility lambda to draw to screen and depth buffer
	auto DepthDraw = [&](int x, int y, float z, olc::Pixel p)
	{
		if (z <= pDepthBuffer[y * vScreenSize.x + x])
		{			
			pge->Draw(x, y, p);
			pDepthBuffer[y * vScreenSize.x + x] = z;
		}
	};

	
	// Clear screen and depth buffer ========================================
	// pge->Clear(olc::BLACK); <- Left to user to decide
	for (int i = 0; i < vScreenSize.x * vScreenSize.y; i++)
		pDepthBuffer[i] = INFINITY;

	// Draw World ===========================================================

	// For each column on screen...
	for (int x = 0; x < vScreenSize.x; x++)
	{
		// ...create a ray eminating from player position into world...
		float fRayAngle = (fCameraHeading - (fFieldOfView / 2.0f)) + (float(x) / vFloatScreenSize.x) * fFieldOfView;

		// ...create unit vector for that ray...
		olc::vf2d vRayDirection = { std::cos(fRayAngle), std::sin(fRayAngle) };

		// ... and cast ray into world, see what it hits (if anything)
		sTileHit hit;

		// Assuming it hits nothing, then we draw to the middle of the screen (far far away)				
		float fRayLength = INFINITY;

		// Otherwise...
		if (CastRayDDA(vCameraPos, vRayDirection, hit))
		{
			// It has hit something, so extract information to draw column
			olc::vf2d vRay = hit.vHitPos - vCameraPos;

			// Length of ray is vital for pseudo-depth, but we'll also cosine correct to remove fisheye
			fRayLength = vRay.mag() * std::cos(fRayAngle - fCameraHeading);
		}

		// Calculate locations in column that divides ceiling, wall and floor
		float fCeiling = (vFloatScreenSize.y / 2.0f) - (vFloatScreenSize.y / fRayLength);
		float fFloor = vFloatScreenSize.y - fCeiling;
		float fWallHeight = fFloor - fCeiling;
		float fFloorHeight = vFloatScreenSize.y - fFloor;

		// Now draw the column from top to bottom
		for (int y = 0; y < vScreenSize.y; y++)
		{
			if (y <= int(fCeiling))
			{
				// For floors and ceilings, we don't use the ray, instead we just pseudo-project
				// a plane, a la Mode 7. First calculate depth into screen...
				float fPlaneZ = (vFloatScreenSize.y / 2.0f) / ((vFloatScreenSize.y / 2.0f) - float(y));

				// ... then project polar coordinate (r, theta) from camera into screen (x, y), again
				// compensating with cosine to remove fisheye
				olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading);

				// Work out which planar tile we are in
				int nPlaneTileX = int(vPlanePoint.x);
				int nPlaneTileY = int(vPlanePoint.y);

				// Work out normalised offset into planar tile
				float fPlaneSampleX = vPlanePoint.x - nPlaneTileX;
				float fPlaneSampleY = vPlanePoint.y - nPlaneTileY;

				// Location is marked as ceiling					
				olc::Pixel pixel = SelectSceneryPixel(nPlaneTileX, nPlaneTileY, olc::rcw::Engine::CellSide::Top, fPlaneSampleX, fPlaneSampleY, fPlaneZ);

				// Draw ceiling pixel - no depth buffer required
				pge->Draw(x, y, pixel);
			}
			else if (y > int(fCeiling) && y <= int(fFloor))
			{
				// Location is marked as wall. Here we will sample the appropriate
				// texture at the hit location. The U sample coordinate is provided
				// by the "hit" structure, though we will need to scan the V sample
				// coordinate based upon the height of the wall in screen space

				// Create normalised "V" based on height of wall --> (0.0 to 1.0)
				float fSampleY = (float(y) - fCeiling) / fWallHeight;

				// Select appropriate texture of that wall
				olc::Pixel pixel = SelectSceneryPixel(hit.vTilePos.x, hit.vTilePos.y, hit.eSide, hit.fSampleX, fSampleY, fRayLength);

				// Finally draw the screen pixel doing a depth test too
				DepthDraw(x, y, fRayLength, pixel);
			}
			else
			{
				// For floors and ceilings, we don't use the ray, instead we just pseudo-project
				// a plane, a la Mode 7. First calculate depth into screen...
				float fPlaneZ = (vFloatScreenSize.y / 2.0f) / (float(y) - (vFloatScreenSize.y / 2.0f));

				// ... then project polar coordinate (r, theta) from camera into screen (x, y), again
				// compensating with cosine to remove fisheye
				olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading);

				// Work out which planar tile we are in
				int nPlaneTileX = int(vPlanePoint.x);
				int nPlaneTileY = int(vPlanePoint.y);

				// Work out normalised offset into planar tile
				float fPlaneSampleX = vPlanePoint.x - nPlaneTileX;
				float fPlaneSampleY = vPlanePoint.y - nPlaneTileY;

				// Location is marked as floor
				olc::Pixel pixel = SelectSceneryPixel(nPlaneTileX, nPlaneTileY, olc::rcw::Engine::CellSide::Bottom, fPlaneSampleX, fPlaneSampleY, fPlaneZ);

				// Draw floor pixel - no depth buffer required
				pge->Draw(x, y, pixel);
			}
		}
	}

	// Scenery is now drawn, and depth buffer is filled. We can now draw
	// the ingame objects. Assuming binary transparency, we've no need to
	// sort objects

	// Iterate through all in game objects
	for (const auto& ob : mapObjects)
	{
		const std::shared_ptr<olc::rcw::Object> object = ob.second;

		// If object is invisible, nothing to do - this is useful
		// for both effects, and making sure we dont render the
		// "player" at the camera location perhaps
		if (!object->bVisible) continue;

		// Create vector from camera to object
		olc::vf2d vObject = object->pos - vCameraPos;

		// Calculate distance object is away from camera
		float fDistanceToObject = vObject.mag();

		// Check if object center is within camera FOV...
		float fObjectAngle = atan2f(vObject.y, vObject.x) - fCameraHeading;
		if (fObjectAngle < -3.14159f) fObjectAngle += 2.0f * 3.14159f;
		if (fObjectAngle > 3.14159f) fObjectAngle -= 2.0f * 3.14159f;

		// ...with a bias based upon distance - allows us to have object centers offscreen
		bool bInPlayerFOV = fabs(fObjectAngle) < (fFieldOfView + (1.0f / fDistanceToObject)) / 2.0f;

		// If object is within view, and not too close to camera, draw it!
		if (bInPlayerFOV && vObject.mag() >= 0.5f)
		{
			// Work out its position on the floor...
			olc::vf2d vFloorPoint;

			// Horizontal screen location is determined based on object angle relative to camera heading
			vFloorPoint.x = (0.5f * ((fObjectAngle / (fFieldOfView * 0.5f))) + 0.5f) * vFloatScreenSize.x;

			// Vertical screen location is projected distance
			vFloorPoint.y = (vFloatScreenSize.y / 2.0f) + (vFloatScreenSize.y / fDistanceToObject) / std::cos(fObjectAngle / 2.0f);

			// First we need the objects size...
			olc::vf2d vObjectSize = { float(GetObjectWidth(object->nGenericID)), float(GetObjectHeight(object->nGenericID)) };

			// ...which we can scale into world space (maintaining aspect ratio)...
			vObjectSize *= 2.0f * vFloatScreenSize.y;

			// ...then project into screen space
			vObjectSize /= fDistanceToObject;

			// Second we need the objects top left position in screen space...
			olc::vf2d vObjectTopLeft;

			// ...which is relative to the objects size and assumes the middle of the object is
			// the location in world space
			vObjectTopLeft = { vFloorPoint.x - vObjectSize.x / 2.0f, vFloorPoint.y - vObjectSize.y };

			// Now iterate through the objects screen pixels
			for (float y = 0; y < vObjectSize.y; y++)
			{
				for (float x = 0; x < vObjectSize.x; x++)
				{
					// Create a normalised sample coordinate
					float fSampleX = x / vObjectSize.x;
					float fSampleY = y / vObjectSize.y;

					// Get pixel from a suitable texture
					float fNiceAngle = fCameraHeading - object->fHeading + 3.14159f / 4.0f;
					if (fNiceAngle < 0) fNiceAngle += 2.0f * 3.14159f;
					if (fNiceAngle > 2.0f * 3.14159f) fNiceAngle -= 2.0f * 3.14159f;
					olc::Pixel p = SelectObjectPixel(object->nGenericID, fSampleX, fSampleY, fDistanceToObject, fNiceAngle);

					// Calculate screen pixel location
					olc::vi2d a = { int(vObjectTopLeft.x + x), int(vObjectTopLeft.y + y) };

					// Check if location is actually on screen (to not go OOB on depth buffer)
					// and if the pixel is indeed visible (has no transparency component)
					if (a.x >= 0 && a.x < vScreenSize.x && a.y >= 0 && a.y < vScreenSize.y && p.a == 255)
					{
						// Draw the pixel taking into account the depth buffer
						DepthDraw(a.x, a.y, fDistanceToObject, p);
					}
				}
			}
		}
	}
}

void olc::rcw::Engine::HandleObjectVsScenery(std::shared_ptr<olc::rcw::Object> object, const int tile_x, const int tile_y, const olc::rcw::Engine::CellSide side, const float offset_x, const float offset_y)
{}

void olc::rcw::Engine::HandleObjectVsObject(std::shared_ptr<olc::rcw::Object> object1, std::shared_ptr<olc::rcw::Object> object2)
{}

// Will be explained in upcoming video...
bool olc::rcw::Engine::CastRayDDA(const olc::vf2d& vOrigin, const olc::vf2d& vDirection, sTileHit& hit)
{
	olc::vf2d vRayDelta = { sqrt(1 + (vDirection.y / vDirection.x) * (vDirection.y / vDirection.x)), sqrt(1 + (vDirection.x / vDirection.y) * (vDirection.x / vDirection.y)) };

	olc::vi2d vMapCheck = vOrigin;
	olc::vf2d vSideDistance;
	olc::vi2d vStepDistance;

	if (vDirection.x < 0)
	{
		vStepDistance.x = -1;
		vSideDistance.x = (vOrigin.x - (float)vMapCheck.x) * vRayDelta.x;
	}
	else
	{
		vStepDistance.x = 1;
		vSideDistance.x = ((float)vMapCheck.x + 1.0f - vOrigin.x) * vRayDelta.x;
	}

	if (vDirection.y < 0)
	{
		vStepDistance.y = -1;
		vSideDistance.y = (vOrigin.y - (float)vMapCheck.y) * vRayDelta.y;
	}
	else
	{
		vStepDistance.y = 1;
		vSideDistance.y = ((float)vMapCheck.y + 1.0f - vOrigin.y) * vRayDelta.y;
	}

	olc::vf2d vIntersection;
	olc::vi2d vHitTile;
	float fMaxDistance = 100.0f;
	float fDistance = 0.0f;
	bool bTileFound = false;
	while (!bTileFound && fDistance < fMaxDistance)
	{
		if (vSideDistance.x < vSideDistance.y)
		{
			vSideDistance.x += vRayDelta.x;
			vMapCheck.x += vStepDistance.x;
		}
		else
		{
			vSideDistance.y += vRayDelta.y;
			vMapCheck.y += vStepDistance.y;
		}

		olc::vf2d rayDist = { (float)vMapCheck.x - vOrigin.x, (float)vMapCheck.y - vOrigin.y };
		fDistance = rayDist.mag();


		if (IsLocationSolid(float(vMapCheck.x), float(vMapCheck.y)))
		{
			vHitTile = vMapCheck;
			bTileFound = true;

			hit.vTilePos = vMapCheck;


			// Find accurate Hit Location

			float m = vDirection.y / vDirection.x;


			// From Top Left

			if (vOrigin.y <= vMapCheck.y)
			{
				if (vOrigin.x <= vMapCheck.x)
				{
					hit.eSide = olc::rcw::Engine::CellSide::West;
					vIntersection.y = m * (vMapCheck.x - vOrigin.x) + vOrigin.y;
					vIntersection.x = float(vMapCheck.x);
					hit.fSampleX = vIntersection.y - std::floor(vIntersection.y);
				}
				else if (vOrigin.x >= (vMapCheck.x + 1))
				{
					hit.eSide = olc::rcw::Engine::CellSide::East;
					vIntersection.y = m * ((vMapCheck.x + 1) - vOrigin.x) + vOrigin.y;
					vIntersection.x = float(vMapCheck.x + 1);
					hit.fSampleX = vIntersection.y - std::floor(vIntersection.y);
				}
				else
				{
					hit.eSide = olc::rcw::Engine::CellSide::North;
					vIntersection.y = float(vMapCheck.y);
					vIntersection.x = (vMapCheck.y - vOrigin.y) / m + vOrigin.x;
					hit.fSampleX = vIntersection.x - std::floor(vIntersection.x);
				}


				if (vIntersection.y < vMapCheck.y)
				{
					hit.eSide = olc::rcw::Engine::CellSide::North;
					vIntersection.y = float(vMapCheck.y);
					vIntersection.x = (vMapCheck.y - vOrigin.y) / m + vOrigin.x;
					hit.fSampleX = vIntersection.x - std::floor(vIntersection.x);
				}
			}
			else if (vOrigin.y >= vMapCheck.y + 1)
			{
				if (vOrigin.x <= vMapCheck.x)
				{
					hit.eSide = olc::rcw::Engine::CellSide::West;
					vIntersection.y = m * (vMapCheck.x - vOrigin.x) + vOrigin.y;
					vIntersection.x = float(vMapCheck.x);
					hit.fSampleX = vIntersection.y - std::floor(vIntersection.y);
				}
				else if (vOrigin.x >= (vMapCheck.x + 1))
				{
					hit.eSide = olc::rcw::Engine::CellSide::East;
					vIntersection.y = m * ((vMapCheck.x + 1) - vOrigin.x) + vOrigin.y;
					vIntersection.x = float(vMapCheck.x + 1);
					hit.fSampleX = vIntersection.y - std::floor(vIntersection.y);
				}
				else
				{
					hit.eSide = olc::rcw::Engine::CellSide::South;
					vIntersection.y = float(vMapCheck.y + 1);
					vIntersection.x = ((vMapCheck.y + 1) - vOrigin.y) / m + vOrigin.x;
					hit.fSampleX = vIntersection.x - std::floor(vIntersection.x);
				}

				if (vIntersection.y > (vMapCheck.y + 1))
				{
					hit.eSide = olc::rcw::Engine::CellSide::South;
					vIntersection.y = float(vMapCheck.y + 1);
					vIntersection.x = ((vMapCheck.y + 1) - vOrigin.y) / m + vOrigin.x;
					hit.fSampleX = vIntersection.x - std::floor(vIntersection.x);
				}
			}
			else
			{
				if (vOrigin.x <= vMapCheck.x)
				{
					hit.eSide = olc::rcw::Engine::CellSide::West;
					vIntersection.y = m * (vMapCheck.x - vOrigin.x) + vOrigin.y;
					vIntersection.x = float(vMapCheck.x);
					hit.fSampleX = vIntersection.y - std::floor(vIntersection.y);
				}
				else if (vOrigin.x >= (vMapCheck.x + 1))
				{
					hit.eSide = olc::rcw::Engine::CellSide::East;
					vIntersection.y = m * ((vMapCheck.x + 1) - vOrigin.x) + vOrigin.y;
					vIntersection.x = float(vMapCheck.x + 1);
					hit.fSampleX = vIntersection.y - std::floor(vIntersection.y);
				}
			}

			hit.vHitPos = vIntersection;
		}
	}

	return bTileFound;
}


#endif // OLC_PGEX_RAYCASTWORLD
#endif // OLC_PGEX_RAYCASTWORLD_H
