75#ifndef OLC_PGEX_RAYCASTWORLD_H
76#define OLC_PGEX_RAYCASTWORLD_H
78#include <unordered_map>
120 void Walk(
const float fWalkSpeed);
122 void Turn(
const float fTurnSpeed);
144 Engine(
const int screen_w,
const int screen_h,
const float fov);
164 virtual void HandleObjectVsObject(std::shared_ptr<olc::rcw::Object> object1, std::shared_ptr<olc::rcw::Object> object2);
179 std::unordered_map<uint32_t, std::shared_ptr<olc::rcw::Object>>
mapObjects;
188 float fLength = 0.0f;
189 float fSampleX = 0.0f;
194 bool CastRayDDA(
const olc::vf2d& vOrigin,
const olc::vf2d& vDirection, sTileHit& hit);
200 float fFieldOfView = 0.0f;
203 std::unique_ptr<float[]> pDepthBuffer;
207 float fCameraHeading = 0.0f;
213#ifdef OLC_PGEX_RAYCASTWORLD
214#undef OLC_PGEX_RAYCASTWORLD
227 fSpeed = fStrafeSpeed;
228 vel =
olc::vf2d(std::cos(fHeading), std::sin(fHeading)).
perp() * fSpeed;
233 fHeading += fTurnSpeed;
236 if (fHeading < -3.14159f) fHeading += 2.0f * 3.14159f;
237 if (fHeading > 3.14159f) fHeading -= 2.0f * 3.14159f;
248 vScreenSize(screen_w, screen_h),
249 vHalfScreenSize(screen_w / 2, screen_h / 2),
250 vFloatScreenSize(float(screen_w), float(screen_h))
253 pDepthBuffer.reset(
new float[vScreenSize.
x * vScreenSize.
y]);
260 fCameraHeading = heading;
267 for (
auto& ob : mapObjects)
269 std::shared_ptr<olc::rcw::Object>
object = ob.second;
270 if (!object->bIsActive)
continue;
273 float fDelta = fElapsedTime;
274 float fTotalTravel = (
object->vel * fElapsedTime).mag2();
275 float fTotalRadius = (
object->fRadius *
object->fRadius);
277 if(fTotalTravel >= fTotalRadius)
279 float fSteps = std::ceil(fTotalTravel / fTotalRadius);
280 nSteps = int(fSteps);
281 fDelta = fElapsedTime / fSteps;
284 for (
int nStep = 0; nStep < nSteps; nStep++)
287 olc::vf2d vPotentialPosition =
object->pos +
object->vel * fDelta;
290 if (object->bCollideWithObjects)
293 for (
auto& ob2 : mapObjects)
295 std::shared_ptr<olc::rcw::Object> target = ob2.second;
298 if (!target->bCollideWithObjects)
continue;
301 if (target ==
object)
continue;
304 if ((target->pos - object->pos).mag2() <= (target->fRadius + object->fRadius) * (target->fRadius + object->fRadius))
307 float fDistance = (target->pos -
object->pos).mag();
308 float fOverlap = 1.0f * (fDistance -
object->fRadius - target->fRadius);
311 vPotentialPosition -= (
object->pos - target->pos) / fDistance * fOverlap;
313 if (target->bCanBeMoved)
314 target->pos += (
object->pos - target->pos) / fDistance * fOverlap;
316 if (object->bNotifyObjectCollision)
317 HandleObjectVsObject(
object, target);
325 if (object->bCollideWithScenery)
330 olc::vi2d vTargetCell = vPotentialPosition;
331 olc::vi2d vAreaTL = { std::min(vCurrentCell.
x, vTargetCell.
x) - 1, std::min(vCurrentCell.
y, vTargetCell.
y) - 1 };
332 olc::vi2d vAreaBR = { std::max(vCurrentCell.
x, vTargetCell.
x) + 1, std::max(vCurrentCell.
y, vTargetCell.
y) + 1 };
339 for (vCell.
y = vAreaTL.
y; vCell.
y <= vAreaBR.
y; vCell.
y++)
341 for (vCell.
x = vAreaTL.
x; vCell.
x <= vAreaBR.
x; vCell.
x++)
345 if (IsLocationSolid(vCellMiddle.
x, vCellMiddle.
y))
354 vNearestPoint.
x = std::max(
float(vCell.
x), std::min(vPotentialPosition.
x,
float(vCell.
x + 1)));
355 vNearestPoint.
y = std::max(
float(vCell.
y), std::min(vPotentialPosition.
y,
float(vCell.
y + 1)));
358 olc::vf2d vRayToNearest = vNearestPoint - vPotentialPosition;
359 float fOverlap =
object->fRadius - vRayToNearest.
mag();
360 if (std::isnan(fOverlap)) fOverlap = 0;
368 vPotentialPosition = vPotentialPosition - vRayToNearest.
norm() * fOverlap;
371 if (object->bNotifySceneryCollision)
379 HandleObjectVsScenery(
object, vCell.
x, vCell.
y, side, vNearestPoint.
x -
float(vCell.
x), vNearestPoint.
y -
float(vCell.
y));
388 object->pos = vPotentialPosition;
396 auto DepthDraw = [&](
int x,
int y,
float z,
olc::Pixel p)
398 if (z <= pDepthBuffer[y * vScreenSize.x + x])
401 pDepthBuffer[y * vScreenSize.x + x] = z;
408 for (
int i = 0; i < vScreenSize.x * vScreenSize.y; i++)
409 pDepthBuffer[i] = INFINITY;
414 for (
int x = 0; x < vScreenSize.x; x++)
417 float fRayAngle = (fCameraHeading - (fFieldOfView / 2.0f)) + (float(x) / vFloatScreenSize.x) * fFieldOfView;
420 olc::vf2d vRayDirection = { std::cos(fRayAngle), std::sin(fRayAngle) };
426 float fRayLength = INFINITY;
429 if (CastRayDDA(vCameraPos, vRayDirection, hit))
432 olc::vf2d vRay = hit.vHitPos - vCameraPos;
435 fRayLength = vRay.
mag() * std::cos(fRayAngle - fCameraHeading);
439 float fCeiling = (vFloatScreenSize.y / 2.0f) - (vFloatScreenSize.y / fRayLength);
440 float fFloor = vFloatScreenSize.y - fCeiling;
441 float fWallHeight = fFloor - fCeiling;
442 float fFloorHeight = vFloatScreenSize.y - fFloor;
445 for (
int y = 0; y < vScreenSize.y; y++)
447 if (y <=
int(fCeiling))
451 float fPlaneZ = (vFloatScreenSize.y / 2.0f) / ((vFloatScreenSize.y / 2.0f) - float(y));
455 olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading);
458 int nPlaneTileX = int(vPlanePoint.
x);
459 int nPlaneTileY = int(vPlanePoint.
y);
462 float fPlaneSampleX = vPlanePoint.
x - nPlaneTileX;
463 float fPlaneSampleY = vPlanePoint.
y - nPlaneTileY;
469 pge->Draw(x, y, pixel);
471 else if (y >
int(fCeiling) && y <=
int(fFloor))
479 float fSampleY = (float(y) - fCeiling) / fWallHeight;
482 olc::Pixel pixel = SelectSceneryPixel(hit.vTilePos.x, hit.vTilePos.y, hit.eSide, hit.fSampleX, fSampleY, fRayLength);
485 DepthDraw(x, y, fRayLength, pixel);
491 float fPlaneZ = (vFloatScreenSize.y / 2.0f) / (
float(y) - (vFloatScreenSize.y / 2.0f));
495 olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading);
498 int nPlaneTileX = int(vPlanePoint.
x);
499 int nPlaneTileY = int(vPlanePoint.
y);
502 float fPlaneSampleX = vPlanePoint.
x - nPlaneTileX;
503 float fPlaneSampleY = vPlanePoint.
y - nPlaneTileY;
509 pge->Draw(x, y, pixel);
519 for (
const auto& ob : mapObjects)
521 const std::shared_ptr<olc::rcw::Object>
object = ob.second;
526 if (!object->bVisible)
continue;
529 olc::vf2d vObject =
object->pos - vCameraPos;
532 float fDistanceToObject = vObject.
mag();
535 float fObjectAngle = atan2f(vObject.
y, vObject.
x) - fCameraHeading;
536 if (fObjectAngle < -3.14159f) fObjectAngle += 2.0f * 3.14159f;
537 if (fObjectAngle > 3.14159f) fObjectAngle -= 2.0f * 3.14159f;
540 bool bInPlayerFOV = fabs(fObjectAngle) < (fFieldOfView + (1.0f / fDistanceToObject)) / 2.0f;
543 if (bInPlayerFOV && vObject.
mag() >= 0.5f)
549 vFloorPoint.
x = (0.5f * ((fObjectAngle / (fFieldOfView * 0.5f))) + 0.5f) * vFloatScreenSize.x;
552 vFloorPoint.
y = (vFloatScreenSize.y / 2.0f) + (vFloatScreenSize.y / fDistanceToObject) / std::cos(fObjectAngle / 2.0f);
555 olc::vf2d vObjectSize = { float(GetObjectWidth(object->nGenericID)), float(GetObjectHeight(object->nGenericID)) };
558 vObjectSize *= 2.0f * vFloatScreenSize.
y;
561 vObjectSize /= fDistanceToObject;
568 vObjectTopLeft = { vFloorPoint.
x - vObjectSize.
x / 2.0f, vFloorPoint.
y - vObjectSize.
y };
571 for (
float y = 0; y < vObjectSize.
y; y++)
573 for (
float x = 0; x < vObjectSize.
x; x++)
576 float fSampleX = x / vObjectSize.
x;
577 float fSampleY = y / vObjectSize.
y;
580 float fNiceAngle = fCameraHeading -
object->fHeading + 3.14159f / 4.0f;
581 if (fNiceAngle < 0) fNiceAngle += 2.0f * 3.14159f;
582 if (fNiceAngle > 2.0f * 3.14159f) fNiceAngle -= 2.0f * 3.14159f;
583 olc::Pixel p = SelectObjectPixel(object->nGenericID, fSampleX, fSampleY, fDistanceToObject, fNiceAngle);
586 olc::vi2d a = { int(vObjectTopLeft.
x + x), int(vObjectTopLeft.
y + y) };
590 if (a.
x >= 0 && a.
x < vScreenSize.x && a.
y >= 0 && a.
y < vScreenSize.y && p.
a == 255)
593 DepthDraw(a.
x, a.
y, fDistanceToObject, p);
608bool olc::rcw::Engine::CastRayDDA(
const olc::vf2d& vOrigin,
const olc::vf2d& vDirection, sTileHit& hit)
610 olc::vf2d vRayDelta = { sqrt(1 + (vDirection.
y / vDirection.
x) * (vDirection.
y / vDirection.
x)), sqrt(1 + (vDirection.
x / vDirection.
y) * (vDirection.
x / vDirection.
y)) };
616 if (vDirection.
x < 0)
618 vStepDistance.
x = -1;
619 vSideDistance.
x = (vOrigin.
x - (float)vMapCheck.
x) * vRayDelta.
x;
624 vSideDistance.
x = ((float)vMapCheck.
x + 1.0f - vOrigin.
x) * vRayDelta.
x;
627 if (vDirection.
y < 0)
629 vStepDistance.
y = -1;
630 vSideDistance.
y = (vOrigin.
y - (float)vMapCheck.
y) * vRayDelta.
y;
635 vSideDistance.
y = ((float)vMapCheck.
y + 1.0f - vOrigin.
y) * vRayDelta.
y;
640 float fMaxDistance = 100.0f;
641 float fDistance = 0.0f;
642 bool bTileFound =
false;
643 while (!bTileFound && fDistance < fMaxDistance)
645 if (vSideDistance.
x < vSideDistance.
y)
647 vSideDistance.
x += vRayDelta.
x;
648 vMapCheck.
x += vStepDistance.
x;
652 vSideDistance.
y += vRayDelta.
y;
653 vMapCheck.
y += vStepDistance.
y;
656 olc::vf2d rayDist = { (float)vMapCheck.
x - vOrigin.
x, (
float)vMapCheck.
y - vOrigin.
y };
657 fDistance = rayDist.
mag();
660 if (IsLocationSolid(
float(vMapCheck.
x), float(vMapCheck.
y)))
662 vHitTile = vMapCheck;
665 hit.vTilePos = vMapCheck;
670 float m = vDirection.
y / vDirection.
x;
675 if (vOrigin.
y <= vMapCheck.
y)
677 if (vOrigin.
x <= vMapCheck.
x)
680 vIntersection.
y = m * (vMapCheck.
x - vOrigin.
x) + vOrigin.
y;
681 vIntersection.
x = float(vMapCheck.
x);
682 hit.fSampleX = vIntersection.
y - std::floor(vIntersection.
y);
684 else if (vOrigin.
x >= (vMapCheck.
x + 1))
687 vIntersection.
y = m * ((vMapCheck.
x + 1) - vOrigin.
x) + vOrigin.
y;
688 vIntersection.
x = float(vMapCheck.
x + 1);
689 hit.fSampleX = vIntersection.
y - std::floor(vIntersection.
y);
694 vIntersection.
y = float(vMapCheck.
y);
695 vIntersection.
x = (vMapCheck.
y - vOrigin.
y) / m + vOrigin.
x;
696 hit.fSampleX = vIntersection.
x - std::floor(vIntersection.
x);
700 if (vIntersection.
y < vMapCheck.
y)
703 vIntersection.
y = float(vMapCheck.
y);
704 vIntersection.
x = (vMapCheck.
y - vOrigin.
y) / m + vOrigin.
x;
705 hit.fSampleX = vIntersection.
x - std::floor(vIntersection.
x);
708 else if (vOrigin.
y >= vMapCheck.
y + 1)
710 if (vOrigin.
x <= vMapCheck.
x)
713 vIntersection.
y = m * (vMapCheck.
x - vOrigin.
x) + vOrigin.
y;
714 vIntersection.
x = float(vMapCheck.
x);
715 hit.fSampleX = vIntersection.
y - std::floor(vIntersection.
y);
717 else if (vOrigin.
x >= (vMapCheck.
x + 1))
720 vIntersection.
y = m * ((vMapCheck.
x + 1) - vOrigin.
x) + vOrigin.
y;
721 vIntersection.
x = float(vMapCheck.
x + 1);
722 hit.fSampleX = vIntersection.
y - std::floor(vIntersection.
y);
727 vIntersection.
y = float(vMapCheck.
y + 1);
728 vIntersection.
x = ((vMapCheck.
y + 1) - vOrigin.
y) / m + vOrigin.
x;
729 hit.fSampleX = vIntersection.
x - std::floor(vIntersection.
x);
732 if (vIntersection.
y > (vMapCheck.
y + 1))
735 vIntersection.
y = float(vMapCheck.
y + 1);
736 vIntersection.
x = ((vMapCheck.
y + 1) - vOrigin.
y) / m + vOrigin.
x;
737 hit.fSampleX = vIntersection.
x - std::floor(vIntersection.
x);
742 if (vOrigin.
x <= vMapCheck.
x)
745 vIntersection.
y = m * (vMapCheck.
x - vOrigin.
x) + vOrigin.
y;
746 vIntersection.
x = float(vMapCheck.
x);
747 hit.fSampleX = vIntersection.
y - std::floor(vIntersection.
y);
749 else if (vOrigin.
x >= (vMapCheck.
x + 1))
752 vIntersection.
y = m * ((vMapCheck.
x + 1) - vOrigin.
x) + vOrigin.
y;
753 vIntersection.
x = float(vMapCheck.
x + 1);
754 hit.fSampleX = vIntersection.
y - std::floor(vIntersection.
y);
758 hit.vHitPos = vIntersection;
Definition olcPixelGameEngine.h:1615
Definition olcPGEX_RayCastWorld.h:129
std::unordered_map< uint32_t, std::shared_ptr< olc::rcw::Object > > mapObjects
Definition olcPGEX_RayCastWorld.h:179
CellSide
Definition olcPGEX_RayCastWorld.h:133
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
virtual void HandleObjectVsObject(std::shared_ptr< olc::rcw::Object > object1, std::shared_ptr< olc::rcw::Object > object2)
Engine(const int screen_w, const int screen_h, const float fov)
virtual olc::Pixel SelectObjectPixel(const uint32_t id, const float sample_x, const float sample_y, const float distance, const float angle)=0
virtual float GetObjectHeight(const uint32_t id)=0
void SetCamera(const olc::vf2d &pos, const float heading)
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)
virtual float GetObjectWidth(const uint32_t id)=0
virtual bool IsLocationSolid(const float tile_x, const float tile_y)=0
virtual void Update(float fElapsedTime)
Definition olcPGEX_RayCastWorld.h:89
bool bRemove
Definition olcPGEX_RayCastWorld.h:106
bool bIsActive
Definition olcPGEX_RayCastWorld.h:118
float fRadius
Definition olcPGEX_RayCastWorld.h:102
float fSpeed
Definition olcPGEX_RayCastWorld.h:98
bool bCollideWithObjects
Definition olcPGEX_RayCastWorld.h:112
bool bNotifySceneryCollision
Definition olcPGEX_RayCastWorld.h:110
void Turn(const float fTurnSpeed)
uint32_t nGenericID
Definition olcPGEX_RayCastWorld.h:92
bool bCanBeMoved
Definition olcPGEX_RayCastWorld.h:116
olc::vf2d vel
Definition olcPGEX_RayCastWorld.h:96
void Strafe(const float fStrafeSpeed)
bool bNotifyObjectCollision
Definition olcPGEX_RayCastWorld.h:114
float fHeading
Definition olcPGEX_RayCastWorld.h:100
void Walk(const float fWalkSpeed)
bool bCollideWithScenery
Definition olcPGEX_RayCastWorld.h:108
olc::vf2d pos
Definition olcPGEX_RayCastWorld.h:94
bool bVisible
Definition olcPGEX_RayCastWorld.h:104
Definition olcPixelGameEngine.h:593
v_2d< float > vf2d
Definition olcPixelGameEngine.h:894
Definition olcPixelGameEngine.h:924
uint8_t a
Definition olcPixelGameEngine.h:928
T x
Definition olcPixelGameEngine.h:604
T y
Definition olcPixelGameEngine.h:606
v_2d norm() const
Definition olcPixelGameEngine.h:641
constexpr v_2d perp() const
Definition olcPixelGameEngine.h:648
auto mag() const
Definition olcPixelGameEngine.h:629