diff options
author | salaaad2 <arthurdurant263@gmail.com> | 2022-05-17 20:58:07 +0200 |
---|---|---|
committer | salaaad2 <arthurdurant263@gmail.com> | 2022-05-17 20:58:07 +0200 |
commit | ec8e4eb6320ddaabd71afbda12e93fbdab0d10d0 (patch) | |
tree | f9cf6bbdbdc512a258701ed2c0ea8c838d51a6c1 /src/raycastlib.h | |
download | watchoom-ec8e4eb6320ddaabd71afbda12e93fbdab0d10d0.tar.gz watchoom-ec8e4eb6320ddaabd71afbda12e93fbdab0d10d0.tar.bz2 watchoom-ec8e4eb6320ddaabd71afbda12e93fbdab0d10d0.tar.xz watchoom-ec8e4eb6320ddaabd71afbda12e93fbdab0d10d0.tar.zst watchoom-ec8e4eb6320ddaabd71afbda12e93fbdab0d10d0.zip |
initial commit
Diffstat (limited to 'src/raycastlib.h')
-rw-r--r-- | src/raycastlib.h | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/raycastlib.h b/src/raycastlib.h new file mode 100644 index 0000000..c882bfd --- /dev/null +++ b/src/raycastlib.h @@ -0,0 +1,504 @@ +#ifndef RAYCASTLIB_H +#define RAYCASTLIB_H + +/** + raycastlib (RCL) - 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. + + Before including the library define RCL_PIXEL_FUNCTION to the name of the + function (with RCL_PixelFunction signature) that will render your pixels! + + - All public (and most private) library identifiers start with RCL_. + - 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 RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE points. + - Angles are in RCL_Units, 0 means pointing right (x+) and positively rotates + clockwise. A full angle has RCL_UNITS_PER_SQUARE RCL_Units. + - Most things are normalized with RCL_UNITS_PER_SQUARE (sin, cos, vector + unit length, texture coordinates etc.). + - Screen coordinates are normal: [0,0] = top left, x goes right, y goes down. + + author: Miloslav "drummyfish" Ciz + license: CC0 1.0 + version: 0.908d + + Version numbering: major.minor[d], id 'd' is appended, this is a + in-development version based on the previous stable major.minor version. Two + 'd' versions with the same version number, .e.g. 1.0d, may be different. +*/ + +#include <stdint.h> + +#ifndef RCL_RAYCAST_TINY /** Turns on super efficient version of this library. + Only use if neccesarry, looks ugly. Also not done + yet. */ + #define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a + spatial square. */ + typedef int32_t RCL_Unit; /**< Smallest spatial unit, there is + RCL_UNITS_PER_SQUARE units in a square's + length. This effectively serves the purpose of + a fixed-point arithmetic. */ + #define RCL_INFINITY 2000000000 +#else + #define RCL_UNITS_PER_SQUARE 32 + typedef int16_t RCL_Unit; + #define RCL_INFINITY 30000 + #define RCL_USE_DIST_APPROX 2 +#endif + +#ifndef RCL_COMPUTE_WALL_TEXCOORDS +#define RCL_COMPUTE_WALL_TEXCOORDS 1 +#endif + +#ifndef RCL_COMPUTE_FLOOR_TEXCOORDS +#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) + 1: 64 items + 2: 128 items */ +#endif + +#ifndef RCL_USE_DIST_APPROX +#define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use: + 0: none (compute full Euclidean distance) + 1: accurate approximation + 2: octagonal approximation (LQ) */ +#endif + +#ifndef RCL_RECTILINEAR +#define RCL_RECTILINEAR 1 /**< Whether to use rectilinear perspective (normally + used), or curvilinear perspective (fish eye). */ +#endif + +#ifndef RCL_TEXTURE_VERTICAL_STRETCH +#define RCL_TEXTURE_VERTICAL_STRETCH 1 /**< Whether textures should be + stretched to wall height (possibly + slightly slower if on). */ +#endif + +#ifndef RCL_COMPUTE_FLOOR_DEPTH +#define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for + floor pixels - turns this off if not + needed. */ +#endif + +#ifndef RCL_COMPUTE_CEILING_DEPTH +#define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for + ceiling. */ +#endif + +#ifndef RCL_ROLL_TEXTURE_COORDS +#define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also + roll the texture coordinates along (mostly + desired for doors). */ +#endif + +#ifndef RCL_VERTICAL_FOV +#define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 3) +#endif + +#define RCL_VERTICAL_FOV_TAN (RCL_VERTICAL_FOV * 4) ///< tan approximation + +#ifndef RCL_HORIZONTAL_FOV +#define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4) +#endif + +#define RCL_HORIZONTAL_FOV_TAN (RCL_HORIZONTAL_FOV * 4) + +#define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2) + +#ifndef RCL_CAMERA_COLL_RADIUS +#define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4 +#endif + +#ifndef RCL_CAMERA_COLL_HEIGHT_BELOW +#define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE +#endif + +#ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE +#define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3) +#endif + +#ifndef RCL_CAMERA_COLL_STEP_HEIGHT +#define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2) +#endif + +#ifndef RCL_TEXTURE_INTERPOLATION_SCALE + #define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed + poit vertical texture coord + computation. This should be power + of two! Higher number can look more + accurate but may cause overflow. */ +#endif + +#define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the + horizon has (the floor + depth is only + approximated with the + help of this + constant). */ +#ifndef RCL_VERTICAL_DEPTH_MULTIPLY +#define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height + difference when approximating floor/ceil + depth. */ +#endif + +#define RCL_min(a,b) ((a) < (b) ? (a) : (b)) +#define RCL_max(a,b) ((a) > (b) ? (a) : (b)) +#define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions. +#define RCL_zeroClamp(x) ((x) * ((x) >= 0)) +#define RCL_likely(cond) __builtin_expect(!!(cond),1) +#define RCL_unlikely(cond) __builtin_expect(!!(cond),0) + +#define RCL_logV2D(v)\ + printf("[%d,%d]\n",v.x,v.y); + +#define RCL_logRay(r){\ + printf("ray:\n");\ + printf(" start: ");\ + RCL_logV2D(r.start);\ + printf(" dir: ");\ + RCL_logV2D(r.direction);} + +#define RCL_logHitResult(h){\ + printf("hit:\n");\ + printf(" square: ");\ + RCL_logV2D(h.square);\ + printf(" pos: ");\ + RCL_logV2D(h.position);\ + printf(" dist: %d\n", h.distance);\ + printf(" dir: %d\n", h.direction);\ + 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 +{ + RCL_Unit x; + RCL_Unit y; +} RCL_Vector2D; + +typedef struct +{ + RCL_Vector2D start; + RCL_Vector2D direction; +} RCL_Ray; + +typedef struct +{ + RCL_Unit distance; /**< Distance to the hit position, or -1 if no + collision happened. If RCL_RECTILINEAR != 0, then + the distance is perpendicular to the projection + plane (fish eye correction), otherwise it is + the straight distance to the ray start + position. */ + uint8_t direction; /**< Direction of hit. The convention for angle + units is explained above. */ + RCL_Unit textureCoord; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1) + texture coordinate (horizontal). */ + RCL_Vector2D square; ///< Collided square coordinates. + RCL_Vector2D position; ///< Exact collision position in RCL_Units. + RCL_Unit arrayValue; /** Value returned by array function (most often + this will be the floor height). */ + RCL_Unit type; /**< Integer identifying type of square (number + returned by type function, e.g. texture + index).*/ + RCL_Unit doorRoll; ///< Holds value of door roll. +} RCL_HitResult; + +typedef struct +{ + RCL_Vector2D position; + 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. */ + RCL_Unit height; +} RCL_Camera; + +/** + Holds an information about a single rendered pixel (for a pixel function + that works as a fragment shader). +*/ +typedef struct +{ + RCL_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; ///< If the pixel belongs to horizon segment. + RCL_Unit depth; ///< Corrected depth. + RCL_Unit wallHeight;///< Only for wall pixels, says its height. + 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. */ +} RCL_PixelInfo; + +void RCL_PIXEL_FUNCTION (RCL_PixelInfo *pixel); + +typedef struct +{ + uint16_t maxHits; + uint16_t maxSteps; +} RCL_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 RCL_Unit (*RCL_ArrayFunction)(int16_t x, int16_t y); +/* + TODO: maybe array functions should be replaced by defines of funtion names + like with pixelFunc? Could be more efficient than function pointers. +*/ + + +RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor); + +/** + 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 (*RCL_PixelFunction)(RCL_PixelInfo *info); + +typedef void + (*RCL_ColumnFunction)(RCL_HitResult *hits, uint16_t hitCount, uint16_t x, + RCL_Ray ray); + +/** + 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). +*/ +RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height, + RCL_Camera camera); + +/** + Casts a single ray and returns a list of collisions. + + @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit + distance is divided by the ray direction vector length (to correct + the fish eye effect) + @param arrayFunc function that will be used to determine collisions (hits) + with the ray (squares for which this function returns different values + are considered to have a collision between them), this will typically + be a function returning floor height + @param typeFunc optional (can be 0) function - if provided, it will be used + to mark the hit result with the number returned by this function + (it can be e.g. a texture index) + @param hitResults array in which the hit results will be stored (has to be + preallocated with at space for at least as many hit results as + maxHits specified with the constraints parameter) + @param hitResultsLen in this variable the number of hit results will be + returned + @param constraints specifies constraints for the ray cast +*/ +void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc, + RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults, + uint16_t *hitResultsLen, RCL_RayConstraints constraints); + +RCL_Vector2D RCL_angleToDirection(RCL_Unit angle); + +/** +Cos function. + +@param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees) +@return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to + RCL_UNITS_PER_SQUARE) +*/ +RCL_Unit RCL_cos(RCL_Unit input); + +RCL_Unit RCL_sin(RCL_Unit input); + +RCL_Unit RCL_tan(RCL_Unit input); + +RCL_Unit RCL_ctg(RCL_Unit input); + +/// Normalizes given vector to have RCL_UNITS_PER_SQUARE length. +RCL_Vector2D RCL_normalize(RCL_Vector2D v); + +/// Computes a cos of an angle between two vectors. +RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2); + +uint16_t RCL_sqrt(RCL_Unit value); +RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2); +RCL_Unit RCL_len(RCL_Vector2D v); + +/** + Converts an angle in whole degrees to an angle in RCL_Units that this library + uses. +*/ +RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees); + +///< 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_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); + +/** + Casts rays for given camera view and for each hit calls a user provided + function. +*/ +void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc, + RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc, + RCL_RayConstraints constraints); + +/** + Using provided functions, renders a complete complex (multilevel) camera + view. + + This function should render each screen pixel exactly once. + + function rendering summary: + - performance: slower + - accuracy: higher + - wall textures: yes + - different wall heights: yes + - floor/ceiling textures: no + - floor geometry: yes, multilevel + - ceiling geometry: yes (optional), multilevel + - rolling door: no + - camera shearing: yes + - rendering order: left-to-right, not specifically ordered vertically + + @param cam camera whose view to render + @param floorHeightFunc function that returns floor height (in RCL_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 RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, + RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction, + RCL_RayConstraints constraints); + +/** + Renders given camera view, with help of provided functions. This function is + simpler and faster than RCL_renderComplex(...) and is meant to be rendering + flat levels. + + function rendering summary: + - performance: faster + - accuracy: lower + - wall textures: yes + - different wall heights: yes + - floor/ceiling textures: yes (only floor, you can mirror it for ceiling) + - floor geometry: no (just flat floor, with depth information) + - ceiling geometry: no (just flat ceiling, with depth information) + - rolling door: yes + - camera shearing: no + - rendering order: left-to-right, top-to-bottom + + Additionally this function supports rendering rolling doors. + + This function should render each screen pixel exactly once. + + @param rollFunc function that for given square says its door roll in + RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right, + -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door, + rendering should also be faster as fewer intersections will be tested) +*/ +void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc, + RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc, + RCL_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 RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset, + RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc, + RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force); + +void RCL_initCamera(RCL_Camera *camera); +void RCL_initRayConstraints(RCL_RayConstraints *constraints); + +#endif |