diff --git a/raycastlib.h b/raycastlib.h deleted file mode 100644 index a089839..0000000 --- a/raycastlib.h +++ /dev/null @@ -1,1344 +0,0 @@ -#ifndef RAYCASTLIB_H -#define RAYCASTLIB_H - -/** - raycastlib - Small C header-only raycasting library for embedded and low - performance computers, such as Arduino. Only uses integer math and stdint - standard library. - - Check the defines below to fine-tune accuracy vs performance! Don't forget - to compile with optimizations. - - author: Miloslav "drummyfish" Ciz - license: CC0 - version: 0.1 - - - Game field's bottom left corner is at [0,0]. - - X axis goes right in the ground plane. - - Y axis goes up in the ground plane. - - Height means the Z (vertical) coordinate. - - Each game square is UNITS_PER_SQUARE * UNITS_PER_SQUARE points. - - Angles are in Units, 0 means pointing right (x+) and positively rotates - clockwise. A full angle has UNITS_PER_SQUARE Units. -*/ - -#include - -#ifndef RAYCAST_TINY /** Turns on super efficient version of this library. Only - use if neccesarry, looks ugly. */ - #define UNITS_PER_SQUARE 1024 ///< N. of Units in a side of a spatial square. - typedef int32_t Unit; /**< Smallest spatial unit, there is UNITS_PER_SQUARE - units in a square's length. This effectively - serves the purpose of a fixed-point arithmetic. */ - #define UNIT_INFINITY 5000000; -#else - #define UNITS_PER_SQUARE 16 - typedef int16_t Unit; - #define UNIT_INFINITY 32000; - #define USE_DIST_APPROX 2 -#endif - -#ifndef USE_COS_LUT -#define USE_COS_LUT 0 /**< type of look up table for cos function: - 0: none (compute) - 1: 64 items - 2: 128 items */ -#endif - -#ifndef USE_DIST_APPROX -#define USE_DIST_APPROX 0 /** What distance approximation to use: - 0: none (compute full Euclidean distance) - 1: accurate approximation - 2: octagonal approximation (LQ) */ -#endif - -#ifndef VERTICAL_FOV -#define VERTICAL_FOV (UNITS_PER_SQUARE / 2) -#endif - -#ifndef HORIZONTAL_FOV -#define HORIZONTAL_FOV (UNITS_PER_SQUARE / 4) -#endif - -#define HORIZONTAL_FOV_HALF (HORIZONTAL_FOV / 2) - -#ifndef CAMERA_COLL_RADIUS -#define CAMERA_COLL_RADIUS UNITS_PER_SQUARE / 4 -#endif - -#ifndef CAMERA_COLL_HEIGHT_BELOW -#define CAMERA_COLL_HEIGHT_BELOW UNITS_PER_SQUARE -#endif - -#ifndef CAMERA_COLL_HEIGHT_ABOVE -#define CAMERA_COLL_HEIGHT_ABOVE (UNITS_PER_SQUARE / 3) -#endif - -#ifndef CAMERA_COLL_STEP_HEIGHT -#define CAMERA_COLL_STEP_HEIGHT (UNITS_PER_SQUARE / 2) -#endif - -#define logVector2D(v)\ - printf("[%d,%d]\n",v.x,v.y); - -#define logRay(r){\ - printf("ray:\n");\ - printf(" start: ");\ - logVector2D(r.start);\ - printf(" dir: ");\ - logVector2D(r.direction);}\ - -#define logHitResult(h){\ - printf("hit:\n");\ - printf(" sqaure: ");\ - logVector2D(h.square);\ - printf(" pos: ");\ - logVector2D(h.position);\ - printf(" dist: %d\n", h.distance);\ - printf(" texcoord: %d\n", h.textureCoord);}\ - -#define logPixelInfo(p){\ - printf("pixel:\n");\ - printf(" position: ");\ - logVector2D(p.position);\ - printf(" depth: %d\n", p.depth);\ - printf(" wall: %d\n", p.isWall);\ - printf(" textCoordY: %d\n", p.textureCoordY);\ - printf(" hit: ");\ - logHitResult(p.hit);\ - }\ - -/// Position in 2D space. -typedef struct -{ - Unit y; - Unit x; -} Vector2D; - -typedef struct -{ - Vector2D start; - Vector2D direction; -} Ray; - -typedef struct -{ - Vector2D square; ///< Collided square coordinates. - Vector2D position; ///< Exact collision position in Units. - Unit distance; /**< Euclidean distance to the hit position, or -1 if - no collision happened. */ - Unit textureCoord; ///< Normalized (0 to UNITS_PER_SQUARE - 1) tex coord. - Unit type; ///< Integer identifying type of square. - uint8_t direction; ///< Direction of hit. -} HitResult; - -// TODO: things like FOV could be constants to make them precomp. and faster? - -typedef struct -{ - Vector2D position; - Unit direction; - Vector2D resolution; - int16_t shear; /* Shear offset in pixels (0 => no shear), can simulate - looking up/down. */ - Unit height; -} Camera; - -/** - Holds an information about a single rendered pixel (for a pixel function - that works as a fragment shader. -*/ -typedef struct -{ - Vector2D position; ///< On-screen position. - int8_t isWall; ///< Whether the pixel is a wall or a floor/ceiling. - int8_t isFloor; ///< Whether the pixel is floor or ceiling. - int8_t isHorizon; ///< Whether the pixel is floor going towards horizon. - Unit depth; ///< Corrected depth. - HitResult hit; ///< Corresponding ray hit. - Unit textureCoordY; ///< Normalized (0 to UNITS_PER_SQUARE - 1) tex coord. -} PixelInfo; - -typedef struct -{ - uint16_t maxHits; - uint16_t maxSteps; - uint8_t computeTextureCoords; ///< Turns texture coords on/off. -} RayConstraints; - -/** - Function used to retrieve some information about cells of the rendered scene. - It should return a characteristic of given square as an integer (e.g. square - height, texture index, ...) - between squares that return different numbers - there is considered to be a collision. - - This function should be as fast as possible as it will typically be called - very often. -*/ -typedef Unit (*ArrayFunction)(int16_t x, int16_t y); - -/** - Function that renders a single pixel at the display. It is handed an info - about the pixel it should draw. - - This function should be as fast as possible as it will typically be called - very often. -*/ -typedef void (*PixelFunction)(PixelInfo *info); - -typedef void - (*ColumnFunction)(HitResult *hits, uint16_t hitCount, uint16_t x, Ray ray); - -/** - Simple-interface function to cast a single ray. - @return The first collision result. -*/ -HitResult castRay(Ray ray, ArrayFunction arrayFunc); - -/** - Maps a single point in the world to the screen (2D position + depth). -*/ -PixelInfo mapToScreen(Vector2D worldPosition, Unit height, Camera camera); - -/** - Casts a single ray and returns a list of collisions. -*/ -void castRayMultiHit(Ray ray, ArrayFunction arrayFunc, ArrayFunction typeFunc, - HitResult *hitResults, uint16_t *hitResultsLen, RayConstraints constraints); - -Vector2D angleToDirection(Unit angle); - -/** -Cos function. - -@param input to cos in Units (UNITS_PER_SQUARE = 2 * pi = 360 degrees) -@return normalized output in Units (from -UNITS_PER_SQUARE to UNITS_PER_SQUARE) -*/ -Unit cosInt(Unit input); - -Unit sinInt(Unit input); - -/// Normalizes given vector to have UNITS_PER_SQUARE length. -Vector2D normalize(Vector2D v); - -/// Computes a cos of an angle between two vectors. -Unit vectorsAngleCos(Vector2D v1, Vector2D v2); - -uint16_t sqrtInt(Unit value); -Unit dist(Vector2D p1, Vector2D p2); -Unit len(Vector2D v); - -/** - Converts an angle in whole degrees to an angle in Units that this library - uses. -*/ -Unit degreesToUnitsAngle(int16_t degrees); - -///< Computes the change in size of an object due to perspective. -Unit perspectiveScale(Unit originalSize, Unit distance); - -/** - Casts rays for given camera view and for each hit calls a user provided - function. -*/ -void castRaysMultiHit(Camera cam, ArrayFunction arrayFunc, - ArrayFunction typeFunction, ColumnFunction columnFunc, - RayConstraints constraints); - -/** - Using provided functions, renders a complete complex camera view. - - This function should render each screen pixel exactly once. - - @param cam camera whose view to render - @param floorHeightFunc function that returns floor height (in Units) - @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be - 0 (no ceiling will be rendered) - @param typeFunction function that says a type of square (e.g. its texture - index), can be 0 (no type in hit result) - @param pixelFunc callback function to draw a single pixel on screen - @param constraints constraints for each cast ray -*/ -void render(Camera cam, ArrayFunction floorHeightFunc, - ArrayFunction ceilingHeightFunc, ArrayFunction typeFunction, - PixelFunction pixelFunc, RayConstraints constraints); - -/** - Renders given camera view, with help of provided functions. This function is - simpler and faster than render(...) and is meant to be rendering scenes - with simple 1-intersection raycasting. The render(...) function can give more - accurate results than this function, so it's to be considered even for simple - scenes. - - This function should render each screen pixel exactly once. -*/ -void renderSimple(Camera cam, ArrayFunction floorHeightFunc, - ArrayFunction typeFunc, PixelFunction pixelFunc, RayConstraints constraints); - -/** - Function that moves given camera and makes it collide with walls and - potentially also floor and ceilings. It's meant to help implement player - movement. - - @param camera camera to move - @param planeOffset offset to move the camera in - @param heightOffset height offset to move the camera in - @param floorHeightFunc function used to retrieve the floor height - @param ceilingHeightFunc function for retrieving ceiling height, can be 0 - (camera won't collide with ceiling) - @param computeHeight whether to compute height - if false (0), floor and - ceiling functions won't be used and the camera will - only collide horizontally with walls (good for simpler - game, also faster) - @param force if true, forces to recompute collision even if position doesn't - change -*/ -void moveCameraWithCollision(Camera *camera, Vector2D planeOffset, - Unit heightOffset, ArrayFunction floorHeightFunc, - ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force); - -//============================================================================= -// privates - -#ifdef RAYCASTLIB_PROFILE - // function call counters for profiling - uint32_t profile_sqrtInt = 0; - uint32_t profile_clamp = 0; - uint32_t profile_cosInt = 0; - uint32_t profile_angleToDirection = 0; - uint32_t profile_dist = 0; - uint32_t profile_len = 0; - uint32_t profile_pointIsLeftOfRay = 0; - uint32_t profile_castRaySquare = 0; - uint32_t profile_castRayMultiHit = 0; - uint32_t profile_castRay = 0; - uint32_t profile_absVal = 0; - uint32_t profile_normalize = 0; - uint32_t profile_vectorsAngleCos = 0; - uint32_t profile_perspectiveScale = 0; - uint32_t profile_wrap = 0; - uint32_t profile_divRoundDown = 0; - #define profileCall(c) profile_##c += 1 - - #define printProfile() {\ - printf("profile:\n");\ - printf(" sqrtInt: %d\n",profile_sqrtInt);\ - printf(" clamp: %d\n",profile_clamp);\ - printf(" cosInt: %d\n",profile_cosInt);\ - printf(" angleToDirection: %d\n",profile_angleToDirection);\ - printf(" dist: %d\n",profile_dist);\ - printf(" len: %d\n",profile_len);\ - printf(" pointIsLeftOfRay: %d\n",profile_pointIsLeftOfRay);\ - printf(" castRaySquare: %d\n",profile_castRaySquare);\ - printf(" castRayMultiHit : %d\n",profile_castRayMultiHit);\ - printf(" castRay: %d\n",profile_castRay);\ - printf(" normalize: %d\n",profile_normalize);\ - printf(" vectorsAngleCos: %d\n",profile_vectorsAngleCos);\ - printf(" absVal: %d\n",profile_absVal);\ - printf(" perspectiveScale: %d\n",profile_perspectiveScale);\ - printf(" wrap: %d\n",profile_wrap);\ - printf(" divRoundDown: %d\n",profile_divRoundDown); } -#else - #define profileCall(c) -#endif - -Unit clamp(Unit value, Unit valueMin, Unit valueMax) -{ - profileCall(clamp); - - if (value >= valueMin) - { - if (value <= valueMax) - return value; - else - return valueMax; - } - else - return valueMin; -} - -Unit absVal(Unit value) -{ - profileCall(absVal); - - return value >= 0 ? value : -1 * value; -} - -/// Like mod, but behaves differently for negative values. -Unit wrap(Unit value, Unit mod) -{ - profileCall(wrap); - - return value >= 0 ? (value % mod) : (mod + (value % mod) - 1); -} - -/// Performs division, rounding down, NOT towards zero. -Unit divRoundDown(Unit value, Unit divisor) -{ - profileCall(divRoundDown); - - return value / divisor - ((value >= 0) ? 0 : 1); -} - -// Bhaskara's cosine approximation formula -#define trigHelper(x) (((Unit) UNITS_PER_SQUARE) *\ - (UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\ - (UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 + (x) * (x))) - - -#if USE_COS_LUT == 1 -const 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 -}; -#elif USE_COS_LUT == 2 -const 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 - -Unit cosInt(Unit input) -{ - profileCall(cosInt); - - // TODO: could be optimized with LUT - - input = wrap(input,UNITS_PER_SQUARE); - -#if USE_COS_LUT == 1 - return cosLUT[input / 16]; -#elif USE_COS_LUT == 2 - return cosLUT[input / 8]; -#else - if (input < UNITS_PER_SQUARE / 4) - return trigHelper(input); - else if (input < UNITS_PER_SQUARE / 2) - return -1 * trigHelper(UNITS_PER_SQUARE / 2 - input); - else if (input < 3 * UNITS_PER_SQUARE / 4) - return -1 * trigHelper(input - UNITS_PER_SQUARE / 2); - else - return trigHelper(UNITS_PER_SQUARE - input); -#endif -} - -#undef trigHelper - -Unit sinInt(Unit input) -{ - return cosInt(input - UNITS_PER_SQUARE / 4); -} - -Vector2D angleToDirection(Unit angle) -{ - profileCall(angleToDirection); - - Vector2D result; - - result.x = cosInt(angle); - result.y = -1 * sinInt(angle); - - return result; -} - -uint16_t sqrtInt(Unit value) -{ - profileCall(sqrtInt); - -#ifdef 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; -} - -Unit dist(Vector2D p1, Vector2D p2) -{ - profileCall(dist); - - Unit dx = p2.x - p1.x; - Unit dy = p2.y - p1.y; - -#if USE_DIST_APPROX == 2 - // octagonal approximation - - dx = absVal(dx); - dy = absVal(dy); - - return dy > dx ? dx / 2 + dy : dy / 2 + dx; -#elif USE_DIST_APPROX == 1 - // more accurate approximation - - Unit a, b, result; - - dx = dx < 0 ? -1 * dx : dx; - dy = dy < 0 ? -1 * dy : 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 sqrtInt((Unit) (dx + dy)); -#endif -} - -Unit len(Vector2D v) -{ - profileCall(len); - - Vector2D zero; - zero.x = 0; - zero.y = 0; - - return dist(zero,v); -} - -int8_t pointIsLeftOfRay(Vector2D point, Ray ray) -{ - profileCall(pointIsLeftOfRay); - - Unit dX = point.x - ray.start.x; - Unit dY = point.y - ray.start.y; - return (ray.direction.x * dY - ray.direction.y * dX) > 0; - // ^ Z component of cross-product -} - -/** - Casts a ray within a single square, to collide with the square borders. -*/ -void castRaySquare(Ray *localRay, Vector2D *nextCellOff, Vector2D *collOff) -{ - profileCall(castRaySquare); - - nextCellOff->x = 0; - nextCellOff->y = 0; - - Ray criticalLine; - criticalLine.start = localRay->start; - criticalLine.direction = localRay->direction; - - #define helper(c1,c2,n)\ - {\ - nextCellOff->c1 = n;\ - collOff->c1 = criticalLine.start.c1 - localRay->start.c1;\ - collOff->c2 = \ - (((Unit) collOff->c1) * localRay->direction.c2) /\ - ((localRay->direction.c1 == 0) ? 1 : localRay->direction.c1);\ - } - - #define helper2(n1,n2,c)\ - if (pointIsLeftOfRay(localRay->start,criticalLine) == c)\ - helper(y,x,n1)\ - else\ - helper(x,y,n2) - - if (localRay->direction.x > 0) - { - criticalLine.start.x = UNITS_PER_SQUARE - 1; - - if (localRay->direction.y > 0) - { - // top right - criticalLine.start.y = UNITS_PER_SQUARE - 1; - helper2(1,1,1) - } - else - { - // bottom right - criticalLine.start.y = 0; - helper2(-1,1,0) - } - } - else - { - criticalLine.start.x = 0; - - if (localRay->direction.y > 0) - { - // top left - criticalLine.start.y = UNITS_PER_SQUARE - 1; - helper2(1,-1,0) - } - else - { - // bottom left - criticalLine.start.y = 0; - helper2(-1,-1,1) - } - } - - #undef helper2 - #undef helper - - collOff->x += nextCellOff->x; - collOff->y += nextCellOff->y; -} - -void castRayMultiHit(Ray ray, ArrayFunction arrayFunc, ArrayFunction typeFunc, - HitResult *hitResults, uint16_t *hitResultsLen, RayConstraints constraints) -{ - profileCall(castRayMultiHit); - - Vector2D initialPos = ray.start; - Vector2D currentPos = ray.start; - - Vector2D currentSquare; - - currentSquare.x = divRoundDown(ray.start.x, UNITS_PER_SQUARE); - currentSquare.y = divRoundDown(ray.start.y,UNITS_PER_SQUARE); - - *hitResultsLen = 0; - - Unit squareType = arrayFunc(currentSquare.x,currentSquare.y); - - Vector2D no, co; // next cell offset, collision offset - - no.x = 0; // just to supress a warning - no.y = 0; - co.x = 0; - co.y = 0; - - for (uint16_t i = 0; i < constraints.maxSteps; ++i) - { - Unit currentType = arrayFunc(currentSquare.x,currentSquare.y); - - if (currentType != squareType) - { - // collision - - HitResult h; - - h.position = currentPos; - h.square = currentSquare; - h.distance = dist(initialPos,currentPos); - - if (typeFunc != 0) - h.type = typeFunc(currentSquare.x,currentSquare.y); - - if (no.y > 0) - { - h.direction = 0; - h.textureCoord = constraints.computeTextureCoords ? - wrap(currentPos.x,UNITS_PER_SQUARE) : 0; - } - else if (no.x > 0) - { - h.direction = 1; - h.textureCoord = constraints.computeTextureCoords ? - wrap(UNITS_PER_SQUARE - currentPos.y,UNITS_PER_SQUARE) : 0; - } - else if (no.y < 0) - { - h.direction = 2; - h.textureCoord = constraints.computeTextureCoords ? - wrap(UNITS_PER_SQUARE - currentPos.x,UNITS_PER_SQUARE) : 0; - } - else - { - h.direction = 3; - h.textureCoord = constraints.computeTextureCoords ? - wrap(currentPos.y,UNITS_PER_SQUARE) : 0; - } - - hitResults[*hitResultsLen] = h; - - *hitResultsLen += 1; - - squareType = currentType; - - if (*hitResultsLen >= constraints.maxHits) - break; - } - - ray.start.x = wrap(currentPos.x,UNITS_PER_SQUARE); - ray.start.y = wrap(currentPos.y,UNITS_PER_SQUARE); - - castRaySquare(&ray,&no,&co); - - currentSquare.x += no.x; - currentSquare.y += no.y; - - // offset into the next cell - currentPos.x += co.x; - currentPos.y += co.y; - } -} - -HitResult castRay(Ray ray, ArrayFunction arrayFunc) -{ - profileCall(castRay); - - HitResult result; - uint16_t len; - RayConstraints c; - - c.maxSteps = 1000; - c.maxHits = 1; - - castRayMultiHit(ray,arrayFunc,0,&result,&len,c); - - if (len == 0) - result.distance = -1; - - return result; -} - -void castRaysMultiHit(Camera cam, ArrayFunction arrayFunc, - ArrayFunction typeFunction, ColumnFunction columnFunc, - RayConstraints constraints) -{ - Vector2D dir1 = angleToDirection(cam.direction - HORIZONTAL_FOV_HALF); - Vector2D dir2 = angleToDirection(cam.direction + HORIZONTAL_FOV_HALF); - - Unit dX = dir2.x - dir1.x; - Unit dY = dir2.y - dir1.y; - - HitResult hits[constraints.maxHits]; - uint16_t hitCount; - - Ray r; - r.start = cam.position; - - for (uint16_t i = 0; i < cam.resolution.x; ++i) - { - r.direction.x = dir1.x + (dX * i) / cam.resolution.x; - r.direction.y = dir1.y + (dY * i) / cam.resolution.x; - - castRayMultiHit(r,arrayFunc,typeFunction,hits,&hitCount,constraints); - - columnFunc(hits,hitCount,i,r); - } -} - -// global helper variables, for precomputing stuff etc. -PixelFunction _pixelFunction = 0; -Camera _camera; -Unit _horizontalDepthStep = 0; -Unit _startFloorHeight = 0; -Unit _startCeilHeight = 0; -Unit _camResYLimit = 0; -Unit _middleRow = 0; -ArrayFunction _floorFunction = 0; -ArrayFunction _ceilFunction = 0; -uint8_t _computeTextureCoords = 0; -Unit _fHorizontalDepthStart = 0; -Unit _cHorizontalDepthStart = 0; -int16_t _cameraHeightScreen = 0; - -/** - Helper function that determines intersection with both ceiling and floor. -*/ -Unit _floorCeilFunction(int16_t x, int16_t y) -{ - // TODO: adjust also for RAYCAST_TINY - - Unit f = _floorFunction(x,y); - - if (_ceilFunction == 0) - return f; - - Unit c = _ceilFunction(x,y); - -#ifndef RAYCAST_TINY - return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff); -#else - return ((f & 0x00ff) << 8) | (c & 0x00ff); -#endif -} - -Unit _floorHeightNotZeroFunction(int16_t x, int16_t y) -{ - return _floorFunction(x,y) == 0 ? 0 : UNITS_PER_SQUARE; -} - -Unit adjustDistance(Unit distance, Camera *camera, 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). */ - - Unit result = - (distance * - vectorsAngleCos(angleToDirection(camera->direction),ray->direction)) / - UNITS_PER_SQUARE; - - return result == 0 ? 1 : result; - // ^ prevent division by zero -} - -void _columnFunction(HitResult *hits, uint16_t hitCount, uint16_t x, Ray ray) -{ - // last written Y position, can never go backwards - Unit fPosY = _camera.resolution.y; - Unit cPosY = -1; - - // world coordinates - Unit fZ1World = _startFloorHeight; - Unit cZ1World = _startCeilHeight; - - PixelInfo p; - p.position.x = x; - - Unit i; - - // we'll be simulatenously drawing the floor and the ceiling now - for (Unit j = 0; j <= hitCount; ++j) - { // ^ = add extra iteration for horizon plane - int8_t drawingHorizon = j == hitCount; - - HitResult hit; - Unit distance; - - Unit fWallHeight, cWallHeight; - Unit fZ2World, cZ2World; - Unit fZ1Screen, cZ1Screen; - Unit fZ2Screen, cZ2Screen; - - if (!drawingHorizon) - { - hit = hits[j]; - distance = adjustDistance(hit.distance,&_camera,&ray); - - fWallHeight = _floorFunction(hit.square.x,hit.square.y); - fZ2World = fWallHeight - _camera.height; - fZ1Screen = _middleRow - perspectiveScale( - (fZ1World * _camera.resolution.y) / UNITS_PER_SQUARE,distance); - fZ2Screen = _middleRow - perspectiveScale( - (fZ2World * _camera.resolution.y) / UNITS_PER_SQUARE,distance); - - if (_ceilFunction != 0) - { - cWallHeight = _ceilFunction(hit.square.x,hit.square.y); - cZ2World = cWallHeight - _camera.height; - cZ1Screen = _middleRow - perspectiveScale( - (cZ1World * _camera.resolution.y) / UNITS_PER_SQUARE,distance); - cZ2Screen = _middleRow - perspectiveScale( - (cZ2World * _camera.resolution.y) / UNITS_PER_SQUARE,distance); - } - } - else - { - fZ1Screen = _middleRow; - cZ1Screen = _middleRow + 1; - } - - Unit limit; - Unit verticalDistance; - - #define VERTICAL_DEPTH_MULTIPLY 2 - - #define drawHorizontal(pref,l1,l2,comp,inc)\ - p.depth += absVal(pref##Z1World) * VERTICAL_DEPTH_MULTIPLY;\ - limit = clamp(pref##Z1Screen,l1,l2);\ - for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\ - {\ - p.position.y = i;\ - p.depth += _horizontalDepthStep;\ - _pixelFunction(&p);\ - }\ - if (pref##PosY comp limit)\ - pref##PosY = limit; - - p.isWall = 0; - p.isHorizon = drawingHorizon; - - // draw floor until wall - p.isFloor = 1; - p.depth = (_fHorizontalDepthStart - fPosY) * _horizontalDepthStep; - drawHorizontal(f,cPosY + 1,_camera.resolution.y,>,-) - // ^ purposfully allow outside screen bounds here - - if (_ceilFunction != 0 || drawingHorizon) - { - // draw ceiling until wall - p.isFloor = 0; - p.depth = (cPosY - _cHorizontalDepthStart) * _horizontalDepthStep; - drawHorizontal(c,-1,fPosY - 1,<,+) - // ^ purposfully allow outside screen bounds here - } - - #undef drawHorizontal - #undef VERTICAL_DEPTH_MULTIPLY - - if (!drawingHorizon) // don't draw walls for horizon plane - { - #define drawVertical(pref,l1,l2,comp,inc)\ - {\ - limit = clamp(pref##Z2Screen,l1,l2);\ - Unit wallLength = pref##Z2Screen - pref##Z1Screen - 1;\ - wallLength = wallLength != 0 ? wallLength : 1;\ - Unit wallPosition = absVal(pref##Z1Screen - pref##PosY) inc (-1);\ - for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\ - {\ - p.position.y = i;\ - p.hit = hit;\ - p.textureCoordY = (wallPosition * UNITS_PER_SQUARE) / wallLength; \ - wallPosition++;\ - _pixelFunction(&p);\ - }\ - if (pref##PosY comp limit)\ - pref##PosY = limit;\ - pref##Z1World = pref##Z2World; /* for the next iteration */\ - } - - p.isWall = 1; - p.depth = distance; - p.isFloor = 1; - - // draw floor wall - - if (fPosY > 0) // still pixels left? - { - p.isFloor = 1; - drawVertical(f,cPosY + 1,_camera.resolution.y,>,-) - } // ^ purposfully allow outside screen bounds here - - // draw ceiling wall - - if (_ceilFunction != 0 && cPosY < _camResYLimit) // still pixels left? - { - p.isFloor = 0; - drawVertical(c,-1,fPosY - 1,<,+) - } // ^ puposfully allow outside screen bounds here - - #undef drawVertical - } - } -} - -void _columnFunctionSimple(HitResult *hits, uint16_t hitCount, uint16_t x, - Ray ray) -{ - int16_t y = 0; - int16_t wallHeightScreen = 0; - int16_t coordHelper = 0; - int16_t wallStart = _middleRow; - int16_t wallEnd = _middleRow; - int16_t heightOffset = 0; - - Unit dist = 1; - - PixelInfo p; - p.position.x = x; - - if (hitCount > 0) - { - HitResult hit = hits[0]; - p.hit = hit; - - dist = adjustDistance(hit.distance,&_camera,&ray); - - int16_t wallHeightWorld = _floorFunction(hit.square.x,hit.square.y); - - wallHeightScreen = perspectiveScale((wallHeightWorld * - _camera.resolution.y) / UNITS_PER_SQUARE,dist); - - int16_t normalizedWallHeight = - (UNITS_PER_SQUARE * wallHeightScreen) / wallHeightWorld; - - heightOffset = perspectiveScale(_cameraHeightScreen,dist); - - wallStart = _middleRow - wallHeightScreen + heightOffset + - normalizedWallHeight; - - coordHelper = -1 * wallStart; - coordHelper = coordHelper >= 0 ? coordHelper : 0; - - wallEnd = clamp(wallStart + wallHeightScreen,0,_camResYLimit); - wallStart = clamp(wallStart,0,_camResYLimit); - } - - // draw ceiling - - p.isWall = 0; - p.isFloor = 0; - p.isHorizon = 1; - p.depth = 1; - - while (y < wallStart) - { - p.position.y = y; - _pixelFunction(&p); - ++y; - p.depth += _horizontalDepthStep; - } - - // draw wall - - p.isWall = 1; - p.isFloor = 1; - p.depth = dist; - - while (y < wallEnd) - { - p.position.y = y; - - if (_computeTextureCoords) - p.textureCoordY = (coordHelper * UNITS_PER_SQUARE) / wallHeightScreen; - - _pixelFunction(&p); - - ++y; - ++coordHelper; - } - - // draw floor - - p.isWall = 0; - p.depth = _middleRow * _horizontalDepthStep; - - while (y < _camera.resolution.y) - { - p.position.y = y; - _pixelFunction(&p); - ++y; - p.depth -= _horizontalDepthStep; - } -} - -void render(Camera cam, ArrayFunction floorHeightFunc, - ArrayFunction ceilingHeightFunc, ArrayFunction typeFunction, - PixelFunction pixelFunc, RayConstraints constraints) -{ - _pixelFunction = pixelFunc; - _floorFunction = floorHeightFunc; - _ceilFunction = ceilingHeightFunc; - _camera = cam; - _camResYLimit = cam.resolution.y - 1; - - int16_t halfResY = cam.resolution.y / 2; - - _middleRow = halfResY + cam.shear; - - _fHorizontalDepthStart = _middleRow + halfResY; - _cHorizontalDepthStart = _middleRow - halfResY; - - _computeTextureCoords = constraints.computeTextureCoords; - - _startFloorHeight = floorHeightFunc( - divRoundDown(cam.position.x,UNITS_PER_SQUARE), - divRoundDown(cam.position.y,UNITS_PER_SQUARE)) -1 * cam.height; - - _startCeilHeight = - ceilingHeightFunc != 0 ? - ceilingHeightFunc( - divRoundDown(cam.position.x,UNITS_PER_SQUARE), - divRoundDown(cam.position.y,UNITS_PER_SQUARE)) -1 * cam.height - : UNIT_INFINITY; - - // TODO - _horizontalDepthStep = (12 * UNITS_PER_SQUARE) / cam.resolution.y; - - castRaysMultiHit(cam,_floorCeilFunction,typeFunction, - _columnFunction,constraints); -} - -void renderSimple(Camera cam, ArrayFunction floorHeightFunc, - ArrayFunction typeFunc, PixelFunction pixelFunc, RayConstraints constraints) -{ - _pixelFunction = pixelFunc; - _floorFunction = floorHeightFunc; - _camera = cam; - _camResYLimit = cam.resolution.y - 1; - _middleRow = cam.resolution.y / 2; - _computeTextureCoords = constraints.computeTextureCoords; - - _cameraHeightScreen = - (_camera.resolution.y * (_camera.height - UNITS_PER_SQUARE)) / - UNITS_PER_SQUARE; - - // TODO - _horizontalDepthStep = (12 * UNITS_PER_SQUARE) / cam.resolution.y; - - constraints.maxHits = 1; - - castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc, - _columnFunctionSimple, constraints); -} - -Vector2D normalize(Vector2D v) -{ - profileCall(normalize); - - Vector2D result; - - Unit l = len(v); - - l = l != 0 ? l : 1; - - result.x = (v.x * UNITS_PER_SQUARE) / l; - result.y = (v.y * UNITS_PER_SQUARE) / l; - - return result; -} - -Unit vectorsAngleCos(Vector2D v1, Vector2D v2) -{ - profileCall(vectorsAngleCos); - - v1 = normalize(v1); - v2 = normalize(v2); - - return (v1.x * v2.x + v1.y * v2.y) / UNITS_PER_SQUARE; -} - -PixelInfo mapToScreen(Vector2D worldPosition, Unit height, Camera camera) -{ - // TODO: precompute some stuff that's constant in the frame - - PixelInfo result; - - Unit d = dist(worldPosition,camera.position); - - Vector2D toPoint; - - toPoint.x = worldPosition.x - camera.position.x; - toPoint.y = worldPosition.y - camera.position.y; - - Vector2D cameraDir = angleToDirection(camera.direction); - - result.depth = // adjusted distance - (d * vectorsAngleCos(cameraDir,toPoint)) / UNITS_PER_SQUARE; - - result.position.y = camera.resolution.y / 2 - - (camera.resolution.y * - perspectiveScale(height - camera.height,result.depth)) / UNITS_PER_SQUARE - + camera.shear; - - Unit middleColumn = camera.resolution.x / 2; - - Unit a = sqrtInt(d * d - result.depth * result.depth); - - Ray r; - r.start = camera.position; - r.direction = cameraDir; - - if (!pointIsLeftOfRay(worldPosition,r)) - a *= -1; - - Unit cos = cosInt(HORIZONTAL_FOV_HALF); - - Unit b = - (result.depth * sinInt(HORIZONTAL_FOV_HALF)) / (cos == 0 ? 1 : cos); - // sin/cos = tan - - result.position.x = (a * middleColumn) / (b == 0 ? 1 : b); - result.position.x = middleColumn - result.position.x; - - return result; -} - -Unit degreesToUnitsAngle(int16_t degrees) -{ - return (degrees * UNITS_PER_SQUARE) / 360; -} - -Unit perspectiveScale(Unit originalSize, Unit distance) -{ - profileCall(perspectiveScale); - - return distance != 0 ? - (originalSize * UNITS_PER_SQUARE) / - ((VERTICAL_FOV * 2 * distance) / UNITS_PER_SQUARE) - : 0; -} - -void moveCameraWithCollision(Camera *camera, Vector2D planeOffset, - Unit heightOffset, ArrayFunction floorHeightFunc, - ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force) -{ - // TODO: have the cam coll parameters precomputed as macros? => faster - - int8_t movesInPlane = planeOffset.x != 0 || planeOffset.y != 0; - int16_t xSquareNew, ySquareNew; - - if (movesInPlane || force) - { - Vector2D corner; // BBox corner in the movement direction - Vector2D cornerNew; - - int16_t xDir = planeOffset.x > 0 ? 1 : (planeOffset.x < 0 ? -1 : 0); - int16_t yDir = planeOffset.y > 0 ? 1 : (planeOffset.y < 0 ? -1 : 0); - - corner.x = camera->position.x + xDir * CAMERA_COLL_RADIUS; - corner.y = camera->position.y + yDir * CAMERA_COLL_RADIUS; - - int16_t xSquare = divRoundDown(corner.x,UNITS_PER_SQUARE); - int16_t ySquare = divRoundDown(corner.y,UNITS_PER_SQUARE); - - cornerNew.x = corner.x + planeOffset.x; - cornerNew.y = corner.y + planeOffset.y; - - xSquareNew = divRoundDown(cornerNew.x,UNITS_PER_SQUARE); - ySquareNew = divRoundDown(cornerNew.y,UNITS_PER_SQUARE); - - Unit bottomLimit = camera->height - CAMERA_COLL_HEIGHT_BELOW + - CAMERA_COLL_STEP_HEIGHT; - Unit topLimit = camera->height + CAMERA_COLL_HEIGHT_ABOVE; - - // checks a single square for collision against the camera - #define collCheck(dir,s1,s2)\ - if (computeHeight)\ - {\ - Unit height = floorHeightFunc(s1,s2);\ - if (height > bottomLimit)\ - dir##Collides = 1;\ - else if (ceilingHeightFunc != 0)\ - {\ - height = ceilingHeightFunc(s1,s2);\ - if (height < topLimit)\ - dir##Collides = 1;\ - }\ - }\ - else\ - dir##Collides = floorHeightFunc(s1,s2) > CAMERA_COLL_STEP_HEIGHT; - - // check a 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 = divRoundDown(corner.dir2 - dir2##Dir *\ - CAMERA_COLL_RADIUS * 2,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) - - #define collHandle(dir)\ - if (dir##Collides)\ - cornerNew.dir = (dir##Square) * UNITS_PER_SQUARE + UNITS_PER_SQUARE / 2\ - + dir##Dir * (UNITS_PER_SQUARE / 2) - dir##Dir;\ - - if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner - collision can't happen */ - { - if (xSquare != xSquareNew && ySquare != ySquareNew) // corner? - { - int8_t xyCollides = 0; - collCheck(xy,xSquareNew,ySquareNew) - - if (xyCollides) - { - // normally should slide, but let's KISS - cornerNew = corner; - } - } - } - - collHandle(x) - collHandle(y) - - #undef collCheck - #undef collHandle - - camera->position.x = cornerNew.x - xDir * CAMERA_COLL_RADIUS; - camera->position.y = cornerNew.y - yDir * CAMERA_COLL_RADIUS; - } - - if (computeHeight && (movesInPlane || heightOffset != 0 || force)) - { - camera->height += heightOffset; - - int16_t xSquare1 = - divRoundDown(camera->position.x - CAMERA_COLL_RADIUS,UNITS_PER_SQUARE); - int16_t xSquare2 = - divRoundDown(camera->position.x + CAMERA_COLL_RADIUS,UNITS_PER_SQUARE); - int16_t ySquare1 = - divRoundDown(camera->position.y - CAMERA_COLL_RADIUS,UNITS_PER_SQUARE); - int16_t ySquare2 = - divRoundDown(camera->position.y + CAMERA_COLL_RADIUS,UNITS_PER_SQUARE); - - Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1); - Unit topLimit = ceilingHeightFunc != 0 ? - ceilingHeightFunc(xSquare1,ySquare1) : UNIT_INFINITY; - - Unit height; - - #define checkSquares(s1,s2)\ - {\ - height = floorHeightFunc(xSquare##s1,ySquare##s2);\ - bottomLimit = bottomLimit < height ? height : bottomLimit;\ - height = ceilingHeightFunc != 0 ?\ - ceilingHeightFunc(xSquare##s1,ySquare##s2) : UNIT_INFINITY;\ - topLimit = topLimit > height ? height : topLimit;\ - } - - if (xSquare2 != xSquare1) - checkSquares(2,1) - - if (ySquare2 != ySquare1) - checkSquares(1,2) - - if (xSquare2 != xSquare1 && ySquare2 != ySquare1) - checkSquares(2,2) - - camera->height = clamp(camera->height, - bottomLimit + CAMERA_COLL_HEIGHT_BELOW, - topLimit - CAMERA_COLL_HEIGHT_ABOVE); - #undef checkSquares - } -} - -#endif