summaryrefslogtreecommitdiffstats
path: root/src/raycastlib.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/raycastlib.h')
-rw-r--r--src/raycastlib.h504
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