diff options
Diffstat (limited to 'src/raycastlib.cpp')
-rw-r--r-- | src/raycastlib.cpp | 1563 |
1 files changed, 1563 insertions, 0 deletions
diff --git a/src/raycastlib.cpp b/src/raycastlib.cpp new file mode 100644 index 0000000..07f1b87 --- /dev/null +++ b/src/raycastlib.cpp @@ -0,0 +1,1563 @@ +#include "raycastlib.h" + +//============================================================================= +// privates + +#define _RCL_UNUSED(what) (void)(what); + +#ifndef RCL_PIXEL_FUNCTION +#define RCL_PIXEL_FUNCTION pixelFunc +#endif +void pixelFunc(RCL_PixelInfo *p); + +// global helper variables, for precomputing stuff etc. +RCL_Camera _RCL_camera; +RCL_Unit _RCL_horizontalDepthStep = 0; +RCL_Unit _RCL_startFloorHeight = 0; +RCL_Unit _RCL_startCeil_Height = 0; +RCL_Unit _RCL_camResYLimit = 0; +RCL_Unit _RCL_middleRow = 0; +RCL_ArrayFunction _RCL_floorFunction = 0; +RCL_ArrayFunction _RCL_ceilFunction = 0; +RCL_Unit _RCL_fHorizontalDepthStart = 0; +RCL_Unit _RCL_cHorizontalDepthStart = 0; +int16_t _RCL_cameraHeightScreen = 0; +RCL_ArrayFunction _RCL_rollFunction = 0; // says door rolling +RCL_Unit *_RCL_floorPixelDistances = 0; +RCL_Unit _RCL_fovCorrectionFactors[2] = {0,0}; //correction for hor/vert fov + +RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax) +{ + if (value >= valueMin) + { + if (value <= valueMax) + return value; + else + return valueMax; + } + else + return valueMin; +} + +static inline RCL_Unit RCL_abs(RCL_Unit value) +{ + return value * (((value >= 0) << 1) - 1); +} + +/// Like mod, but behaves differently for negative values. +static inline RCL_Unit RCL_wrap(RCL_Unit value, RCL_Unit mod) +{ + RCL_Unit cmp = value < 0; + return cmp * mod + (value % mod) - cmp; +} + +/// Performs division, rounding down, NOT towards zero. +RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor) +{ + return value / divisor - ((value >= 0) ? 0 : 1); +} + +// Bhaskara's cosine approximation formula +#define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\ + (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\ + (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x))) + +#if RCL_USE_COS_LUT == 1 + + #ifdef RCL_RAYCAST_TINY + const RCL_Unit cosLUT[64] = + { + 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14 + }; + #else + const RCL_Unit cosLUT[64] = + { + 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100, + -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019, + -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297, + -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019 + }; + #endif + +#elif RCL_USE_COS_LUT == 2 +const RCL_Unit cosLUT[128] = +{ + 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724, + 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150, + -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791, + -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023, + -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791, + -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150, + -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724, + 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022 +}; +#endif + +RCL_Unit RCL_cos(RCL_Unit input) +{ + input = RCL_wrap(input,RCL_UNITS_PER_SQUARE); + +#if RCL_USE_COS_LUT == 1 + + #ifdef RCL_RAYCAST_TINY + return cosLUT[input]; + #else + return cosLUT[input / 16]; + #endif + +#elif RCL_USE_COS_LUT == 2 + return cosLUT[input / 8]; +#else + if (input < RCL_UNITS_PER_SQUARE / 4) + return trigHelper(input); + else if (input < RCL_UNITS_PER_SQUARE / 2) + return -1 * trigHelper(RCL_UNITS_PER_SQUARE / 2 - input); + else if (input < 3 * RCL_UNITS_PER_SQUARE / 4) + return -1 * trigHelper(input - RCL_UNITS_PER_SQUARE / 2); + else + return trigHelper(RCL_UNITS_PER_SQUARE - input); +#endif +} + +#undef trigHelper + +RCL_Unit RCL_sin(RCL_Unit input) +{ + return RCL_cos(input - RCL_UNITS_PER_SQUARE / 4); +} + +RCL_Unit RCL_tan(RCL_Unit input) +{ + return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_nonZero(RCL_cos(input) +); + + return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_nonZero(RCL_cos(input)); +} + +RCL_Unit RCL_ctg(RCL_Unit input) +{ + return (RCL_cos(input) * RCL_UNITS_PER_SQUARE) / RCL_sin(input); +} + +RCL_Vector2D RCL_angleToDirection(RCL_Unit angle) +{ + RCL_Vector2D result; + + result.x = RCL_cos(angle); + result.y = -1 * RCL_sin(angle); + + return result; +} + +uint16_t RCL_sqrt(RCL_Unit value) +{ +#ifdef RCL_RAYCAST_TINY + uint16_t result = 0; + uint16_t a = value; + uint16_t b = 1u << 14; +#else + uint32_t result = 0; + uint32_t a = value; + uint32_t b = 1u << 30; +#endif + + while (b > a) + b >>= 2; + + while (b != 0) + { + if (a >= result + b) + { + a -= result + b; + result = result + 2 * b; + } + + b >>= 2; + result >>= 1; + } + + return result; +} + +RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2) +{ + RCL_Unit dx = p2.x - p1.x; + RCL_Unit dy = p2.y - p1.y; + +#if RCL_USE_DIST_APPROX == 2 + // octagonal approximation + + dx = RCL_abs(dx); + dy = RCL_abs(dy); + + return dy > dx ? dx / 2 + dy : dy / 2 + dx; +#elif RCL_USE_DIST_APPROX == 1 + // more accurate approximation + + RCL_Unit a, b, result; + + dx = ((dx < 0) * 2 - 1) * dx; + dy = ((dy < 0) * 2 - 1) * dy; + + if (dx < dy) + { + a = dy; + b = dx; + } + else + { + a = dx; + b = dy; + } + + result = a + (44 * b) / 102; + + if (a < (b << 4)) + result -= (5 * a) / 128; + + return result; +#else + dx = dx * dx; + dy = dy * dy; + + return RCL_sqrt((RCL_Unit) (dx + dy)); +#endif +} + +RCL_Unit RCL_len(RCL_Vector2D v) +{ + RCL_Vector2D zero; + zero.x = 0; + zero.y = 0; + + return RCL_dist(zero,v); +} + +static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point, RCL_Ray ray) +{ + RCL_Unit dX = point.x - ray.start.x; + RCL_Unit dY = point.y - ray.start.y; + return (ray.direction.x * dY - ray.direction.y * dX) > 0; + // ^ Z component of cross-product +} + +void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, + RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults, + uint16_t *hitResultsLen, RCL_RayConstraints constraints) +{ + RCL_Vector2D currentPos = ray.start; + RCL_Vector2D currentSquare; + + currentSquare.x = RCL_divRoundDown(ray.start.x,RCL_UNITS_PER_SQUARE); + currentSquare.y = RCL_divRoundDown(ray.start.y,RCL_UNITS_PER_SQUARE); + + *hitResultsLen = 0; + + RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y); + + // DDA variables + RCL_Vector2D nextSideDist; // dist. from start to the next side in given axis + RCL_Vector2D delta; + RCL_Vector2D step; // -1 or 1 for each axis + int8_t stepHorizontal = 0; // whether the last step was hor. or vert. + + nextSideDist.x = 0; + nextSideDist.y = 0; + + RCL_Unit dirVecLengthNorm = RCL_len(ray.direction) * RCL_UNITS_PER_SQUARE; + + delta.x = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.x)); + delta.y = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.y)); + + // init DDA + + if (ray.direction.x < 0) + { + step.x = -1; + nextSideDist.x = (RCL_wrap(ray.start.x,RCL_UNITS_PER_SQUARE) * delta.x) / + RCL_UNITS_PER_SQUARE; + } + else + { + step.x = 1; + nextSideDist.x = + ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.x,RCL_UNITS_PER_SQUARE)) * + delta.x) / RCL_UNITS_PER_SQUARE; + } + + if (ray.direction.y < 0) + { + step.y = -1; + nextSideDist.y = (RCL_wrap(ray.start.y,RCL_UNITS_PER_SQUARE) * delta.y) / + RCL_UNITS_PER_SQUARE; + } + else + { + step.y = 1; + nextSideDist.y = + ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.y,RCL_UNITS_PER_SQUARE)) * + delta.y) / RCL_UNITS_PER_SQUARE; + } + + // DDA loop + + #define RECIP_SCALE 65536 + + RCL_Unit rayDirXRecip = RECIP_SCALE / RCL_nonZero(ray.direction.x); + RCL_Unit rayDirYRecip = RECIP_SCALE / RCL_nonZero(ray.direction.y); + // ^ we precompute reciprocals to avoid divisions in the loop + + for (uint16_t i = 0; i < constraints.maxSteps; ++i) + { + RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y); + + if (RCL_unlikely(currentType != squareType)) + { + // collision + + RCL_HitResult h; + + h.arrayValue = currentType; + h.doorRoll = 0; + h.position = currentPos; + h.square = currentSquare; + + if (stepHorizontal) + { + h.position.x = currentSquare.x * RCL_UNITS_PER_SQUARE; + h.direction = 3; + + if (step.x == -1) + { + h.direction = 1; + h.position.x += RCL_UNITS_PER_SQUARE; + } + + RCL_Unit diff = h.position.x - ray.start.x; + + h.position.y = // avoid division by multiplying with reciprocal + ray.start.y + (ray.direction.y * diff * rayDirXRecip) / RECIP_SCALE; + +#if RCL_RECTILINEAR + /* Here we compute the fish eye corrected distance (perpendicular to + the projection plane) as the Euclidean distance (of hit from camera + position) divided by the length of the ray direction vector. This can + be computed without actually computing Euclidean distances as a + hypothenuse A (distance) divided by hypothenuse B (length) is equal to + leg A (distance along principal axis) divided by leg B (length along + the same principal axis). */ + +#define CORRECT(dir1,dir2)\ + RCL_Unit tmp = diff / 4; /* 4 to prevent overflow */ \ + h.distance = ((tmp / 8) != 0) ? /* prevent a bug with small dists */ \ + ((tmp * RCL_UNITS_PER_SQUARE * rayDir ## dir1 ## Recip) / (RECIP_SCALE / 4)):\ + RCL_abs(h.position.dir2 - ray.start.dir2); + + CORRECT(X,y) + +#endif // RCL_RECTILINEAR + } + else + { + h.position.y = currentSquare.y * RCL_UNITS_PER_SQUARE; + h.direction = 2; + + if (step.y == -1) + { + h.direction = 0; + h.position.y += RCL_UNITS_PER_SQUARE; + } + + RCL_Unit diff = h.position.y - ray.start.y; + + h.position.x = + ray.start.x + (ray.direction.x * diff * rayDirYRecip) / RECIP_SCALE; + +#if RCL_RECTILINEAR + + CORRECT(Y,x) // same as above but for different axis + +#undef CORRECT + +#endif // RCL_RECTILINEAR + } + +#if !RCL_RECTILINEAR + h.distance = RCL_dist(h.position,ray.start); +#endif + if (typeFunc != 0) + 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); + + if (h.direction == 0 || h.direction == 1) + h.doorRoll *= -1; + } + +#else + h.textureCoord = 0; +#endif + + hitResults[*hitResultsLen] = h; + + *hitResultsLen += 1; + + squareType = currentType; + + if (*hitResultsLen >= constraints.maxHits) + break; + } + + // DDA step + + if (nextSideDist.x < nextSideDist.y) + { + nextSideDist.x += delta.x; + currentSquare.x += step.x; + stepHorizontal = 1; + } + else + { + nextSideDist.y += delta.y; + currentSquare.y += step.y; + stepHorizontal = 0; + } + } +} + +RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc) +{ + RCL_HitResult result; + uint16_t len; + RCL_RayConstraints c; + + c.maxSteps = 1000; + c.maxHits = 1; + + RCL_castRayMultiHit(ray,arrayFunc,0,&result,&len,c); + + if (len == 0) + result.distance = -1; + + return result; +} + +void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc, + RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc, + RCL_RayConstraints constraints) +{ + RCL_Vector2D dir1 = + RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF); + + 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_cos(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; + + RCL_HitResult hits[constraints.maxHits]; + uint16_t hitCount; + + RCL_Ray r; + r.start = cam.position; + + RCL_Unit currentDX = 0; + RCL_Unit currentDY = 0; + + for (int16_t i = 0; i < cam.resolution.x; ++i) + { + /* Here by linearly interpolating the direction vector its length changes, + which in result achieves correcting the fish eye effect (computing + perpendicular distance). */ + + r.direction.x = dir1.x + currentDX / cam.resolution.x; + r.direction.y = dir1.y + currentDY / cam.resolution.x; + + RCL_castRayMultiHit(r,arrayFunc,typeFunction,hits,&hitCount,constraints); + + columnFunc(hits,hitCount,i,r); + + currentDX += dX; + currentDY += dY; + } +} + +/** + Helper function that determines intersection with both ceiling and floor. +*/ +RCL_Unit _RCL_floorCeilFunction(int16_t x, int16_t y) +{ + RCL_Unit f = _RCL_floorFunction(x,y); + + if (_RCL_ceilFunction == 0) + return f; + + RCL_Unit c = _RCL_ceilFunction(x,y); + +#ifndef RCL_RAYCAST_TINY + return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff); +#else + return ((f & 0x00ff) << 8) | (c & 0x00ff); +#endif +} + +RCL_Unit _floorHeightNotZeroFunction(int16_t x, int16_t y) +{ + return _RCL_floorFunction(x,y) == 0 ? 0 : + RCL_nonZero((x & 0x00FF) | ((y & 0x00FF) << 8)); + // ^ this makes collisions between all squares - needed for rolling doors +} + +RCL_Unit RCL_adjustDistance(RCL_Unit distance, RCL_Camera *camera, + RCL_Ray *ray) +{ + /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could + possibly be computed more efficiently by not computing Euclidean + distance at all, but rather compute the distance of the collision + point from the projection plane (line). */ + + RCL_Unit result = + (distance * + RCL_vectorsAngleCos(RCL_angleToDirection(camera->direction), + ray->direction)) / RCL_UNITS_PER_SQUARE; + + return RCL_nonZero(result); + // ^ prevent division by zero +} + +/// Helper for drawing floor or ceiling. Returns the last drawn pixel position. +static inline int16_t _RCL_drawHorizontalColumn( + RCL_Unit yCurrent, + RCL_Unit yTo, + RCL_Unit limit1, // TODO: int16_t? + RCL_Unit limit2, + RCL_Unit verticalOffset, + int16_t increment, + int8_t computeDepth, + int8_t computeCoords, + int16_t depthIncrementMultiplier, + RCL_Ray *ray, + RCL_PixelInfo *pixelInfo +) +{ + _RCL_UNUSED(ray); + + RCL_Unit depthIncrement; + RCL_Unit dx; + RCL_Unit dy; + + pixelInfo->isWall = 0; + + int16_t limit = RCL_clamp(yTo,limit1,limit2); + + RCL_Unit depth = 0; /* TODO: this is for clamping depth to 0 so that we don't + have negative depths, but we should do it more + elegantly and efficiently */ + + _RCL_UNUSED(depth); + + /* for performance reasons have different version of the critical loop + to be able to branch early */ + #define loop(doDepth,doCoords)\ + {\ + if (doDepth) /*constant condition - compiler should optimize it out*/\ + {\ + depth = pixelInfo->depth + RCL_abs(verticalOffset) *\ + RCL_VERTICAL_DEPTH_MULTIPLY;\ + depthIncrement = depthIncrementMultiplier *\ + _RCL_horizontalDepthStep;\ + }\ + if (doCoords) /*constant condition - compiler should optimize it out*/\ + {\ + dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\ + dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\ + }\ + 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*/\ + {\ + depth += depthIncrement;\ + pixelInfo->depth = RCL_zeroClamp(depth); \ + /* ^ int comparison is fast, it is not braching! (= test instr.) */\ + }\ + if (doCoords) /*constant condition - compiler should optimize it out*/\ + {\ + RCL_Unit d = _RCL_floorPixelDistances[i];\ + RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\ + pixelInfo->texCoords.x =\ + _RCL_camera.position.x + ((d * dx) / d2);\ + pixelInfo->texCoords.y =\ + _RCL_camera.position.y + ((d * dy) / d2);\ + }\ + RCL_PIXEL_FUNCTION(pixelInfo);\ + }\ + } + + if (computeDepth) // branch early + { + if (!computeCoords) + loop(1,0) + else + loop(1,1) + } + else + { + if (!computeCoords) + loop(0,0) + else + loop(1,1) + } + + #undef loop + + return limit; +} + +/// Helper for drawing walls. Returns the last drawn pixel position. +static inline int16_t _RCL_drawWall( + RCL_Unit yCurrent, + RCL_Unit yFrom, + RCL_Unit yTo, + RCL_Unit limit1, // TODO: int16_t? + RCL_Unit limit2, + RCL_Unit height, + int16_t increment, + RCL_PixelInfo *pixelInfo + ) +{ + _RCL_UNUSED(height) + + height = RCL_abs(height); + + pixelInfo->isWall = 1; + + RCL_Unit limit = RCL_clamp(yTo,limit1,limit2); + + RCL_Unit wallLength = RCL_nonZero(RCL_abs(yTo - yFrom - 1)); + + RCL_Unit wallPosition = RCL_abs(yFrom - yCurrent) - increment; + + RCL_Unit heightScaled = height * RCL_TEXTURE_INTERPOLATION_SCALE; + _RCL_UNUSED(heightScaled); + + RCL_Unit coordStepScaled = RCL_COMPUTE_WALL_TEXCOORDS ? +#if RCL_TEXTURE_VERTICAL_STRETCH == 1 + ((RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE) / wallLength) +#else + (heightScaled / wallLength) +#endif + : 0; + + pixelInfo->texCoords.y = RCL_COMPUTE_WALL_TEXCOORDS ? + (wallPosition * coordStepScaled) : 0; + + if (increment < 0) + { + coordStepScaled *= -1; + pixelInfo->texCoords.y = +#if RCL_TEXTURE_VERTICAL_STRETCH == 1 + (RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE) + - pixelInfo->texCoords.y; +#else + heightScaled - pixelInfo->texCoords.y; +#endif + } + else + { + // with floor wall, don't start under 0 + pixelInfo->texCoords.y = RCL_zeroClamp(pixelInfo->texCoords.y); + } + + RCL_Unit textureCoordScaled = pixelInfo->texCoords.y; + + for (RCL_Unit i = yCurrent + increment; + increment == -1 ? i >= limit : i <= limit; // TODO: is efficient? + i += increment) + { + pixelInfo->position.y = i; + +#if RCL_COMPUTE_WALL_TEXCOORDS == 1 + pixelInfo->texCoords.y = + textureCoordScaled / RCL_TEXTURE_INTERPOLATION_SCALE; + + textureCoordScaled += coordStepScaled; +#endif + + RCL_PIXEL_FUNCTION(pixelInfo); + } + + 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) +{ + // last written Y position, can never go backwards + RCL_Unit fPosY = _RCL_camera.resolution.y; + RCL_Unit cPosY = -1; + + // 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.wallHeight = 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 + int8_t drawingHorizon = j == hitCount; + + RCL_HitResult hit; + RCL_Unit distance = 1; + + RCL_Unit fWallHeight = 0, cWallHeight = 0; + RCL_Unit fZ2World = 0, cZ2World = 0; + RCL_Unit fZ1Screen = 0, cZ1Screen = 0; + RCL_Unit fZ2Screen = 0, cZ2Screen = 0; + + if (!drawingHorizon) + { + hit = hits[j]; + distance = RCL_nonZero(hit.distance); + p.hit = hit; + + fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y); + fZ2World = fWallHeight - _RCL_camera.height; + fZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( + (fZ1World * _RCL_camera.resolution.y) / + RCL_UNITS_PER_SQUARE,distance); + fZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( + (fZ2World * _RCL_camera.resolution.y) / + RCL_UNITS_PER_SQUARE,distance); + + if (_RCL_ceilFunction != 0) + { + cWallHeight = _RCL_ceilFunction(hit.square.x,hit.square.y); + cZ2World = cWallHeight - _RCL_camera.height; + cZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( + (cZ1World * _RCL_camera.resolution.y) / + RCL_UNITS_PER_SQUARE,distance); + cZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical( + (cZ2World * _RCL_camera.resolution.y) / + RCL_UNITS_PER_SQUARE,distance); + } + } + else + { + fZ1Screen = _RCL_middleRow; + cZ1Screen = _RCL_middleRow + 1; + _RCL_makeInfiniteHit(&p.hit,&ray); + } + + RCL_Unit limit; + + p.isWall = 0; + p.isHorizon = drawingHorizon; + + // draw floor until wall + p.isFloor = 1; + p.height = fZ1World + _RCL_camera.height; + p.wallHeight = 0; + +#if RCL_COMPUTE_FLOOR_DEPTH == 1 + p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep; +#else + p.depth = 0; +#endif + + limit = _RCL_drawHorizontalColumn(fPosY,fZ1Screen,cPosY + 1, + _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; + + if (_RCL_ceilFunction != 0 || drawingHorizon) + { + // draw ceiling until wall + p.isFloor = 0; + p.height = cZ1World + _RCL_camera.height; + +#if RCL_COMPUTE_CEILING_DEPTH == 1 + p.depth = (cPosY - _RCL_cHorizontalDepthStart) * + _RCL_horizontalDepthStep; +#endif + + limit = _RCL_drawHorizontalColumn(cPosY,cZ1Screen, + -1,fPosY - 1,cZ1World,1,RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p); + // ^ purposfully allow outside screen bounds here + + if (cPosY < limit) + cPosY = limit; + } + + if (!drawingHorizon) // don't draw walls for horizon plane + { + p.isWall = 1; + p.depth = distance; + p.isFloor = 1; + p.texCoords.x = hit.textureCoord; + p.height = fZ1World + _RCL_camera.height; + p.wallHeight = fWallHeight; + + // draw floor wall + + if (fPosY > 0) // still pixels left? + { + p.isFloor = 1; + + limit = _RCL_drawWall(fPosY,fZ1Screen,fZ2Screen,cPosY + 1, + _RCL_camera.resolution.y, + // ^ purposfully allow outside screen bounds here +#if RCL_TEXTURE_VERTICAL_STRETCH == 1 + RCL_UNITS_PER_SQUARE +#else + fZ2World - fZ1World +#endif + ,-1,&p); + + + if (fPosY > limit) + fPosY = limit; + + fZ1World = fZ2World; // for the next iteration + } // ^ purposfully allow outside screen bounds here + + // draw ceiling wall + + if (_RCL_ceilFunction != 0 && cPosY < _RCL_camResYLimit) // pixels left? + { + p.isFloor = 0; + p.height = cZ1World + _RCL_camera.height; + p.wallHeight = cWallHeight; + + limit = _RCL_drawWall(cPosY,cZ1Screen,cZ2Screen, + -1,fPosY - 1, + // ^ puposfully allow outside screen bounds here +#if RCL_TEXTURE_VERTICAL_STRETCH == 1 + RCL_UNITS_PER_SQUARE +#else + cZ1World - cZ2World +#endif + ,1,&p); + + if (cPosY < limit) + cPosY = limit; + + cZ1World = cZ2World; // for the next iteration + } // ^ puposfully allow outside screen bounds here + } + } +} + +void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, + uint16_t x, RCL_Ray ray) +{ + RCL_Unit y = 0; + RCL_Unit wallHeightScreen = 0; + RCL_Unit wallStart = _RCL_middleRow; + + RCL_Unit dist = 1; + + RCL_PixelInfo p; + p.position.x = x; + p.wallHeight = RCL_UNITS_PER_SQUARE; + + if (hitCount > 0) + { + RCL_HitResult hit = hits[0]; + + uint8_t goOn = 1; + + if (_RCL_rollFunction != 0 && RCL_COMPUTE_WALL_TEXCOORDS == 1) + { + if (hit.arrayValue == 0) + { + // standing inside door square, looking out => move to the next hit + + if (hitCount > 1) + hit = hits[1]; + else + goOn = 0; + } + else + { + // normal hit, check the door roll + + RCL_Unit texCoordMod = hit.textureCoord % RCL_UNITS_PER_SQUARE; + + int8_t unrolled = hit.doorRoll >= 0 ? + (hit.doorRoll > texCoordMod) : + (texCoordMod > RCL_UNITS_PER_SQUARE + hit.doorRoll); + + if (unrolled) + { + goOn = 0; + + if (hitCount > 1) /* should probably always be true (hit on square + exit) */ + { + if (hit.direction % 2 != hits[1].direction % 2) + { + // hit on the inner side + hit = hits[1]; + goOn = 1; + } + else if (hitCount > 2) + { + // hit on the opposite side + hit = hits[2]; + goOn = 1; + } + } + } + } + } + + p.hit = hit; + + if (goOn) + { + dist = hit.distance; + + RCL_Unit wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y); + + if (wallHeightWorld < 0) + { + /* We can't just do wallHeightWorld = max(0,wallHeightWorld) because + we would be processing an actual hit with height 0, which shouldn't + ever happen, so we assign some arbitrary height. */ + + wallHeightWorld = RCL_UNITS_PER_SQUARE; + } + + RCL_Unit worldPointTop = wallHeightWorld - _RCL_camera.height; + RCL_Unit worldPointBottom = -1 * _RCL_camera.height; + + wallStart = _RCL_middleRow - + (RCL_perspectiveScaleVertical(worldPointTop,dist) + * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE; + + int16_t wallEnd = _RCL_middleRow - + (RCL_perspectiveScaleVertical(worldPointBottom,dist) + * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE; + + wallHeightScreen = wallEnd - wallStart; + + if (wallHeightScreen <= 0) // can happen because of rounding errors + wallHeightScreen = 1; + } + } + else + { + _RCL_makeInfiniteHit(&p.hit,&ray); + } + + // draw ceiling + + p.isWall = 0; + p.isFloor = 0; + p.isHorizon = 1; + p.depth = 1; + p.height = RCL_UNITS_PER_SQUARE; + + y = _RCL_drawHorizontalColumn(-1,wallStart,-1,_RCL_middleRow,_RCL_camera.height,1, + RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p); + + // draw wall + + 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; +#endif + + p.texCoords.x = p.hit.textureCoord; + p.texCoords.y = 0; + + RCL_Unit limit = _RCL_drawWall(y,wallStart,wallStart + wallHeightScreen - 1, + -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,wallStart); + + // draw floor + + p.isWall = 0; + +#if RCL_COMPUTE_FLOOR_DEPTH == 1 + p.depth = (_RCL_camera.resolution.y - y) * _RCL_horizontalDepthStep + 1; +#endif + + _RCL_drawHorizontalColumn(y,_RCL_camResYLimit,-1,_RCL_camResYLimit, + _RCL_camera.height,1,RCL_COMPUTE_FLOOR_DEPTH,RCL_COMPUTE_FLOOR_TEXCOORDS, + -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_perspectiveScaleVerticalInverse(camHeightScreenSize, + RCL_abs(i - _RCL_middleRow)); +} + +void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, + RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction, + RCL_RayConstraints constraints) +{ + _RCL_floorFunction = floorHeightFunc; + _RCL_ceilFunction = ceilingHeightFunc; + _RCL_camera = cam; + _RCL_camResYLimit = cam.resolution.y - 1; + + uint16_t halfResY = cam.resolution.y / 2; + + _RCL_middleRow = halfResY + cam.shear; + + _RCL_fHorizontalDepthStart = _RCL_middleRow + halfResY; + _RCL_cHorizontalDepthStart = _RCL_middleRow - halfResY; + + _RCL_startFloorHeight = floorHeightFunc( + RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE), + RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height; + + _RCL_startCeil_Height = + ceilingHeightFunc != 0 ? + ceilingHeightFunc( + RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE), + RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height + : RCL_INFINITY; + + _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); +} + +void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, + RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc, + RCL_RayConstraints constraints) +{ + _RCL_floorFunction = floorHeightFunc; + _RCL_camera = cam; + _RCL_camResYLimit = cam.resolution.y - 1; + _RCL_middleRow = cam.resolution.y / 2; + _RCL_rollFunction = rollFunc; + + _RCL_cameraHeightScreen = + (_RCL_camera.resolution.y * (_RCL_camera.height - RCL_UNITS_PER_SQUARE)) / + RCL_UNITS_PER_SQUARE; + + _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y; + + constraints.maxHits = + _RCL_rollFunction == 0 ? + 1 : // no door => 1 hit is enough + 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2) + +#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1 + 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, + _RCL_columnFunctionSimple, constraints); + +#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1 + _RCL_floorPixelDistances = 0; +#endif +} + +RCL_Vector2D RCL_normalize(RCL_Vector2D v) +{ + RCL_Vector2D result; + RCL_Unit l = RCL_len(v); + l = RCL_nonZero(l); + + result.x = (v.x * RCL_UNITS_PER_SQUARE) / l; + result.y = (v.y * RCL_UNITS_PER_SQUARE) / l; + + return result; +} + +RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2) +{ + v1 = RCL_normalize(v1); + v2 = RCL_normalize(v2); + + return (v1.x * v2.x + v1.y * v2.y) / RCL_UNITS_PER_SQUARE; +} + + +RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height, + RCL_Camera camera) +{ + RCL_PixelInfo result; + + RCL_Vector2D toPoint; + + toPoint.x = worldPosition.x - camera.position.x; + toPoint.y = worldPosition.y - camera.position.y; + + RCL_Unit middleColumn = camera.resolution.x / 2; + + // rotate the point to camera space (y left/right, x forw/backw) + + RCL_Unit cos = RCL_cos(camera.direction); + RCL_Unit sin = RCL_sin(camera.direction); + + RCL_Unit tmp = toPoint.x; + + toPoint.x = (toPoint.x * cos - toPoint.y * sin) / RCL_UNITS_PER_SQUARE; + toPoint.y = (tmp * sin + toPoint.y * cos) / RCL_UNITS_PER_SQUARE; + + result.depth = toPoint.x; + + result.position.x = middleColumn - + (RCL_perspectiveScaleHorizontal(toPoint.y,result.depth) * middleColumn) / + RCL_UNITS_PER_SQUARE; + + result.position.y = + (RCL_perspectiveScaleVertical(height - camera.height,result.depth) + * camera.resolution.y) / RCL_UNITS_PER_SQUARE; + + result.position.y = camera.resolution.y / 2 - result.position.y + camera.shear; + + return result; +} + +RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees) +{ + return (degrees * RCL_UNITS_PER_SQUARE) / 360; +} + +/** + Ugly temporary hack to solve mapping to screen. This function computes + (approximately, usin a table) a divisor needed for FOV correction. +*/ +RCL_Unit _RCL_fovCorrectionFactor(RCL_Unit fov) +{ + uint16_t table[9] = + {1,208,408,692,1024,1540,2304,5376,30000}; + + fov = RCL_min(RCL_UNITS_PER_SQUARE / 2 - 1,fov); + + uint8_t index = fov / 64; + uint32_t t = ((fov - index * 64) * RCL_UNITS_PER_SQUARE) / 64; + uint32_t v1 = table[index]; + uint32_t v2 = table[index + 1]; + + return v1 + ((v2 - v1) * t) / RCL_UNITS_PER_SQUARE; +} + +RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance) +{ + if (_RCL_fovCorrectionFactors[1] == 0) + _RCL_fovCorrectionFactors[1] = _RCL_fovCorrectionFactor(RCL_VERTICAL_FOV); + + return distance != 0 ? ((originalSize * RCL_UNITS_PER_SQUARE) / + RCL_nonZero((_RCL_fovCorrectionFactors[1] * distance) / RCL_UNITS_PER_SQUARE) + ) : 0; +} + +RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize, + RCL_Unit scaledSize) +{ + if (_RCL_fovCorrectionFactors[1] == 0) + _RCL_fovCorrectionFactors[1] = _RCL_fovCorrectionFactor(RCL_VERTICAL_FOV); + + return scaledSize != 0 ? + + ((originalSize * RCL_UNITS_PER_SQUARE) / + RCL_nonZero((_RCL_fovCorrectionFactors[1] * scaledSize) + / RCL_UNITS_PER_SQUARE)) : RCL_INFINITY; +} + +RCL_Unit + RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance) +{ + if (_RCL_fovCorrectionFactors[0] == 0) + _RCL_fovCorrectionFactors[0] = _RCL_fovCorrectionFactor(RCL_HORIZONTAL_FOV); + + return distance != 0 ? + ((originalSize * RCL_UNITS_PER_SQUARE) / + RCL_nonZero((_RCL_fovCorrectionFactors[0] * distance) / RCL_UNITS_PER_SQUARE) + ) : 0; +} + +RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize, + RCL_Unit scaledSize) +{ + // TODO: probably doesn't work + + 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) +{ + int8_t movesInPlane = planeOffset.x != 0 || planeOffset.y != 0; + + if (movesInPlane || force) + { + int16_t xSquareNew, ySquareNew; + + RCL_Vector2D corner; // BBox corner in the movement direction + RCL_Vector2D cornerNew; + + int16_t xDir = planeOffset.x > 0 ? 1 : -1; + int16_t yDir = planeOffset.y > 0 ? 1 : -1; + + corner.x = camera->position.x + xDir * RCL_CAMERA_COLL_RADIUS; + corner.y = camera->position.y + yDir * RCL_CAMERA_COLL_RADIUS; + + int16_t xSquare = RCL_divRoundDown(corner.x,RCL_UNITS_PER_SQUARE); + int16_t ySquare = RCL_divRoundDown(corner.y,RCL_UNITS_PER_SQUARE); + + cornerNew.x = corner.x + planeOffset.x; + cornerNew.y = corner.y + planeOffset.y; + + xSquareNew = RCL_divRoundDown(cornerNew.x,RCL_UNITS_PER_SQUARE); + ySquareNew = RCL_divRoundDown(cornerNew.y,RCL_UNITS_PER_SQUARE); + + RCL_Unit bottomLimit = -1 * RCL_INFINITY; + RCL_Unit topLimit = RCL_INFINITY; + + RCL_Unit currCeilHeight = RCL_INFINITY; + + if (computeHeight) + { + bottomLimit = camera->height - RCL_CAMERA_COLL_HEIGHT_BELOW + + RCL_CAMERA_COLL_STEP_HEIGHT; + + topLimit = camera->height + RCL_CAMERA_COLL_HEIGHT_ABOVE; + + if (ceilingHeightFunc != 0) + currCeilHeight = ceilingHeightFunc(xSquare,ySquare); + } + + // checks a single square for collision against the camera + #define collCheck(dir,s1,s2)\ + if (computeHeight)\ + {\ + RCL_Unit height = floorHeightFunc(s1,s2);\ + if (height > bottomLimit || \ + currCeilHeight - height < \ + RCL_CAMERA_COLL_HEIGHT_BELOW + RCL_CAMERA_COLL_HEIGHT_ABOVE)\ + dir##Collides = 1;\ + else if (ceilingHeightFunc != 0)\ + {\ + RCL_Unit height2 = ceilingHeightFunc(s1,s2);\ + if ((height2 < topLimit) || ((height2 - height) < \ + (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))\ + dir##Collides = 1;\ + }\ + }\ + else\ + dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT; + + // check collision against non-diagonal square + #define collCheckOrtho(dir,dir2,s1,s2,x)\ + if (dir##SquareNew != dir##Square)\ + {\ + collCheck(dir,s1,s2)\ + }\ + if (!dir##Collides)\ + { /* now also check for coll on the neighbouring square */ \ + int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\ + RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\ + if (dir2##Square2 != dir2##Square)\ + {\ + if (x)\ + collCheck(dir,dir##SquareNew,dir2##Square2)\ + else\ + collCheck(dir,dir2##Square2,dir##SquareNew)\ + }\ + } + + int8_t xCollides = 0; + collCheckOrtho(x,y,xSquareNew,ySquare,1) + + int8_t yCollides = 0; + collCheckOrtho(y,x,xSquare,ySquareNew,0) + + if (xCollides || yCollides) + { + if (movesInPlane) + { + #define collHandle(dir)\ + if (dir##Collides)\ + cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\ + RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\ + dir##Dir;\ + + collHandle(x) + collHandle(y) + + #undef collHandle + } + else + { + /* Player collides without moving in the plane; this can happen e.g. on + elevators due to vertical only movement. This code can get executed + when force == 1. */ + + RCL_Vector2D squarePos; + RCL_Vector2D newPos; + + squarePos.x = xSquare * RCL_UNITS_PER_SQUARE; + squarePos.y = ySquare * RCL_UNITS_PER_SQUARE; + + newPos.x = + RCL_max(squarePos.x + RCL_CAMERA_COLL_RADIUS + 1, + RCL_min(squarePos.x + RCL_UNITS_PER_SQUARE - RCL_CAMERA_COLL_RADIUS - 1, + camera->position.x)); + + newPos.y = + RCL_max(squarePos.y + RCL_CAMERA_COLL_RADIUS + 1, + RCL_min(squarePos.y + RCL_UNITS_PER_SQUARE - RCL_CAMERA_COLL_RADIUS - 1, + camera->position.y)); + + cornerNew.x = corner.x + (newPos.x - camera->position.x); + cornerNew.y = corner.y + (newPos.y - camera->position.y); + } + } + else + { + /* If no non-diagonal collision is detected, a diagonal/corner collision + can still happen, check it here. */ + + if (xSquare != xSquareNew && ySquare != ySquareNew) + { + int8_t xyCollides = 0; + collCheck(xy,xSquareNew,ySquareNew) + + if (xyCollides) + { + // normally should slide, but let's KISS and simply stop any movement + cornerNew = corner; + } + } + } + + #undef collCheck + + camera->position.x = cornerNew.x - xDir * RCL_CAMERA_COLL_RADIUS; + camera->position.y = cornerNew.y - yDir * RCL_CAMERA_COLL_RADIUS; + } + + if (computeHeight && (movesInPlane || (heightOffset != 0) || force)) + { + camera->height += heightOffset; + + int16_t xSquare1 = RCL_divRoundDown(camera->position.x - + RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE); + + int16_t xSquare2 = RCL_divRoundDown(camera->position.x + + RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE); + + int16_t ySquare1 = RCL_divRoundDown(camera->position.y - + RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE); + + int16_t ySquare2 = RCL_divRoundDown(camera->position.y + + RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE); + + RCL_Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1); + RCL_Unit topLimit = ceilingHeightFunc != 0 ? + ceilingHeightFunc(xSquare1,ySquare1) : RCL_INFINITY; + + RCL_Unit height; + + #define checkSquares(s1,s2)\ + {\ + height = floorHeightFunc(xSquare##s1,ySquare##s2);\ + bottomLimit = RCL_max(bottomLimit,height);\ + height = ceilingHeightFunc != 0 ?\ + ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\ + topLimit = RCL_min(topLimit,height);\ + } + + if (xSquare2 != xSquare1) + checkSquares(2,1) + + if (ySquare2 != ySquare1) + checkSquares(1,2) + + if (xSquare2 != xSquare1 && ySquare2 != ySquare1) + checkSquares(2,2) + + camera->height = RCL_clamp(camera->height, + bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW, + topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE); + + #undef checkSquares + } +} + +void RCL_initCamera(RCL_Camera *camera) +{ + camera->position.x = 0; + camera->position.y = 0; + camera->direction = 0; + camera->resolution.x = 20; + camera->resolution.y = 15; + camera->shear = 0; + camera->height = RCL_UNITS_PER_SQUARE; +} + +void RCL_initRayConstraints(RCL_RayConstraints *constraints) +{ + constraints->maxHits = 1; + constraints->maxSteps = 20; +} + |