olcPixelGameEngine v2.28
The official distribution of olcPixelGameEngine, a tool used in javidx9's YouTube videos and projects
Loading...
Searching...
No Matches
olcPGEX_RayCastWorld.h
Go to the documentation of this file.
1/*
2 olcPGEX_RayCastWorld.h
3
4 +-------------------------------------------------------------+
5 | OneLoneCoder Pixel Game Engine Extension |
6 | Ray Cast World v1.02 |
7 +-------------------------------------------------------------+
8
9 NOTE: UNDER ACTIVE DEVELOPMENT - THERE ARE BUGS/GLITCHES
10
11 What is this?
12 ~~~~~~~~~~~~~
13 This is an extension to the olcPixelGameEngine, which provides
14 a quick and easy to use, flexible, skinnable, ray-cast 3D world
15 engine, which handles all graphics and collisions within a
16 pseudo 3D world.
17
18 It is designed to be implementation independent. Please see example
19 files for usage instructions.
20
21 Video: https://youtu.be/Vij_obgv9h4
22
23 License (OLC-3)
24 ~~~~~~~~~~~~~~~
25
26 Copyright 2018 - 2020 OneLoneCoder.com
27
28 Redistribution and use in source and binary forms, with or without
29 modification, are permitted provided that the following conditions
30 are met:
31
32 1. Redistributions or derivations of source code must retain the above
33 copyright notice, this list of conditions and the following disclaimer.
34
35 2. Redistributions or derivative works in binary form must reproduce
36 the above copyright notice. This list of conditions and the following
37 disclaimer must be reproduced in the documentation and/or other
38 materials provided with the distribution.
39
40 3. Neither the name of the copyright holder nor the names of its
41 contributors may be used to endorse or promote products derived
42 from this software without specific prior written permission.
43
44 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
45 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
46 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
47 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
48 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
49 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
50 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
51 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
52 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
54 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55
56 Links
57 ~~~~~
58 YouTube: https://www.youtube.com/javidx9
59 Discord: https://discord.gg/WhwHUMV
60 Twitter: https://www.twitter.com/javidx9
61 Twitch: https://www.twitch.tv/javidx9
62 GitHub: https://www.github.com/onelonecoder
63 Homepage: https://www.onelonecoder.com
64
65 Author
66 ~~~~~~
67 David Barr, aka javidx9, ©OneLoneCoder 2019, 2020
68
69 Revisions:
70 1.00: Initial Release
71 1.01: Fix NaN check on overlap distance (Thanks Dandistine)
72 1.02: Added dynamic step size for collisions
73*/
74
75#ifndef OLC_PGEX_RAYCASTWORLD_H
76#define OLC_PGEX_RAYCASTWORLD_H
77
78#include <unordered_map>
79#include <algorithm>
80
81#include "olcPixelGameEngine.h"
82
83namespace olc
84{
85 namespace rcw
86 {
87 // Base class for objects that exist in world
88 class Object
89 {
90 public:
91 // Linkage to user object description
92 uint32_t nGenericID = 0;
93 // Position in tile/world space
95 // Velocity in tile/world space
97 // Speed of object
98 float fSpeed = 0.0f;
99 // Angular direction of object
100 float fHeading = 0.0f;
101 // Collision radius of object
102 float fRadius = 0.5f;
103 // Is drawn?
104 bool bVisible = true;
105 // Flag to be removed form world
106 bool bRemove = false;
107 // Can collide with scenery?
109 // Notify scenery collision?
111 // Can collide with other objects?
113 // Notify object collisions?
115 // Can this object be moved by another object?
116 bool bCanBeMoved = true;
117 // Has physics/collisions applied
118 bool bIsActive = true;
119
120 void Walk(const float fWalkSpeed);
121 void Strafe(const float fStrafeSpeed);
122 void Turn(const float fTurnSpeed);
123 void Stop();
124 };
125
126 // The RayCastWorld Engine - Inherit from this, implement abstract
127 // methods, call Update() and Render() when required
128 class Engine : public olc::PGEX
129 {
130 public:
131 // Identifies side of cell
132 enum class CellSide
133 {
134 North,
135 East,
136 South,
137 West,
138 Top,
139 Bottom,
140 };
141
142 public:
143 // Construct world rednering parameters
144 Engine(const int screen_w, const int screen_h, const float fov);
145
146 protected:
147 // ABSTRACT - User must return a suitable olc::Pixel depending on world location information provided
148 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;
149
150 // ABSTRACT - User must return a boolean indicating if the tile is solid or not
151 virtual bool IsLocationSolid(const float tile_x, const float tile_y) = 0;
152
153 // ABSTRACT - User must return sizes of requested objects in Unit Cell Size
154 virtual float GetObjectWidth(const uint32_t id) = 0;
155 virtual float GetObjectHeight(const uint32_t id) = 0;
156
157 // ABSTRACT - User must return suitable olc::Pixel for object sprite sample location
158 virtual olc::Pixel SelectObjectPixel(const uint32_t id, const float sample_x, const float sample_y, const float distance, const float angle) = 0;
159
160 // OPTIONAL - User can handle collsiion response with scenery should they choose to
161 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);
162
163 // OPTIONAL - User can handle collsiion response with objects should they choose to
164 virtual void HandleObjectVsObject(std::shared_ptr<olc::rcw::Object> object1, std::shared_ptr<olc::rcw::Object> object2);
165
166
167
168 public:
169 // Sets In-Game Camera position
170 void SetCamera(const olc::vf2d& pos, const float heading);
171
172 // Called to update world state
173 virtual void Update(float fElapsedTime);
174
175 // Called to draw the world and its contents
176 void Render();
177
178 public:
179 std::unordered_map<uint32_t, std::shared_ptr<olc::rcw::Object>> mapObjects;
180
181 private:
182 // A convenient utility struct to store all the info required to understand how a ray
183 // has hit a specific tile
184 struct sTileHit
185 {
186 olc::vi2d vTilePos = { 0,0 };
187 olc::vf2d vHitPos = { 0,0 };
188 float fLength = 0.0f;
189 float fSampleX = 0.0f;
191 };
192
193 // Cast ray into tile world, and return info about what it hits (if anything)
194 bool CastRayDDA(const olc::vf2d& vOrigin, const olc::vf2d& vDirection, sTileHit& hit);
195
196 // Convenient constants in algorithms
197 const olc::vi2d vScreenSize;
198 const olc::vi2d vHalfScreenSize;
199 const olc::vf2d vFloatScreenSize;
200 float fFieldOfView = 0.0f;
201
202 // A depth buffer used to sort pixels in Z-Axis
203 std::unique_ptr<float[]> pDepthBuffer;
204
205 // Local store of camera position and direction
206 olc::vf2d vCameraPos = { 5.0f, 5.0f };
207 float fCameraHeading = 0.0f;
208 };
209 }
210}
211
212
213#ifdef OLC_PGEX_RAYCASTWORLD
214#undef OLC_PGEX_RAYCASTWORLD
215
216#undef min
217#undef max
218
219void olc::rcw::Object::Walk(const float fWalkSpeed)
220{
221 fSpeed = fWalkSpeed;
222 vel = olc::vf2d(std::cos(fHeading), std::sin(fHeading)) * fSpeed;
223}
224
225void olc::rcw::Object::Strafe(const float fStrafeSpeed)
226{
227 fSpeed = fStrafeSpeed;
228 vel = olc::vf2d(std::cos(fHeading), std::sin(fHeading)).perp() * fSpeed;
229}
230
231void olc::rcw::Object::Turn(const float fTurnSpeed)
232{
233 fHeading += fTurnSpeed;
234
235 // Wrap heading to sensible angle
236 if (fHeading < -3.14159f) fHeading += 2.0f * 3.14159f;
237 if (fHeading > 3.14159f) fHeading -= 2.0f * 3.14159f;
238}
239
240
242{
243 fSpeed = 0;
244 vel = { 0,0 };
245}
246
247olc::rcw::Engine::Engine(const int screen_w, const int screen_h, const float fov) :
248 vScreenSize(screen_w, screen_h),
249 vHalfScreenSize(screen_w / 2, screen_h / 2),
250 vFloatScreenSize(float(screen_w), float(screen_h))
251{
252 fFieldOfView = fov;
253 pDepthBuffer.reset(new float[vScreenSize.x * vScreenSize.y]);
254}
255
256
257void olc::rcw::Engine::SetCamera(const olc::vf2d& pos, const float heading)
258{
259 vCameraPos = pos;
260 fCameraHeading = heading;
261}
262
263
264void olc::rcw::Engine::Update(float fElapsedTime)
265{
266 // Update the position and statically resolve for collisions against the map
267 for (auto& ob : mapObjects)
268 {
269 std::shared_ptr<olc::rcw::Object> object = ob.second;
270 if (!object->bIsActive) continue;
271
272 int nSteps = 1;
273 float fDelta = fElapsedTime;
274 float fTotalTravel = (object->vel * fElapsedTime).mag2();
275 float fTotalRadius = (object->fRadius * object->fRadius);
276
277 if(fTotalTravel >= fTotalRadius)
278 {
279 float fSteps = std::ceil(fTotalTravel / fTotalRadius);
280 nSteps = int(fSteps);
281 fDelta = fElapsedTime / fSteps;
282 }
283
284 for (int nStep = 0; nStep < nSteps; nStep++)
285 {
286 // Determine where object is trying to be
287 olc::vf2d vPotentialPosition = object->pos + object->vel * fDelta;
288
289 // If the object can collide with other objects
290 if (object->bCollideWithObjects)
291 {
292 // Iterate through all other objects (this can be costly)
293 for (auto& ob2 : mapObjects)
294 {
295 std::shared_ptr<olc::rcw::Object> target = ob2.second;
296
297 // Ignore if target object cant interact
298 if (!target->bCollideWithObjects) continue;
299
300 // Don't test against self
301 if (target == object) continue;
302
303 // Quick check to see if objects overlap...
304 if ((target->pos - object->pos).mag2() <= (target->fRadius + object->fRadius) * (target->fRadius + object->fRadius))
305 {
306 // ..they do. Calculate displacement required
307 float fDistance = (target->pos - object->pos).mag();
308 float fOverlap = 1.0f * (fDistance - object->fRadius - target->fRadius);
309
310 // Object will always give way to target
311 vPotentialPosition -= (object->pos - target->pos) / fDistance * fOverlap;
312
313 if (target->bCanBeMoved)
314 target->pos += (object->pos - target->pos) / fDistance * fOverlap;
315
316 if (object->bNotifyObjectCollision)
317 HandleObjectVsObject(object, target);
318 }
319
320
321 }
322 }
323
324 // If the object can collide with scenery...
325 if (object->bCollideWithScenery)
326 {
327 // ...Determine an area of cells to check for collision. We use a region
328 // to account for diagonal collisions, and corner collisions.
329 olc::vi2d vCurrentCell = object->pos;
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 };
333
334
335
336
337 // Iterate through each cell in test area
338 olc::vi2d vCell;
339 for (vCell.y = vAreaTL.y; vCell.y <= vAreaBR.y; vCell.y++)
340 {
341 for (vCell.x = vAreaTL.x; vCell.x <= vAreaBR.x; vCell.x++)
342 {
343 // Check if the cell is actually solid...
344 olc::vf2d vCellMiddle = olc::vf2d(float(vCell.x) + 0.5f, float(vCell.y) + 0.5f);
345 if (IsLocationSolid(vCellMiddle.x, vCellMiddle.y))
346 {
347 // ...it is! So work out nearest point to future player position, around perimeter
348 // of cell rectangle. We can test the distance to this point to see if we have
349 // collided.
350
351 olc::vf2d vNearestPoint;
352 // Inspired by this (very clever btw)
353 // https://stackoverflow.com/questions/45370692/circle-rectangle-collision-response
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)));
356
357 // But modified to work :P
358 olc::vf2d vRayToNearest = vNearestPoint - vPotentialPosition;
359 float fOverlap = object->fRadius - vRayToNearest.mag();
360 if (std::isnan(fOverlap)) fOverlap = 0;// Thanks Dandistine!
361
362 // If overlap is positive, then a collision has occurred, so we displace backwards by the
363 // overlap amount. The potential position is then tested against other tiles in the area
364 // therefore "statically" resolving the collision
365 if (fOverlap > 0)
366 {
367 // Statically resolve the collision
368 vPotentialPosition = vPotentialPosition - vRayToNearest.norm() * fOverlap;
369
370 // Notify system that a collision has occurred
371 if (object->bNotifySceneryCollision)
372 {
374 if (vNearestPoint.x == float(vCell.x)) side = olc::rcw::Engine::CellSide::West;
375 if (vNearestPoint.x == float(vCell.x + 1)) side = olc::rcw::Engine::CellSide::East;
376 if (vNearestPoint.y == float(vCell.y)) side = olc::rcw::Engine::CellSide::North;
377 if (vNearestPoint.y == float(vCell.y + 1)) side = olc::rcw::Engine::CellSide::South;
378
379 HandleObjectVsScenery(object, vCell.x, vCell.y, side, vNearestPoint.x - float(vCell.x), vNearestPoint.y - float(vCell.y));
380 }
381 }
382 }
383 }
384 }
385 }
386
387 // Set the objects new position to the allowed potential position
388 object->pos = vPotentialPosition;
389 }
390 }
391}
392
394{
395 // Utility lambda to draw to screen and depth buffer
396 auto DepthDraw = [&](int x, int y, float z, olc::Pixel p)
397 {
398 if (z <= pDepthBuffer[y * vScreenSize.x + x])
399 {
400 pge->Draw(x, y, p);
401 pDepthBuffer[y * vScreenSize.x + x] = z;
402 }
403 };
404
405
406 // Clear screen and depth buffer ========================================
407 // pge->Clear(olc::BLACK); <- Left to user to decide
408 for (int i = 0; i < vScreenSize.x * vScreenSize.y; i++)
409 pDepthBuffer[i] = INFINITY;
410
411 // Draw World ===========================================================
412
413 // For each column on screen...
414 for (int x = 0; x < vScreenSize.x; x++)
415 {
416 // ...create a ray eminating from player position into world...
417 float fRayAngle = (fCameraHeading - (fFieldOfView / 2.0f)) + (float(x) / vFloatScreenSize.x) * fFieldOfView;
418
419 // ...create unit vector for that ray...
420 olc::vf2d vRayDirection = { std::cos(fRayAngle), std::sin(fRayAngle) };
421
422 // ... and cast ray into world, see what it hits (if anything)
423 sTileHit hit;
424
425 // Assuming it hits nothing, then we draw to the middle of the screen (far far away)
426 float fRayLength = INFINITY;
427
428 // Otherwise...
429 if (CastRayDDA(vCameraPos, vRayDirection, hit))
430 {
431 // It has hit something, so extract information to draw column
432 olc::vf2d vRay = hit.vHitPos - vCameraPos;
433
434 // Length of ray is vital for pseudo-depth, but we'll also cosine correct to remove fisheye
435 fRayLength = vRay.mag() * std::cos(fRayAngle - fCameraHeading);
436 }
437
438 // Calculate locations in column that divides ceiling, wall and floor
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;
443
444 // Now draw the column from top to bottom
445 for (int y = 0; y < vScreenSize.y; y++)
446 {
447 if (y <= int(fCeiling))
448 {
449 // For floors and ceilings, we don't use the ray, instead we just pseudo-project
450 // a plane, a la Mode 7. First calculate depth into screen...
451 float fPlaneZ = (vFloatScreenSize.y / 2.0f) / ((vFloatScreenSize.y / 2.0f) - float(y));
452
453 // ... then project polar coordinate (r, theta) from camera into screen (x, y), again
454 // compensating with cosine to remove fisheye
455 olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading);
456
457 // Work out which planar tile we are in
458 int nPlaneTileX = int(vPlanePoint.x);
459 int nPlaneTileY = int(vPlanePoint.y);
460
461 // Work out normalised offset into planar tile
462 float fPlaneSampleX = vPlanePoint.x - nPlaneTileX;
463 float fPlaneSampleY = vPlanePoint.y - nPlaneTileY;
464
465 // Location is marked as ceiling
466 olc::Pixel pixel = SelectSceneryPixel(nPlaneTileX, nPlaneTileY, olc::rcw::Engine::CellSide::Top, fPlaneSampleX, fPlaneSampleY, fPlaneZ);
467
468 // Draw ceiling pixel - no depth buffer required
469 pge->Draw(x, y, pixel);
470 }
471 else if (y > int(fCeiling) && y <= int(fFloor))
472 {
473 // Location is marked as wall. Here we will sample the appropriate
474 // texture at the hit location. The U sample coordinate is provided
475 // by the "hit" structure, though we will need to scan the V sample
476 // coordinate based upon the height of the wall in screen space
477
478 // Create normalised "V" based on height of wall --> (0.0 to 1.0)
479 float fSampleY = (float(y) - fCeiling) / fWallHeight;
480
481 // Select appropriate texture of that wall
482 olc::Pixel pixel = SelectSceneryPixel(hit.vTilePos.x, hit.vTilePos.y, hit.eSide, hit.fSampleX, fSampleY, fRayLength);
483
484 // Finally draw the screen pixel doing a depth test too
485 DepthDraw(x, y, fRayLength, pixel);
486 }
487 else
488 {
489 // For floors and ceilings, we don't use the ray, instead we just pseudo-project
490 // a plane, a la Mode 7. First calculate depth into screen...
491 float fPlaneZ = (vFloatScreenSize.y / 2.0f) / (float(y) - (vFloatScreenSize.y / 2.0f));
492
493 // ... then project polar coordinate (r, theta) from camera into screen (x, y), again
494 // compensating with cosine to remove fisheye
495 olc::vf2d vPlanePoint = vCameraPos + vRayDirection * fPlaneZ * 2.0f / std::cos(fRayAngle - fCameraHeading);
496
497 // Work out which planar tile we are in
498 int nPlaneTileX = int(vPlanePoint.x);
499 int nPlaneTileY = int(vPlanePoint.y);
500
501 // Work out normalised offset into planar tile
502 float fPlaneSampleX = vPlanePoint.x - nPlaneTileX;
503 float fPlaneSampleY = vPlanePoint.y - nPlaneTileY;
504
505 // Location is marked as floor
506 olc::Pixel pixel = SelectSceneryPixel(nPlaneTileX, nPlaneTileY, olc::rcw::Engine::CellSide::Bottom, fPlaneSampleX, fPlaneSampleY, fPlaneZ);
507
508 // Draw floor pixel - no depth buffer required
509 pge->Draw(x, y, pixel);
510 }
511 }
512 }
513
514 // Scenery is now drawn, and depth buffer is filled. We can now draw
515 // the ingame objects. Assuming binary transparency, we've no need to
516 // sort objects
517
518 // Iterate through all in game objects
519 for (const auto& ob : mapObjects)
520 {
521 const std::shared_ptr<olc::rcw::Object> object = ob.second;
522
523 // If object is invisible, nothing to do - this is useful
524 // for both effects, and making sure we dont render the
525 // "player" at the camera location perhaps
526 if (!object->bVisible) continue;
527
528 // Create vector from camera to object
529 olc::vf2d vObject = object->pos - vCameraPos;
530
531 // Calculate distance object is away from camera
532 float fDistanceToObject = vObject.mag();
533
534 // Check if object center is within camera FOV...
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;
538
539 // ...with a bias based upon distance - allows us to have object centers offscreen
540 bool bInPlayerFOV = fabs(fObjectAngle) < (fFieldOfView + (1.0f / fDistanceToObject)) / 2.0f;
541
542 // If object is within view, and not too close to camera, draw it!
543 if (bInPlayerFOV && vObject.mag() >= 0.5f)
544 {
545 // Work out its position on the floor...
546 olc::vf2d vFloorPoint;
547
548 // Horizontal screen location is determined based on object angle relative to camera heading
549 vFloorPoint.x = (0.5f * ((fObjectAngle / (fFieldOfView * 0.5f))) + 0.5f) * vFloatScreenSize.x;
550
551 // Vertical screen location is projected distance
552 vFloorPoint.y = (vFloatScreenSize.y / 2.0f) + (vFloatScreenSize.y / fDistanceToObject) / std::cos(fObjectAngle / 2.0f);
553
554 // First we need the objects size...
555 olc::vf2d vObjectSize = { float(GetObjectWidth(object->nGenericID)), float(GetObjectHeight(object->nGenericID)) };
556
557 // ...which we can scale into world space (maintaining aspect ratio)...
558 vObjectSize *= 2.0f * vFloatScreenSize.y;
559
560 // ...then project into screen space
561 vObjectSize /= fDistanceToObject;
562
563 // Second we need the objects top left position in screen space...
564 olc::vf2d vObjectTopLeft;
565
566 // ...which is relative to the objects size and assumes the middle of the object is
567 // the location in world space
568 vObjectTopLeft = { vFloorPoint.x - vObjectSize.x / 2.0f, vFloorPoint.y - vObjectSize.y };
569
570 // Now iterate through the objects screen pixels
571 for (float y = 0; y < vObjectSize.y; y++)
572 {
573 for (float x = 0; x < vObjectSize.x; x++)
574 {
575 // Create a normalised sample coordinate
576 float fSampleX = x / vObjectSize.x;
577 float fSampleY = y / vObjectSize.y;
578
579 // Get pixel from a suitable texture
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);
584
585 // Calculate screen pixel location
586 olc::vi2d a = { int(vObjectTopLeft.x + x), int(vObjectTopLeft.y + y) };
587
588 // Check if location is actually on screen (to not go OOB on depth buffer)
589 // and if the pixel is indeed visible (has no transparency component)
590 if (a.x >= 0 && a.x < vScreenSize.x && a.y >= 0 && a.y < vScreenSize.y && p.a == 255)
591 {
592 // Draw the pixel taking into account the depth buffer
593 DepthDraw(a.x, a.y, fDistanceToObject, p);
594 }
595 }
596 }
597 }
598 }
599}
600
601void 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)
602{}
603
604void olc::rcw::Engine::HandleObjectVsObject(std::shared_ptr<olc::rcw::Object> object1, std::shared_ptr<olc::rcw::Object> object2)
605{}
606
607// Will be explained in upcoming video...
608bool olc::rcw::Engine::CastRayDDA(const olc::vf2d& vOrigin, const olc::vf2d& vDirection, sTileHit& hit)
609{
610 olc::vf2d vRayDelta = { sqrt(1 + (vDirection.y / vDirection.x) * (vDirection.y / vDirection.x)), sqrt(1 + (vDirection.x / vDirection.y) * (vDirection.x / vDirection.y)) };
611
612 olc::vi2d vMapCheck = vOrigin;
613 olc::vf2d vSideDistance;
614 olc::vi2d vStepDistance;
615
616 if (vDirection.x < 0)
617 {
618 vStepDistance.x = -1;
619 vSideDistance.x = (vOrigin.x - (float)vMapCheck.x) * vRayDelta.x;
620 }
621 else
622 {
623 vStepDistance.x = 1;
624 vSideDistance.x = ((float)vMapCheck.x + 1.0f - vOrigin.x) * vRayDelta.x;
625 }
626
627 if (vDirection.y < 0)
628 {
629 vStepDistance.y = -1;
630 vSideDistance.y = (vOrigin.y - (float)vMapCheck.y) * vRayDelta.y;
631 }
632 else
633 {
634 vStepDistance.y = 1;
635 vSideDistance.y = ((float)vMapCheck.y + 1.0f - vOrigin.y) * vRayDelta.y;
636 }
637
638 olc::vf2d vIntersection;
639 olc::vi2d vHitTile;
640 float fMaxDistance = 100.0f;
641 float fDistance = 0.0f;
642 bool bTileFound = false;
643 while (!bTileFound && fDistance < fMaxDistance)
644 {
645 if (vSideDistance.x < vSideDistance.y)
646 {
647 vSideDistance.x += vRayDelta.x;
648 vMapCheck.x += vStepDistance.x;
649 }
650 else
651 {
652 vSideDistance.y += vRayDelta.y;
653 vMapCheck.y += vStepDistance.y;
654 }
655
656 olc::vf2d rayDist = { (float)vMapCheck.x - vOrigin.x, (float)vMapCheck.y - vOrigin.y };
657 fDistance = rayDist.mag();
658
659
660 if (IsLocationSolid(float(vMapCheck.x), float(vMapCheck.y)))
661 {
662 vHitTile = vMapCheck;
663 bTileFound = true;
664
665 hit.vTilePos = vMapCheck;
666
667
668 // Find accurate Hit Location
669
670 float m = vDirection.y / vDirection.x;
671
672
673 // From Top Left
674
675 if (vOrigin.y <= vMapCheck.y)
676 {
677 if (vOrigin.x <= vMapCheck.x)
678 {
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);
683 }
684 else if (vOrigin.x >= (vMapCheck.x + 1))
685 {
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);
690 }
691 else
692 {
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);
697 }
698
699
700 if (vIntersection.y < vMapCheck.y)
701 {
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);
706 }
707 }
708 else if (vOrigin.y >= vMapCheck.y + 1)
709 {
710 if (vOrigin.x <= vMapCheck.x)
711 {
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);
716 }
717 else if (vOrigin.x >= (vMapCheck.x + 1))
718 {
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);
723 }
724 else
725 {
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);
730 }
731
732 if (vIntersection.y > (vMapCheck.y + 1))
733 {
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);
738 }
739 }
740 else
741 {
742 if (vOrigin.x <= vMapCheck.x)
743 {
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);
748 }
749 else if (vOrigin.x >= (vMapCheck.x + 1))
750 {
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);
755 }
756 }
757
758 hit.vHitPos = vIntersection;
759 }
760 }
761
762 return bTileFound;
763}
764
765
766#endif // OLC_PGEX_RAYCASTWORLD
767#endif // OLC_PGEX_RAYCASTWORLD_H
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