From 2c070332aa6eeac47bf1bd101850140b7064fa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 8 Aug 2020 11:10:34 +0200 Subject: [PATCH] Update README --- README.md | 22 + assets/levelD.gif | Bin 0 -> 3065 bytes assets/levelE.gif | Bin 0 -> 3482 bytes assets/levelZ.gif | Bin 4814 -> 4816 bytes game.html | 1 + index.html | 2 +- {assets => media}/logo_big.png | Bin raycastlib_old.h | 2038 ++++++++++++++++++++++++++++++++ settings.h | 7 +- 9 files changed, 2066 insertions(+), 4 deletions(-) create mode 100644 assets/levelD.gif create mode 100644 assets/levelE.gif create mode 100644 game.html rename {assets => media}/logo_big.png (100%) create mode 100644 raycastlib_old.h diff --git a/README.md b/README.md index 5f9bcd8..2cfcf4d 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,28 @@ Yes. This isn't made for any profit, but if you decide you want to share some fo ## code guide +The repository structure is following: + +``` +assets/ asset sources (textures, sprites, maps, sounds, ...) + *.py scripts for converting assets to C structs/arrays +media/ media presenting the game (screenshots, logo, ...) +constants.h game constants that aren't considered settings +images.h images (textures, sprites) from assets folder converted to C +levels.h levels from assets folder converted to C +palette.h game 256 color palette +platform_*.png fronted implementation for various platforms +raycastlib.h raycasting library +settings.h game settings that users can change (FPS, resolution, ...) +sounds.h sounds from assets folder converted to C +texts.h game texts +main.c main game logic, this file is passed to the compiler +make.sh compiling script constaining compiler settings +HTMLshell.html HTML shell for emscripten (browser) version +index.html game website +README.md this readme +``` + TODO source files diff --git a/assets/levelD.gif b/assets/levelD.gif new file mode 100644 index 0000000000000000000000000000000000000000..9ad796f09d71c50ca3705f90a80934658e0b18e1 GIT binary patch literal 3065 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 H0s;U#={%Kg literal 0 HcmV?d00001 diff --git a/assets/levelE.gif b/assets/levelE.gif new file mode 100644 index 0000000000000000000000000000000000000000..a24f3dc2484be3cb3b5a7e08958fdeb2183ca41b GIT binary patch literal 3482 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 I5D^dnJHk9wga7~l literal 0 HcmV?d00001 diff --git a/assets/levelZ.gif b/assets/levelZ.gif index 7d39331e62978a55380c95d85ec01950d59f3d71..13f936339c3de9753167b65ead128b46357ee36e 100644 GIT binary patch delta 730 zcmV<00ww**N~AVg zp_;0wo!Y3N3aJ$;sik_4_E4QGil^eKrw8hzQJSmx$)q`&pR|8ktLX`>@42cvO034} znCOY7!8)Xc%B;=mn9!=L?pdtWDxt_KrXw|`;i;_;+NZv{kJl=zzLu@q3aA{FpWr$h z@2Z{RTCL}bt>8JY&04Sanw|K{kAM1)|7xw>y084Yu5CK71bdzRTC4h+uwH7gkBP7j z`=k&%pY1xa@~VH2ZD5ZZTd~}Fu^5Y;^IEQ7+OQ#8rXEYFAbYY8oBpx@DX}a&o(5|K z5xcT*dK9D@pSSv>r&^?3ileLQvqoBoY5_$?-~lJ)0T$>1-_W!X;{lRr4;bOH{-~fv zN~l_jsWCdMYhkoV8$3!oe@yGNrUA8*IJHK(v-G;6s+xbKSo*VuS`l5lJx80gOWU-c zShi7%wp44bF#DcJk+Vgqw)tAAS$d`HdAKAsA$KaC3rnYcda5$&w(R+$h&!H)ORar- ztnDhaRrnx%m0GlWMYnD^P$dpM;yCJ)5eYn|xb(xgLOln(L?d7_bK0vxKUw zU7EE{dJ%uNTe_R;x1F1~p{uO3yQIQfi?>_6k^8sVs=Df0y!o#vxxK5r{93lp+pITW zu|E~NlPj;>OT5;LtDWn+zPr7X(YWDyzUcc?CrZ7J%b1q4w&@F_$*aHQ7rY+ozW3Ug z_iL<@yS~2*x>(B-0GzQ@+qxeczHQsTTg$(0`@d4%JE=SCz{V@TAj&S)d%m}dx-kn9 z@OzKCc)}>0!YZu7A_iJ7VG~4A5iGI7G>j86+z~ho!!fKnKvBa!ED<8sQ7a+DKY_zP Me8fmB69EALI~Vzhga7~l delta 728 zcmV;}0w?{@CC(+V0}y}8rjJRV9{Q&48KZHEpr>Gud&;4f!Vy&ZqDe}A{`VQ2!&#ii znVienoX-iK_nD`13Zx?usD#?3O=_sbX`INZoXqK*X8@yMa}kl+o!@DsS4yNdTA`SV zsGGW|p6aL-3aO)dkM>ZVD{80WiKhqZqfvUR_{pR>nxC>dtLcB~tM8erIx4Kh>X_(> zroTF*gQ~2{>X^=otL{0h(<-6HDyAbfrs0XL54xwkx{uWgtG;%v+3Kepm7m@^8||u{ z;5x14YOUZIugf~G^qQUa%8z~ekNrxm+nTTXnyzgcumXFX`#P)mdazzfuyeYu3|p@b z`>;P1vGA&oZD4A*%6Nt3O(*Marc(nyNi(q=l#!P-Fxia8e#%fgbP;OdByCAc^*X5iIME3TmW+ zs->11qp`XcMSHZtleF`nxqrL4eVejyDp2qFxU>qTJbS91n|xb(xgLOln#-s7_^$-Zvx17O zU5d3%dJ%uNTe_y3kCH2+mD;wkYooPWi??gKk?XgstGeh~ytb{{xs|)5!HckD`@G9~ z1M}Irikqw;+r7kVy&d~gZ|l4IYQC$BzV}G7(@S9ITa=cgw&Dw;q06@97rY+YxZm2C z_e-pj%f7!0x>w5+04%XnOStiCxcb|_S{uP_`@c}$8>u?$!2Bq`*Gj#P3%|9>!7uBe z@T-d^e8MQ4!YM3bp!E_qK@=6i5-L2yI1$4galR)mG~B}yAz~f15<&bEH|)bk Ke8e&l5CA(3nS{Oo diff --git a/game.html b/game.html new file mode 100644 index 0000000..1c29504 --- /dev/null +++ b/game.html @@ -0,0 +1 @@ +game \ No newline at end of file diff --git a/index.html b/index.html index fc1befd..95481d3 100644 --- a/index.html +++ b/index.html @@ -159,7 +159,7 @@ the suckless, anticapitalist, public domain game, for everyone - + THIS IS SPECIAL diff --git a/assets/logo_big.png b/media/logo_big.png similarity index 100% rename from assets/logo_big.png rename to media/logo_big.png diff --git a/raycastlib_old.h b/raycastlib_old.h new file mode 100644 index 0000000..bda7c59 --- /dev/null +++ b/raycastlib_old.h @@ -0,0 +1,2038 @@ +#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.904 +*/ + +#include + +#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 / 5) +#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_VERTICAL_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. +*/ + +/** + 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_cosInt(RCL_Unit input); + +RCL_Unit RCL_sinInt(RCL_Unit input); + +RCL_Unit RCL_tanInt(RCL_Unit input); + +RCL_Unit RCL_ctgInt(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_sqrtInt(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); + +//============================================================================= +// privates + +#define _RCL_UNUSED(what) (void)(what); + +// 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; + +#ifdef RCL_PROFILE + // function call counters for profiling + uint32_t profile_RCL_sqrtInt = 0; + uint32_t profile_RCL_clamp = 0; + uint32_t profile_RCL_cosInt = 0; + uint32_t profile_RCL_angleToDirection = 0; + uint32_t profile_RCL_dist = 0; + uint32_t profile_RCL_len = 0; + uint32_t profile_RCL_pointIsLeftOfRay = 0; + uint32_t profile_RCL_castRayMultiHit = 0; + uint32_t profile_RCL_castRay = 0; + uint32_t profile_RCL_absVal = 0; + uint32_t profile_RCL_normalize = 0; + uint32_t profile_RCL_vectorsAngleCos = 0; + uint32_t profile_RCL_perspectiveScaleVertical = 0; + uint32_t profile_RCL_wrap = 0; + uint32_t profile_RCL_divRoundDown = 0; + #define RCL_profileCall(c) profile_##c += 1 + + #define printProfile() {\ + printf("profile:\n");\ + printf(" RCL_sqrtInt: %d\n",profile_RCL_sqrtInt);\ + printf(" RCL_clamp: %d\n",profile_RCL_clamp);\ + printf(" RCL_cosInt: %d\n",profile_RCL_cosInt);\ + printf(" RCL_angleToDirection: %d\n",profile_RCL_angleToDirection);\ + printf(" RCL_dist: %d\n",profile_RCL_dist);\ + printf(" RCL_len: %d\n",profile_RCL_len);\ + printf(" RCL_pointIsLeftOfRay: %d\n",profile_RCL_pointIsLeftOfRay);\ + printf(" RCL_castRayMultiHit : %d\n",profile_RCL_castRayMultiHit);\ + printf(" RCL_castRay: %d\n",profile_RCL_castRay);\ + printf(" RCL_normalize: %d\n",profile_RCL_normalize);\ + printf(" RCL_vectorsAngleCos: %d\n",profile_RCL_vectorsAngleCos);\ + printf(" RCL_absVal: %d\n",profile_RCL_absVal);\ + printf(" RCL_perspectiveScaleVertical: %d\n",profile_RCL_perspectiveScaleVertical);\ + printf(" RCL_wrap: %d\n",profile_RCL_wrap);\ + printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); } +#else + #define RCL_profileCall(c) +#endif + +RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax) +{ + RCL_profileCall(RCL_clamp); + + if (value >= valueMin) + { + if (value <= valueMax) + return value; + else + return valueMax; + } + else + return valueMin; +} + +static inline RCL_Unit RCL_absVal(RCL_Unit value) +{ + RCL_profileCall(RCL_absVal); + + 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_profileCall(RCL_wrap); + RCL_Unit cmp = value < 0; + return cmp * mod + (value % mod) - cmp; +} + +/// Performs division, rounding down, NOT towards zero. +static inline RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor) +{ + RCL_profileCall(RCL_divRoundDown); + + 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_cosInt(RCL_Unit input) +{ + RCL_profileCall(RCL_cosInt); + + 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_sinInt(RCL_Unit input) +{ + return RCL_cosInt(input - RCL_UNITS_PER_SQUARE / 4); +} + +RCL_Unit RCL_tanInt(RCL_Unit input) +{ + return (RCL_sinInt(input) * RCL_UNITS_PER_SQUARE) / RCL_cosInt(input); +} + +RCL_Unit RCL_ctgInt(RCL_Unit input) +{ + return (RCL_cosInt(input) * RCL_UNITS_PER_SQUARE) / RCL_sinInt(input); +} + +RCL_Vector2D RCL_angleToDirection(RCL_Unit angle) +{ + RCL_profileCall(RCL_angleToDirection); + + RCL_Vector2D result; + + result.x = RCL_cosInt(angle); + result.y = -1 * RCL_sinInt(angle); + + return result; +} + +uint16_t RCL_sqrtInt(RCL_Unit value) +{ + RCL_profileCall(RCL_sqrtInt); + +#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_profileCall(RCL_dist); + + RCL_Unit dx = p2.x - p1.x; + RCL_Unit dy = p2.y - p1.y; + +#if RCL_USE_DIST_APPROX == 2 + // octagonal approximation + + dx = RCL_absVal(dx); + dy = RCL_absVal(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_sqrtInt((RCL_Unit) (dx + dy)); +#endif +} + +RCL_Unit RCL_len(RCL_Vector2D v) +{ + RCL_profileCall(RCL_len); + + 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_profileCall(RCL_pointIsLeftOfRay); + + 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_profileCall(RCL_castRayMultiHit); + + 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_absVal(dirVecLengthNorm / RCL_nonZero(ray.direction.x)); + delta.y = RCL_absVal(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 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 one axis) + divided by leg B (length along the same axis). */ + + h.distance = + (((h.position.x - ray.start.x) / 4) * + RCL_UNITS_PER_SQUARE * rayDirXRecip) + / (RECIP_SCALE / 4); + + // ^ / 4 is here to prevent overflow +#endif + } + 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 + h.distance = + (((h.position.y - ray.start.y) / 4) * + RCL_UNITS_PER_SQUARE * rayDirYRecip) + / (RECIP_SCALE / 4); + + // ^ / 4 is here to prevent overflow +#endif + } + +#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_profileCall(RCL_castRay); + + RCL_HitResult result; + uint16_t RCL_len; + RCL_RayConstraints c; + + c.maxSteps = 1000; + c.maxHits = 1; + + RCL_castRayMultiHit(ray,arrayFunc,0,&result,&RCL_len,c); + + if (RCL_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_cosInt(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_absVal(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_absVal(height); + + pixelInfo->isWall = 1; + + RCL_Unit limit = RCL_clamp(yTo,limit1,limit2); + + RCL_Unit wallLength = RCL_nonZero(RCL_absVal(yTo - yFrom - 1)); + + RCL_Unit wallPosition = RCL_absVal(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 heightOffset = 0; + + 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; + + int16_t wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y); + + wallHeightScreen = RCL_perspectiveScaleVertical((wallHeightWorld * + _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,dist); + + int16_t RCL_normalizedWallHeight = wallHeightWorld != 0 ? + ((RCL_UNITS_PER_SQUARE * wallHeightScreen) / wallHeightWorld) : 0; + + heightOffset = RCL_perspectiveScaleVertical(_RCL_cameraHeightScreen,dist); + + wallStart = _RCL_middleRow - wallHeightScreen + heightOffset + + RCL_normalizedWallHeight; + } + } + 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_absVal(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_profileCall(RCL_normalize); + + 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) +{ + RCL_profileCall(RCL_vectorsAngleCos); + + 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 + + RCL_Unit cos = RCL_cosInt(camera.direction); + RCL_Unit sin = RCL_sinInt(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 + (-1 * toPoint.y * middleColumn) / RCL_nonZero(result.depth); + + result.position.y = + camera.resolution.y / 2 - + (RCL_perspectiveScaleVertical(height - camera.height,result.depth) + * camera.resolution.y) / RCL_UNITS_PER_SQUARE + + camera.shear; + + return result; +} + +RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees) +{ + return (degrees * RCL_UNITS_PER_SQUARE) / 360; +} + +RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance) +{ + RCL_profileCall(RCL_perspectiveScaleVertical); + + return distance != 0 ? + (originalSize * RCL_UNITS_PER_SQUARE) / + ((RCL_VERTICAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE) + : 0; +} + +RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize, + RCL_Unit scaledSize) +{ + return scaledSize != 0 ? + (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) / + // ^ take the middle + ((RCL_VERTICAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE) + : RCL_INFINITY; +} + +RCL_Unit + RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance) +{ + return distance != 0 ? + (originalSize * RCL_UNITS_PER_SQUARE) / + ((RCL_HORIZONTAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE) + : 0; +} + +RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize, + RCL_Unit scaledSize) +{ + 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; + int16_t xSquareNew, ySquareNew; + + if (movesInPlane || force) + { + 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 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 = 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) + + #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;\ + + 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 * 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; +} + +#endif diff --git a/settings.h b/settings.h index 942ad3a..acbdd9d 100644 --- a/settings.h +++ b/settings.h @@ -21,9 +21,10 @@ Target FPS (frames per second). This sets the game logic FPS and will try to render at the same rate. If such fast rendering can't be achieved, frames will be droped, but the game logic will still be forced to run at this speed, so - the game may actually become slowed down if FPS is set too high. Too high FPS - can also negatively affect game speeds which are computed as integers and - rounding errors can occur soon, so don't set this to extreme values. + the game may actually become slowed down if FPS is set too high. Too high or + too low FPS can also negatively affect game speeds which are computed as + integers and rounding errors can occur soon, so don't set this to extreme + values (try to keep from 20 to 100). */ #define SFG_FPS 60