diff --git a/raycastlib.h b/raycastlib.h index 095b481..8325df0 100644 --- a/raycastlib.h +++ b/raycastlib.h @@ -26,7 +26,7 @@ author: Miloslav "drummyfish" Ciz license: CC0 1.0 - version: 0.1 + version: 0.8 */ #include @@ -40,11 +40,11 @@ RCL_UNITS_PER_SQUARE units in a square's length. This effectively serves the purpose of a fixed-point arithmetic. */ - #define RCL_INFINITY 5000000; + #define RCL_INFINITY 2000000000 #else #define RCL_UNITS_PER_SQUARE 32 typedef int16_t RCL_Unit; - #define RCL_INFINITY 32000; + #define RCL_INFINITY 30000 #define RCL_USE_DIST_APPROX 2 #endif @@ -56,6 +56,14 @@ #define RCL_COMPUTE_FLOOR_TEXCOORDS 0 #endif +#ifndef RCL_FLOOR_TEXCOORDS_HEIGHT +#define RCL_FLOOR_TEXCOORDS_HEIGHT 0 /** If RCL_COMPUTE_FLOOR_TEXCOORDS == 1, + this says for what height level the + texture coords will be computed for + (for simplicity/performance only one + level is allowed). */ +#endif + #ifndef RCL_USE_COS_LUT #define RCL_USE_COS_LUT 0 /**< type of look up table for cos function: 0: none (compute) @@ -162,7 +170,7 @@ printf(" start: ");\ RCL_logV2D(r.start);\ printf(" dir: ");\ - RCL_logV2D(r.direction);}\ + RCL_logV2D(r.direction);} #define RCL_logHitResult(h){\ printf("hit:\n");\ @@ -172,17 +180,30 @@ RCL_logV2D(h.position);\ printf(" dist: %d\n", h.distance);\ printf(" dir: %d\n", h.direction);\ - printf(" texcoord: %d\n", h.textureCoord);}\ + printf(" texcoord: %d\n", h.textureCoord);} #define RCL_logPixelInfo(p){\ printf("pixel:\n");\ printf(" position: ");\ RCL_logV2D(p.position);\ + printf(" texCoord: ");\ + RCL_logV2D(p.texCoords);\ printf(" depth: %d\n", p.depth);\ + printf(" height: %d\n", p.height);\ printf(" wall: %d\n", p.isWall);\ printf(" hit: ");\ RCL_logHitResult(p.hit);\ - }\ + } + +#define RCL_logCamera(c){\ + printf("camera:\n");\ + printf(" position: ");\ + RCL_logV2D(c.position);\ + printf(" height: %d\n",c.height);\ + printf(" direction: %d\n",c.direction);\ + printf(" shear: %d\n",c.shear);\ + printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\ + } /// Position in 2D space. typedef struct @@ -235,6 +256,7 @@ typedef struct int8_t isFloor; ///< Whether the pixel is floor or ceiling. int8_t isHorizon; ///< If the pixel belongs to horizon segment. RCL_Unit depth; ///< Corrected depth. + RCL_Unit height; ///< World height (mostly for floor). RCL_HitResult hit; ///< Corresponding ray hit. RCL_Vector2D texCoords; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1) texture coordinates. */ @@ -791,9 +813,7 @@ void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, { RCL_profileCall(RCL_castRayMultiHit); - RCL_Vector2D initialPos = ray.start; RCL_Vector2D currentPos = ray.start; - RCL_Vector2D currentSquare; currentSquare.x = RCL_divRoundDown(ray.start.x,RCL_UNITS_PER_SQUARE); @@ -883,7 +903,6 @@ void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, RCL_Unit diff = h.position.x - ray.start.x; h.position.y = ray.start.y + ((ray.direction.y * diff) / RCL_nonZero(ray.direction.x)); - h.textureCoord = h.position.y; h.distance = ((h.position.x - ray.start.x) * RCL_UNITS_PER_SQUARE) / @@ -903,7 +922,6 @@ void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, RCL_Unit diff = h.position.y - ray.start.y; h.position.x = ray.start.x + ((ray.direction.x * diff) / RCL_nonZero(ray.direction.y)); - h.textureCoord = h.position.x; h.distance = ((h.position.y - ray.start.y) * RCL_UNITS_PER_SQUARE) / @@ -914,8 +932,27 @@ void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, h.type = typeFunc(currentSquare.x,currentSquare.y); #if RCL_COMPUTE_WALL_TEXCOORDS == 1 + switch (h.direction) + { + case 0: h.textureCoord = + RCL_wrap(-1 * h.position.x,RCL_UNITS_PER_SQUARE); break; + + case 1: h.textureCoord = + RCL_wrap(h.position.y,RCL_UNITS_PER_SQUARE); break; + + case 2: h.textureCoord = + RCL_wrap(h.position.x,RCL_UNITS_PER_SQUARE); break; + + case 3: h.textureCoord = + RCL_wrap(-1 * h.position.y,RCL_UNITS_PER_SQUARE); break; + + default: h.textureCoord = 0; break; + } + if (_RCL_rollFunction != 0) h.doorRoll = _RCL_rollFunction(currentSquare.x,currentSquare.y); +#else + h.textureCoord = 0; #endif hitResults[*hitResultsLen] = h; @@ -1063,8 +1100,6 @@ static inline int16_t _RCL_drawHorizontal( RCL_Unit depthIncrement; RCL_Unit dx; RCL_Unit dy; - RCL_Unit pixPos; - RCL_Unit rayCameraCos; pixelInfo->isWall = 0; @@ -1085,25 +1120,22 @@ static inline int16_t _RCL_drawHorizontal( {\ dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\ dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\ - pixPos = yCurrent - _RCL_middleRow - 1;\ - rayCameraCos = RCL_vectorsAngleCos(\ - RCL_angleToDirection(_RCL_camera.direction),ray->direction);\ }\ for (int16_t i = yCurrent + increment;\ increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\ i += increment)\ {\ pixelInfo->position.y = i;\ - if (doDepth) /*constant condition - compiler should optimize it out*/\ + if (doDepth) /*constant condition - compiler should optimize it out*/\ pixelInfo->depth += depthIncrement;\ - if (doCoords)/*constant condition - compiler should optimize it out*/\ + if (doCoords) /*constant condition - compiler should optimize it out*/\ {\ - RCL_Unit d = _RCL_floorPixelDistances[pixPos];\ + RCL_Unit d = _RCL_floorPixelDistances[i];\ + RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\ pixelInfo->texCoords.x =\ - _RCL_camera.position.x + ((d * dx) / (pixelInfo->hit.distance));\ + _RCL_camera.position.x + ((d * dx) / d2);\ pixelInfo->texCoords.y =\ - _RCL_camera.position.y + ((d * dy) / (pixelInfo->hit.distance));\ - ++pixPos;\ + _RCL_camera.position.y + ((d * dy) / d2);\ }\ RCL_PIXEL_FUNCTION(pixelInfo);\ }\ @@ -1174,7 +1206,6 @@ static inline int16_t _RCL_drawWall( i += increment) { // more expensive texture coord computing - pixelInfo->position.y = i; #if RCL_COMPUTE_WALL_TEXCOORDS == 1 @@ -1210,6 +1241,22 @@ static inline int16_t _RCL_drawWall( return limit; } +/// Fills a RCL_HitResult struct with info for a hit at infinity. +static inline void _RCL_makeInfiniteHit(RCL_HitResult *hit, RCL_Ray *ray) +{ + hit->distance = RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE; + /* ^ horizon is at infinity, but we can't use too big infinity + (RCL_INFINITY) because it would overflow in the following mult. */ + hit->position.x = (ray->direction.x * hit->distance) / RCL_UNITS_PER_SQUARE; + hit->position.y = (ray->direction.y * hit->distance) / RCL_UNITS_PER_SQUARE; + + hit->direction = 0; + hit->textureCoord = 0; + hit->arrayValue = 0; + hit->doorRoll = 0; + hit->type = 0; +} + void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t x, RCL_Ray ray) { @@ -1217,22 +1264,23 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t RCL_Unit fPosY = _RCL_camera.resolution.y; RCL_Unit cPosY = -1; - // world coordinates + // world coordinates (relative to camera height though) RCL_Unit fZ1World = _RCL_startFloorHeight; RCL_Unit cZ1World = _RCL_startCeil_Height; RCL_PixelInfo p; p.position.x = x; + p.height = 0; p.texCoords.x = 0; p.texCoords.y = 0; // we'll be simulatenously drawing the floor and the ceiling now for (RCL_Unit j = 0; j <= hitCount; ++j) - { // ^ = add extra iteration for horizon plane + { // ^ = add extra iteration for horizon plane int8_t drawingHorizon = j == hitCount; RCL_HitResult hit; - RCL_Unit distance; + RCL_Unit distance = 1; RCL_Unit fWallHeight = 0, cWallHeight = 0; RCL_Unit fZ2World = 0, cZ2World = 0; @@ -1242,7 +1290,8 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t if (!drawingHorizon) { hit = hits[j]; - distance = hit.distance; + distance = RCL_nonZero(hit.distance); + p.hit = hit; fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y); fZ2World = fWallHeight - _RCL_camera.height; @@ -1269,6 +1318,7 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t { fZ1Screen = _RCL_middleRow; cZ1Screen = _RCL_middleRow + 1; + _RCL_makeInfiniteHit(&p.hit,&ray); } RCL_Unit limit; @@ -1278,6 +1328,7 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t // draw floor until wall p.isFloor = 1; + p.height = fZ1World + _RCL_camera.height; #if RCL_COMPUTE_FLOOR_DEPTH == 1 p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep; @@ -1286,8 +1337,10 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t #endif limit = _RCL_drawHorizontal(fPosY,fZ1Screen,cPosY + 1, - _RCL_camera.resolution.y,fZ1World,-1,RCL_COMPUTE_FLOOR_DEPTH,0,1,&ray,&p); + _RCL_camera.resolution.y,fZ1World,-1,RCL_COMPUTE_FLOOR_DEPTH, // ^ purposfully allow outside screen bounds + RCL_COMPUTE_FLOOR_TEXCOORDS && p.height == RCL_FLOOR_TEXCOORDS_HEIGHT, + 1,&ray,&p); if (fPosY > limit) fPosY = limit; @@ -1296,6 +1349,7 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t { // draw ceiling until wall p.isFloor = 0; + p.height = cZ1World + _RCL_camera.height; #if RCL_COMPUTE_CEILING_DEPTH == 1 p.depth = (cPosY - _RCL_cHorizontalDepthStart) * @@ -1315,8 +1369,8 @@ void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t p.isWall = 1; p.depth = distance; p.isFloor = 1; - p.hit = hit; p.texCoords.x = hit.textureCoord; + p.height = 0; // don't compute this, no use // draw floor wall @@ -1400,9 +1454,11 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, { // normal hit, check the door roll + RCL_Unit texCoordMod = hit.textureCoord % RCL_UNITS_PER_SQUARE; + int8_t unrolled = hit.doorRoll >= 0 ? - hit.doorRoll > hit.textureCoord : - hit.textureCoord > RCL_UNITS_PER_SQUARE + hit.doorRoll; + hit.doorRoll > texCoordMod : + texCoordMod > RCL_UNITS_PER_SQUARE + hit.doorRoll; if (unrolled) { @@ -1450,17 +1506,7 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, } else { - RCL_HitResult hit; - - hit.distance = RCL_HORIZON_DEPTH; - hit.direction = 0; - hit.textureCoord = x * 128; - hit.position.x = 0; - hit.position.y = 0; - hit.arrayValue = 0; - hit.type = 0; - - p.hit = hit; + _RCL_makeInfiniteHit(&p.hit,&ray); } // draw ceiling @@ -1469,6 +1515,7 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, p.isFloor = 0; p.isHorizon = 1; p.depth = 1; + p.height = RCL_UNITS_PER_SQUARE; y = _RCL_drawHorizontal(-1,wallStart,-1,_RCL_middleRow,_RCL_camera.height,1, RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p); @@ -1478,6 +1525,7 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, p.isWall = 1; p.isFloor = 1; p.depth = dist; + p.height = 0; #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1 p.hit.textureCoord -= p.hit.doorRoll; @@ -1490,7 +1538,7 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, -1,_RCL_camResYLimit,p.hit.arrayValue,1,&p); y = RCL_max(y,limit); // take max, in case no wall was drawn - y = RCL_max(y,_RCL_middleRow + 1); + y = RCL_max(y,wallStart); // draw floor @@ -1505,6 +1553,22 @@ void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, -1,&ray,&p); } +/** + Precomputes a distance from camera to the floor at each screen row into an + array (must be preallocated with sufficient (camera.resolution.y) length). +*/ +static inline void _RCL_precomputeFloorDistances(RCL_Camera camera, + RCL_Unit *dest, uint16_t startIndex) +{ + RCL_Unit camHeightScreenSize = + (camera.height * camera.resolution.y) / RCL_UNITS_PER_SQUARE; + + for (uint16_t i = startIndex +; i < camera.resolution.y; ++i) + dest[i] = RCL_perspectiveScaleInverse(camHeightScreenSize, + RCL_absVal(i - _RCL_middleRow)); +} + void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction, RCL_RayConstraints constraints) @@ -1514,7 +1578,7 @@ void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, _RCL_camera = cam; _RCL_camResYLimit = cam.resolution.y - 1; - int16_t halfResY = cam.resolution.y / 2; + uint16_t halfResY = cam.resolution.y / 2; _RCL_middleRow = halfResY + cam.shear; @@ -1534,6 +1598,12 @@ void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y; +#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1 + RCL_Unit floorPixelDistances[cam.resolution.y]; + _RCL_precomputeFloorDistances(cam,floorPixelDistances,0); + _RCL_floorPixelDistances = floorPixelDistances; // pass to column function +#endif + RCL_castRaysMultiHit(cam,_RCL_floorCeilFunction,typeFunction, _RCL_columnFunctionComplex,constraints); } @@ -1560,25 +1630,9 @@ void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2) #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1 - uint16_t halfResY = cam.resolution.y / 2; - - RCL_Unit floorPixelDistances[halfResY]; /* for each vertical floor pixel, - this will contain precomputed - distance to the camera */ - RCL_Unit camHeightScreenSize = -#ifdef RCL_RAYCAST_TINY - (cam.height -#else - (((cam.height >> 6) << 6) // prevent weird floor movement with rounding -#endif - * cam.resolution.y) / RCL_UNITS_PER_SQUARE; - - for (uint16_t i = 0; i < halfResY; ++i) // precompute the distances - floorPixelDistances[i] = - RCL_perspectiveScaleInverse(camHeightScreenSize,i); - - // pass to _RCL_columnFunctionSimple - _RCL_floorPixelDistances = floorPixelDistances; + RCL_Unit floorPixelDistances[cam.resolution.y]; + _RCL_precomputeFloorDistances(cam,floorPixelDistances,_RCL_middleRow); + _RCL_floorPixelDistances = floorPixelDistances; // pass to column function #endif RCL_castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc, @@ -1677,9 +1731,10 @@ RCL_Unit RCL_perspectiveScaleInverse(RCL_Unit originalSize, RCL_Unit scaledSize) { return scaledSize != 0 ? - (originalSize * RCL_UNITS_PER_SQUARE) / + (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) / + // ^ take the middle ((RCL_VERTICAL_FOV * 2 * scaledSize) / RCL_UNITS_PER_SQUARE) - : 0; + : RCL_INFINITY; } void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset, @@ -1837,6 +1892,7 @@ void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset, camera->height = RCL_clamp(camera->height, bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW, topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE); + #undef checkSquares } }