From 639a7fddba4312c1006f06a34d9ee0f25b071552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 13 Mar 2020 16:44:02 +0100 Subject: [PATCH] Fix visibility and RCL --- TODO.txt | 1 + assets/levelX.gif | Bin 4758 -> 4764 bytes main.c | 74 ++++++++++-------- platform_pokitto.h | 2 +- raycastlib.h | 183 ++++++++++++++++++++++++++++++++++++++------- 5 files changed, 198 insertions(+), 62 deletions(-) diff --git a/TODO.txt b/TODO.txt index 113861f..6334ade 100644 --- a/TODO.txt +++ b/TODO.txt @@ -37,6 +37,7 @@ general: - easter egg(s) - optioal graphics enhance: vertical wall shading ("ambient occlusions")? - option for disabling wall transparency, for performance? +- open door by shooting at them? level ideas: diff --git a/assets/levelX.gif b/assets/levelX.gif index bb7b6d6ad3edf19cbbb55467b5664c09d8da502e..555f663d18fa22f0a8f547a0467771692dedc3f0 100644 GIT binary patch delta 528 zcmV+r0`L8nC7dO&m=J%(imh>~t=l>&*IKFI8m>;!tG+s|$ZD?W>Z{ZmuIOq63+k=o zS+3@4t?4?C@4Bt+YODKNt^EqC|2nGyi>d>AsRbLV2YVq$aS`H4uOKR?8%m=#s-t7- zpdWAw8v6kT^8t(Su^;d?*k@52P_iG;1}KXW@puD)3I4I^*`R-4%BnKUqcNMK8N0C^ z`>~4}a&)>SKvos$n{(by^V`dkP$@EglQ9BFjHNYqBWY z1}dAc^0=_I)v#~MviWMLI|`;>TCtN7J#cWP_Zg}#d#jUWr%X$stXiV9%C>G>pkBM4 zwlc9g+O#BEqvwCBw=y!QWIK=kaHL>cT?BEsee1V#JEwzdqa1p+Ac0ML%dGC0xb7;k zGux`B>Zxe^0h$ZB;n@c3sJaI#rxaSJiVC=MYP#37xltOq@7TCY^0>Sjn0>IiTxzIx zOSsD#8cc+`xGT3tBD*ykCdb>mfXTY(AiazWx-^KZs6u~+6}G#i$*a7R#+izC6*r4Ng-Wj5YOY4GpxC;e+6u0; zTByE?uI;L>^N6q2YOeRnuKlX7^2o2V>aVs+uwil(7a^YU%Aj((p)_ivIy$Bf`T?hK zu^(VCAFv1<`vG5reHOI=BKrYtP_h^ik2esQ4GW^ZVg9GA`m%pK`lT;xu^6kd9J>e} ziy0vM0V2BrBx|w@`=R#0jze3i3zMj&8m4nvrxkIrr;xGQvaucevHZicBU`daD4~wV}>$f#3xD|m-dCRQsc)0&+D;!F< zt;(OCTDBj6xsDs2ZLp51d!TYkp>?XLlRKxSdrg@erH^~7KDJVm_ML&*2dc}Za0|1F z8YWD1y0`nQ4;#2Liz--AyGsg~zN?O_%cOl9v%`X0UB!RPtH;ZZi(6ZbtG(M>rO6w( zlS{tW%M{L=ZQ(1vp4+OSd!ozRqv&hA+uOJ1o3u(Bu{@E!FB-nxTV3DF9|jAn|C^u# zjHposition.y == SFG_GAME_RESOLUTION_Y / 2) - { - uint8_t zValue = pixel->isWall ? SFG_RCLUnitToZBuffer(pixel->depth) : 255; - - for (uint8_t i = 0; i < SFG_RAYCASTING_SUBSAMPLE; ++i) - SFG_game.zBuffer[pixel->position.x * SFG_RAYCASTING_SUBSAMPLE + i] = - zValue; - } - if (pixel->isHorizon && pixel->depth > RCL_UNITS_PER_SQUARE * 16) { color = SFG_TRANSPARENT_COLOR; @@ -3621,6 +3612,25 @@ for (uint16_t j = 0; j < l; ++j) */ } +/** + Checks a 3D point visibility from player's position (WITHOUT considering + facing direction). +*/ +static inline uint8_t SFG_spriteIsVisible(RCL_Vector2D pos, RCL_Unit height, + uint8_t spriteSize) +{ + return + RCL_castRay3D( + SFG_player.camera.position, + SFG_player.camera.height, + pos, + height, + SFG_floorHeightAt, + SFG_ceilingHeightAt, + SFG_game.rayConstraints + ) == RCL_UNITS_PER_SQUARE; +} + void SFG_draw() { #if SFG_BACKGROUND_BLUR != 0 @@ -3705,17 +3715,18 @@ void SFG_draw() uint8_t spriteSize = SFG_GET_MONSTER_SPRITE_SIZE( SFG_MONSTER_TYPE_TO_INDEX(SFG_MR_TYPE(m))); - RCL_PixelInfo p = - RCL_mapToScreen( - worldPosition, - SFG_floorHeightAt( - SFG_MONSTER_COORD_TO_SQUARES(m.coords[0]), - SFG_MONSTER_COORD_TO_SQUARES(m.coords[1])) - + - SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize), - SFG_player.camera); + RCL_Unit worldHeight = + SFG_floorHeightAt( + SFG_MONSTER_COORD_TO_SQUARES(m.coords[0]), + SFG_MONSTER_COORD_TO_SQUARES(m.coords[1])) + + + SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize); - if (p.depth > 0) + RCL_PixelInfo p = + RCL_mapToScreen(worldPosition,worldHeight,SFG_player.camera); + + if (p.depth > 0 && + SFG_spriteIsVisible(worldPosition,worldHeight,spriteSize)) { const uint8_t *s = SFG_getMonsterSprite( @@ -3725,7 +3736,7 @@ void SFG_draw() SFG_drawScaledSprite(s, p.position.x * SFG_RAYCASTING_SUBSAMPLE,p.position.y, - RCL_perspectiveScale( + RCL_perspectiveScaleVertical( SFG_SPRITE_SIZE_PIXELS(spriteSize), p.depth), p.depth / (RCL_UNITS_PER_SQUARE * 2),p.depth); @@ -3756,21 +3767,19 @@ void SFG_draw() if (sprite != 0) { - RCL_PixelInfo p = - RCL_mapToScreen( - worldPosition, - SFG_floorHeightAt(e.coords[0],e.coords[1]) - + SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize), - SFG_player.camera); + RCL_Unit worldHeight = SFG_floorHeightAt(e.coords[0],e.coords[1]) + + SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize); - if (p.depth > 0) - { + RCL_PixelInfo p = + RCL_mapToScreen(worldPosition,worldHeight,SFG_player.camera); + + if (p.depth > 0 && + SFG_spriteIsVisible(worldPosition,worldHeight,spriteSize)) SFG_drawScaledSprite( sprite, p.position.x * SFG_RAYCASTING_SUBSAMPLE,p.position.y, - RCL_perspectiveScale(SFG_SPRITE_SIZE_PIXELS(spriteSize),p.depth), + RCL_perspectiveScaleVertical(SFG_SPRITE_SIZE_PIXELS(spriteSize),p.depth), p.depth / (RCL_UNITS_PER_SQUARE * 2),p.depth); - } } } @@ -3811,10 +3820,11 @@ void SFG_draw() ) / RCL_UNITS_PER_SQUARE; } - if (p.depth > 0) + if (p.depth > 0 && + SFG_spriteIsVisible(worldPosition,proj->position[2],spriteSize)) SFG_drawScaledSprite(s, p.position.x * SFG_RAYCASTING_SUBSAMPLE,p.position.y, - RCL_perspectiveScale(spriteSize,p.depth), + RCL_perspectiveScaleVertical(spriteSize,p.depth), SFG_fogValueDiminish(p.depth), p.depth); } diff --git a/platform_pokitto.h b/platform_pokitto.h index b3ce232..1a74e94 100644 --- a/platform_pokitto.h +++ b/platform_pokitto.h @@ -22,7 +22,7 @@ #define SFG_TEXTURE_DISTANCE 5000 #undef SFG_FPS -#define SFG_FPS 35 +#define SFG_FPS 30 #undef SFG_SCREEN_RESOLUTION_X #define SFG_SCREEN_RESOLUTION_X 110 diff --git a/raycastlib.h b/raycastlib.h index 8c51e79..bda7c59 100644 --- a/raycastlib.h +++ b/raycastlib.h @@ -26,7 +26,7 @@ author: Miloslav "drummyfish" Ciz license: CC0 1.0 - version: 0.903 + version: 0.904 */ #include @@ -116,6 +116,8 @@ #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4) #endif +#define RCL_HORIZONTAL_FOV_TAN (RCL_VERTICAL_FOV * 4) + #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2) #ifndef RCL_CAMERA_COLL_RADIUS @@ -242,7 +244,7 @@ typedef struct typedef struct { RCL_Vector2D position; - RCL_Unit direction; + RCL_Unit direction; // TODO: rename to "angle" to keep consistency RCL_Vector2D resolution; int16_t shear; /**< Shear offset in pixels (0 => no shear), can simulate looking up/down. */ @@ -305,10 +307,24 @@ typedef void /** Simple-interface function to cast a single ray. + @return The first collision result. */ RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc); +/** + Casts a 3D ray in 3D environment with floor and optional ceiling + (ceilingHeightFunc can be 0). This can be useful for hitscan shooting, + visibility checking etc. + + @return normalized ditance (0 to RCL_UNITS_PER_SQUARE) along the ray at which + the environment was hit, RCL_UNITS_PER_SQUARE means nothing was hit +*/ +RCL_Unit RCL_castRay3D( + RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2, + RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc, + RCL_RayConstraints constraints); + /** Maps a single point in the world to the screen (2D position + depth). */ @@ -352,6 +368,10 @@ RCL_Unit RCL_cosInt(RCL_Unit input); RCL_Unit RCL_sinInt(RCL_Unit input); +RCL_Unit RCL_tanInt(RCL_Unit input); + +RCL_Unit RCL_ctgInt(RCL_Unit input); + /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length. RCL_Vector2D RCL_normalize(RCL_Vector2D v); @@ -368,10 +388,16 @@ RCL_Unit RCL_len(RCL_Vector2D v); */ RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees); -///< Computes the change in size of an object due to perspective. -RCL_Unit RCL_perspectiveScale(RCL_Unit originalSize, RCL_Unit distance); +///< Computes the change in size of an object due to perspective (vertical FOV). +RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance); -RCL_Unit RCL_perspectiveScaleInverse(RCL_Unit originalSize, +RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize, + RCL_Unit scaledSize); + +RCL_Unit + RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance); + +RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize, RCL_Unit scaledSize); /** @@ -502,7 +528,7 @@ RCL_Unit *_RCL_floorPixelDistances = 0; uint32_t profile_RCL_absVal = 0; uint32_t profile_RCL_normalize = 0; uint32_t profile_RCL_vectorsAngleCos = 0; - uint32_t profile_RCL_perspectiveScale = 0; + uint32_t profile_RCL_perspectiveScaleVertical = 0; uint32_t profile_RCL_wrap = 0; uint32_t profile_RCL_divRoundDown = 0; #define RCL_profileCall(c) profile_##c += 1 @@ -521,7 +547,7 @@ RCL_Unit *_RCL_floorPixelDistances = 0; printf(" RCL_normalize: %d\n",profile_RCL_normalize);\ printf(" RCL_vectorsAngleCos: %d\n",profile_RCL_vectorsAngleCos);\ printf(" RCL_absVal: %d\n",profile_RCL_absVal);\ - printf(" RCL_perspectiveScale: %d\n",profile_RCL_perspectiveScale);\ + printf(" RCL_perspectiveScaleVertical: %d\n",profile_RCL_perspectiveScaleVertical);\ printf(" RCL_wrap: %d\n",profile_RCL_wrap);\ printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); } #else @@ -637,6 +663,16 @@ RCL_Unit RCL_sinInt(RCL_Unit input) return RCL_cosInt(input - RCL_UNITS_PER_SQUARE / 4); } +RCL_Unit RCL_tanInt(RCL_Unit input) +{ + return (RCL_sinInt(input) * RCL_UNITS_PER_SQUARE) / RCL_cosInt(input); +} + +RCL_Unit RCL_ctgInt(RCL_Unit input) +{ + return (RCL_cosInt(input) * RCL_UNITS_PER_SQUARE) / RCL_sinInt(input); +} + RCL_Vector2D RCL_angleToDirection(RCL_Unit angle) { RCL_profileCall(RCL_angleToDirection); @@ -892,17 +928,6 @@ void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, #if !RCL_RECTILINEAR h.distance = RCL_dist(h.position,ray.start); -#else - h.distance = (h.distance * 23) / 32; - - /* ^ UGLY HACK - - For some reason the computed distance with rectilinear is larger, the - correct distance is about 0.711 (~= 23/32) of the computed distance, so - we correct it here in this ugly way. - - TODO: investigate why, fix nicely - */ #endif if (typeFunc != 0) h.type = typeFunc(currentSquare.x,currentSquare.y); @@ -993,6 +1018,17 @@ void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc, RCL_Vector2D dir2 = RCL_angleToDirection(cam.direction + RCL_HORIZONTAL_FOV_HALF); + /* We scale the side distances so that the middle one is + RCL_UNITS_PER_SQUARE, which has to be this way. */ + + RCL_Unit cos = RCL_nonZero(RCL_cosInt(RCL_HORIZONTAL_FOV_HALF)); + + dir1.x = (dir1.x * RCL_UNITS_PER_SQUARE) / cos; + dir1.y = (dir1.y * RCL_UNITS_PER_SQUARE) / cos; + + dir2.x = (dir2.x * RCL_UNITS_PER_SQUARE) / cos; + dir2.y = (dir2.y * RCL_UNITS_PER_SQUARE) / cos; + RCL_Unit dX = dir2.x - dir1.x; RCL_Unit dY = dir2.y - dir1.y; @@ -1288,10 +1324,10 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y); fZ2World = fWallHeight - _RCL_camera.height; - fZ1Screen = _RCL_middleRow - RCL_perspectiveScale( + fZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( (fZ1World * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,distance); - fZ2Screen = _RCL_middleRow - RCL_perspectiveScale( + fZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( (fZ2World * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,distance); @@ -1299,10 +1335,10 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t { cWallHeight = _RCL_ceilFunction(hit.square.x,hit.square.y); cZ2World = cWallHeight - _RCL_camera.height; - cZ1Screen = _RCL_middleRow - RCL_perspectiveScale( + cZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( (cZ1World * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,distance); - cZ2Screen = _RCL_middleRow - RCL_perspectiveScale( + cZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( (cZ2World * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,distance); } @@ -1490,13 +1526,13 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, int16_t wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y); - wallHeightScreen = RCL_perspectiveScale((wallHeightWorld * + wallHeightScreen = RCL_perspectiveScaleVertical((wallHeightWorld * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,dist); int16_t RCL_normalizedWallHeight = wallHeightWorld != 0 ? ((RCL_UNITS_PER_SQUARE * wallHeightScreen) / wallHeightWorld) : 0; - heightOffset = RCL_perspectiveScale(_RCL_cameraHeightScreen,dist); + heightOffset = RCL_perspectiveScaleVertical(_RCL_cameraHeightScreen,dist); wallStart = _RCL_middleRow - wallHeightScreen + heightOffset + RCL_normalizedWallHeight; @@ -1562,7 +1598,7 @@ static inline void _RCL_precomputeFloorDistances(RCL_Camera camera, (camera.height * camera.resolution.y) / RCL_UNITS_PER_SQUARE; for (uint16_t i = startIndex; i < camera.resolution.y; ++i) - dest[i] = RCL_perspectiveScaleInverse(camHeightScreenSize, + dest[i] = RCL_perspectiveScaleVerticalInverse(camHeightScreenSize, RCL_absVal(i - _RCL_middleRow)); } @@ -1693,7 +1729,7 @@ RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height, result.position.y = camera.resolution.y / 2 - - (RCL_perspectiveScale(height - camera.height,result.depth) + (RCL_perspectiveScaleVertical(height - camera.height,result.depth) * camera.resolution.y) / RCL_UNITS_PER_SQUARE + camera.shear; @@ -1705,9 +1741,9 @@ RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees) return (degrees * RCL_UNITS_PER_SQUARE) / 360; } -RCL_Unit RCL_perspectiveScale(RCL_Unit originalSize, RCL_Unit distance) +RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance) { - RCL_profileCall(RCL_perspectiveScale); + RCL_profileCall(RCL_perspectiveScaleVertical); return distance != 0 ? (originalSize * RCL_UNITS_PER_SQUARE) / @@ -1715,7 +1751,7 @@ RCL_Unit RCL_perspectiveScale(RCL_Unit originalSize, RCL_Unit distance) : 0; } -RCL_Unit RCL_perspectiveScaleInverse(RCL_Unit originalSize, +RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize, RCL_Unit scaledSize) { return scaledSize != 0 ? @@ -1725,6 +1761,95 @@ RCL_Unit RCL_perspectiveScaleInverse(RCL_Unit originalSize, : RCL_INFINITY; } +RCL_Unit + RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance) +{ + return distance != 0 ? + (originalSize * RCL_UNITS_PER_SQUARE) / + ((RCL_HORIZONTAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE) + : 0; +} + +RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize, + RCL_Unit scaledSize) +{ + return scaledSize != 0 ? + (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) / + ((RCL_HORIZONTAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE) + : RCL_INFINITY; +} + +RCL_Unit RCL_castRay3D( + RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2, + RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc, + RCL_RayConstraints constraints) +{ + RCL_HitResult hits[constraints.maxHits]; + uint16_t numHits; + + RCL_Ray ray; + + ray.start = pos1; + + RCL_Unit distance; + + ray.direction.x = pos2.x - pos1.x; + ray.direction.y = pos2.y - pos1.y; + + distance = RCL_len(ray.direction); + + ray.direction = RCL_normalize(ray.direction); + + RCL_Unit heightDiff = height2 - height1; + + RCL_castRayMultiHit(ray,floorHeightFunc,0,hits,&numHits,constraints); + + RCL_Unit result = RCL_UNITS_PER_SQUARE; + + int16_t squareX = RCL_divRoundDown(pos1.x,RCL_UNITS_PER_SQUARE); + int16_t squareY = RCL_divRoundDown(pos1.y,RCL_UNITS_PER_SQUARE); + + RCL_Unit startHeight = floorHeightFunc(squareX,squareY); + + #define checkHits(comp,res) \ + { \ + RCL_Unit currentHeight = startHeight; \ + for (uint16_t i = 0; i < numHits; ++i) \ + { \ + if (hits[i].distance > distance) \ + break;\ + RCL_Unit h = hits[i].arrayValue; \ + if ((currentHeight comp h ? currentHeight : h) \ + comp (height1 + (hits[i].distance * heightDiff) / distance)) \ + { \ + res = (hits[i].distance * RCL_UNITS_PER_SQUARE) / distance; \ + break; \ + } \ + currentHeight = h; \ + } \ + } + + checkHits(>,result) + + if (ceilingHeightFunc != 0) + { + RCL_Unit result2 = RCL_UNITS_PER_SQUARE; + + startHeight = ceilingHeightFunc(squareX,squareY); + + RCL_castRayMultiHit(ray,ceilingHeightFunc,0,hits,&numHits,constraints); + + checkHits(<,result2) + + if (result2 < result) + result = result2; + } + + #undef checkHits + + return result; +} + void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset, RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force)