1
0
Fork 0
mirror of https://git.coom.tech/drummyfish/raycastlib.git synced 2024-11-21 20:29:59 +01:00
raycastlib/raycastlib.h

1672 lines
48 KiB
C
Raw Normal View History

2018-08-23 02:46:40 +02:00
#ifndef RAYCASTLIB_H
#define RAYCASTLIB_H
2018-08-21 13:49:45 +02:00
/**
2018-09-17 17:55:53 +02:00
raycastlib (RCL) - Small C header-only raycasting library for embedded and
low performance computers, such as Arduino. Only uses integer math and stdint
2018-09-01 07:27:17 +02:00
standard library.
2018-09-14 12:25:59 +02:00
Check the defines below to fine-tune accuracy vs performance! Don't forget
to compile with optimizations.
2018-09-17 17:55:53 +02:00
Before including the library define RCL_PIXEL_FUNCTION to the name of the
function (with RCL_PixelFunction signature) that will render your pixels!
2018-08-21 13:49:45 +02:00
2018-09-17 17:55:53 +02:00
- All public (and most private) library identifiers start with RCL_.
2018-08-21 13:49:45 +02:00
- Game field's bottom left corner is at [0,0].
2018-09-12 08:39:52 +02:00
- X axis goes right in the ground plane.
- Y axis goes up in the ground plane.
- Height means the Z (vertical) coordinate.
2018-09-17 17:55:53 +02:00
- 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.
2018-09-17 18:03:48 +02:00
- 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.
2018-09-17 17:55:53 +02:00
author: Miloslav "drummyfish" Ciz
2018-09-18 17:40:32 +02:00
license: CC0 1.0
2018-09-17 17:55:53 +02:00
version: 0.1
2018-09-09 17:20:44 +02:00
*/
2018-08-21 13:49:45 +02:00
2018-09-01 07:27:17 +02:00
#include <stdint.h>
2018-09-17 17:55:53 +02:00
#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 5000000;
2018-09-02 13:58:46 +02:00
#else
2018-09-17 17:55:53 +02:00
#define RCL_UNITS_PER_SQUARE 32
typedef int16_t RCL_Unit;
#define RCL_INFINITY 32000;
#define RCL_USE_DIST_APPROX 2
2018-09-02 13:58:46 +02:00
#endif
2018-08-21 13:49:45 +02:00
2018-09-17 17:55:53 +02:00
#ifndef RCL_COMPUTE_WALL_TEXCOORDS
#define RCL_COMPUTE_WALL_TEXCOORDS 1
2018-09-16 14:24:32 +02:00
#endif
2018-09-18 10:25:14 +02:00
#ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
#define RCL_COMPUTE_FLOOR_TEXCOORDS 0
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_USE_COS_LUT
#define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
2018-09-12 08:39:52 +02:00
0: none (compute)
1: 64 items
2018-09-14 14:40:31 +02:00
2: 128 items */
2018-09-12 08:39:52 +02:00
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_USE_DIST_APPROX
#define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use:
2018-09-14 14:40:31 +02:00
0: none (compute full Euclidean distance)
1: accurate approximation
2: octagonal approximation (LQ) */
2018-09-14 12:25:59 +02:00
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_ROLL_TEXTURE_COORDS
#define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
roll the texture coordinates along (mostly
2018-09-15 11:56:20 +02:00
desired for doors). */
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_VERTICAL_FOV
#define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 2)
2018-09-05 16:26:13 +02:00
#endif
2018-09-03 16:38:49 +02:00
2018-09-17 17:55:53 +02:00
#ifndef RCL_HORIZONTAL_FOV
#define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
2018-09-08 07:23:21 +02:00
#endif
2018-09-17 17:55:53 +02:00
#define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
2018-09-08 07:23:21 +02:00
2018-09-17 17:55:53 +02:00
#ifndef RCL_CAMERA_COLL_RADIUS
#define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
2018-09-08 07:52:55 +02:00
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
#define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
2018-09-08 07:52:55 +02:00
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
#define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
2018-09-08 07:52:55 +02:00
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_CAMERA_COLL_STEP_HEIGHT
#define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
2018-09-08 07:52:55 +02:00
#endif
2018-09-17 17:55:53 +02:00
#ifndef RCL_MIN_TEXTURE_STEP
#define RCL_MIN_TEXTURE_STEP 12 /**< Specifies the minimum step in pixels that
can be used to compute texture coordinates in
a fast way. Smallet step will trigger a more
2018-09-16 17:12:09 +02:00
expensive way of computing texture coords. */
#endif
2018-09-17 17:55:53 +02:00
#define RCL_logV2D(v)\
2018-08-23 03:04:52 +02:00
printf("[%d,%d]\n",v.x,v.y);
2018-09-17 17:55:53 +02:00
#define RCL_logRay(r){\
2018-08-23 03:04:52 +02:00
printf("ray:\n");\
printf(" start: ");\
2018-09-17 17:55:53 +02:00
RCL_logV2D(r.start);\
2018-08-23 03:04:52 +02:00
printf(" dir: ");\
2018-09-17 17:55:53 +02:00
RCL_logV2D(r.direction);}\
2018-08-23 03:04:52 +02:00
2018-09-17 17:55:53 +02:00
#define RCL_logHitResult(h){\
2018-08-23 03:04:52 +02:00
printf("hit:\n");\
printf(" sqaure: ");\
2018-09-17 17:55:53 +02:00
RCL_logV2D(h.square);\
2018-08-23 03:04:52 +02:00
printf(" pos: ");\
2018-09-17 17:55:53 +02:00
RCL_logV2D(h.position);\
2018-08-30 08:51:53 +02:00
printf(" dist: %d\n", h.distance);\
printf(" texcoord: %d\n", h.textureCoord);}\
2018-08-23 03:04:52 +02:00
2018-09-17 17:55:53 +02:00
#define RCL_logPixelInfo(p){\
2018-09-04 13:01:14 +02:00
printf("pixel:\n");\
printf(" position: ");\
2018-09-17 17:55:53 +02:00
RCL_logV2D(p.position);\
2018-09-04 13:01:14 +02:00
printf(" depth: %d\n", p.depth);\
printf(" wall: %d\n", p.isWall);\
printf(" hit: ");\
2018-09-17 17:55:53 +02:00
RCL_logHitResult(p.hit);\
2018-09-04 13:01:14 +02:00
}\
2018-08-21 13:49:45 +02:00
/// Position in 2D space.
typedef struct
{
2018-09-17 17:55:53 +02:00
RCL_Unit x;
RCL_Unit y;
} RCL_Vector2D;
2018-08-21 13:49:45 +02:00
typedef struct
{
2018-09-17 17:55:53 +02:00
RCL_Vector2D start;
RCL_Vector2D direction;
} RCL_Ray;
2018-08-21 13:49:45 +02:00
2018-08-22 08:21:38 +02:00
typedef struct
{
2018-09-17 17:55:53 +02:00
RCL_Unit distance; /**< Euclidean distance to the hit position, or -1
if no collision happened. */
uint8_t direction; ///< Direction of hit.
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;
2018-08-22 08:21:38 +02:00
2018-08-31 11:51:03 +02:00
typedef struct
{
2018-09-17 17:55:53 +02:00
RCL_Vector2D position;
RCL_Unit direction;
RCL_Vector2D resolution;
int16_t shear; /**< Shear offset in pixels (0 => no shear), can simulate
looking up/down. */
RCL_Unit height;
} RCL_Camera;
2018-08-31 13:29:50 +02:00
2018-09-06 14:04:36 +02:00
/**
Holds an information about a single rendered pixel (for a pixel function
2018-09-17 08:26:25 +02:00
that works as a fragment shader).
2018-09-06 14:04:36 +02:00
*/
2018-08-31 13:29:50 +02:00
typedef struct
{
2018-09-17 17:55:53 +02:00
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_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);
2018-09-17 07:15:06 +02:00
2018-08-31 13:29:50 +02:00
typedef struct
{
uint16_t maxHits;
uint16_t maxSteps;
2018-09-17 17:55:53 +02:00
} RCL_RayConstraints;
2018-08-31 13:29:50 +02:00
2018-09-01 07:27:17 +02:00
/**
2018-09-06 14:04:36 +02:00
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.
2018-09-09 17:20:44 +02:00
This function should be as fast as possible as it will typically be called
very often.
2018-09-01 07:27:17 +02:00
*/
2018-09-17 17:55:53 +02:00
typedef RCL_Unit (*RCL_ArrayFunction)(int16_t x, int16_t y);
2018-09-01 07:27:17 +02:00
2018-09-09 17:20:44 +02:00
/**
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.
*/
2018-09-17 17:55:53 +02:00
typedef void (*RCL_PixelFunction)(RCL_PixelInfo *info);
2018-08-31 11:51:03 +02:00
2018-09-01 07:27:17 +02:00
typedef void
2018-09-17 17:55:53 +02:00
(*RCL_ColumnFunction)(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
RCL_Ray ray);
2018-09-01 07:27:17 +02:00
2018-08-23 00:50:19 +02:00
/**
2018-09-06 14:04:36 +02:00
Simple-interface function to cast a single ray.
@return The first collision result.
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc);
2018-08-21 13:49:45 +02:00
2018-09-04 11:42:27 +02:00
/**
2018-09-06 14:04:36 +02:00
Maps a single point in the world to the screen (2D position + depth).
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
RCL_Camera camera);
2018-09-04 11:42:27 +02:00
2018-08-23 02:36:51 +02:00
/**
2018-09-06 14:04:36 +02:00
Casts a single ray and returns a list of collisions.
2018-09-15 11:19:53 +02:00
@param ray ray to be cast
@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
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
uint16_t *hitResultsLen, RCL_RayConstraints constraints);
2018-08-23 02:36:51 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D RCL_angleToDirection(RCL_Unit angle);
2018-09-09 17:20:44 +02:00
/**
Cos function.
2018-09-17 17:55:53 +02:00
@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)
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_cosInt(RCL_Unit input);
2018-09-09 17:20:44 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_sinInt(RCL_Unit input);
2018-08-30 18:31:08 +02:00
2018-09-17 17:55:53 +02:00
/// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
RCL_Vector2D RCL_normalize(RCL_Vector2D v);
2018-08-30 18:31:08 +02:00
/// Computes a cos of an angle between two vectors.
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2);
2018-08-30 18:31:08 +02:00
2018-09-17 17:55:53 +02:00
uint16_t RCL_sqrtInt(RCL_Unit value);
RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2);
RCL_Unit RCL_len(RCL_Vector2D v);
2018-08-23 09:25:34 +02:00
2018-08-30 08:51:53 +02:00
/**
2018-09-17 17:55:53 +02:00
Converts an angle in whole degrees to an angle in RCL_Units that this library
2018-09-06 14:04:36 +02:00
uses.
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees);
2018-08-30 08:51:53 +02:00
///< Computes the change in size of an object due to perspective.
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_perspectiveScale(RCL_Unit originalSize, RCL_Unit distance);
2018-08-30 08:51:53 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_perspectiveScaleInverse(RCL_Unit originalSize,
RCL_Unit scaledSize);
2018-09-17 15:05:38 +02:00
2018-08-30 08:51:53 +02:00
/**
2018-09-06 14:04:36 +02:00
Casts rays for given camera view and for each hit calls a user provided
function.
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
RCL_RayConstraints constraints);
2018-08-30 08:51:53 +02:00
2018-09-05 12:20:46 +02:00
/**
2018-09-06 14:04:36 +02:00
Using provided functions, renders a complete complex camera view.
2018-09-05 12:20:46 +02:00
2018-09-12 17:37:15 +02:00
This function should render each screen pixel exactly once.
2018-09-17 16:45:02 +02:00
function rendering summary:
- performance: slower
- accuracy: higher
- wall textures: yes
- different wall heights: yes
2018-09-18 14:49:02 +02:00
- floor/ceiling textures: no
2018-09-17 16:45:02 +02:00
- floor geometry: yes, multilevel
- ceiling geometry: yes (optional), multilevel
- rolling door: no
2018-09-18 14:49:02 +02:00
- camera shearing: yes
2018-09-17 18:03:48 +02:00
- rendering order: left-to-right, not specifically ordered vertically
2018-09-17 16:45:02 +02:00
2018-09-06 14:04:36 +02:00
@param cam camera whose view to render
2018-09-17 17:55:53 +02:00
@param floorHeightFunc function that returns floor height (in RCL_Units)
2018-09-06 14:04:36 +02:00
@param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
2018-09-09 17:20:44 +02:00
0 (no ceiling will be rendered)
2018-09-06 14:04:36 +02:00
@param typeFunction function that says a type of square (e.g. its texture
2018-09-05 16:26:13 +02:00
index), can be 0 (no type in hit result)
2018-09-06 14:04:36 +02:00
@param pixelFunc callback function to draw a single pixel on screen
@param constraints constraints for each cast ray
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
void RCL_render(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
RCL_RayConstraints constraints);
2018-08-31 18:26:51 +02:00
2018-09-11 09:07:24 +02:00
/**
Renders given camera view, with help of provided functions. This function is
2018-09-17 17:55:53 +02:00
simpler and faster than RCL_render(...) and is meant to be rendering scenes
with simple 1-intersection raycasting. The RCL_render(...) function can give
more accurate results than this function, so it's to be considered even for
simple scenes.
2018-09-12 17:37:15 +02:00
2018-09-17 16:45:02 +02:00
function rendering summary:
- performance: faster
- accuracy: lower
- wall textures: yes
- different wall heights: yes
2018-09-18 14:49:02 +02:00
- floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
2018-09-17 16:45:02 +02:00
- floor geometry: no (just flat floor, with depth information)
- ceiling geometry: no (just flat ceiling, with depth information)
- rolling door: yes
2018-09-18 14:49:02 +02:00
- camera shearing: no
2018-09-17 18:03:48 +02:00
- rendering order: left-to-right, top-to-bottom
2018-09-17 16:45:02 +02:00
2018-09-14 19:42:59 +02:00
Additionally this function supports rendering rolling doors.
2018-09-12 17:37:15 +02:00
This function should render each screen pixel exactly once.
2018-09-14 19:42:59 +02:00
2018-09-17 17:55:53 +02:00
@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)
2018-09-11 09:07:24 +02:00
*/
2018-09-17 17:55:53 +02:00
void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
RCL_RayConstraints constraints);
2018-09-11 08:09:34 +02:00
2018-09-09 17:20:44 +02:00
/**
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)
2018-09-12 07:55:29 +02:00
@param force if true, forces to recompute collision even if position doesn't
change
2018-09-09 17:20:44 +02:00
*/
2018-09-17 17:55:53 +02:00
void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force);
2018-09-06 17:41:09 +02:00
2018-09-17 17:55:53 +02:00
void RCL_initCamera(RCL_Camera *camera);
void RCL_initRayConstraints(RCL_RayConstraints *constraints);
2018-09-15 14:35:21 +02:00
2018-08-21 13:49:45 +02:00
//=============================================================================
// privates
2018-09-14 20:17:15 +02:00
// global helper variables, for precomputing stuff etc.
2018-09-17 17:55:53 +02:00
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
2018-09-18 10:25:14 +02:00
RCL_Unit *_RCL_floorPixelDistances = 0;
2018-09-14 20:17:15 +02:00
2018-08-31 15:38:02 +02:00
#ifdef RAYCASTLIB_PROFILE
// function call counters for profiling
2018-09-17 17:55:53 +02:00
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_pointIsLeftOfRCL_Ray = 0;
uint32_t profile_RCL_castRaySquare = 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_perspectiveScale = 0;
uint32_t profile_RCL_wrap = 0;
uint32_t profile_RCL_divRoundDown = 0;
#define RCL_profileCall(c) profile_##c += 1
2018-08-31 15:38:02 +02:00
#define printProfile() {\
printf("profile:\n");\
2018-09-17 17:55:53 +02:00
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_pointIsLeftOfRCL_Ray: %d\n",profile_RCL_pointIsLeftOfRCL_Ray);\
printf(" RCL_castRaySquare: %d\n",profile_RCL_castRaySquare);\
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_perspectiveScale: %d\n",profile_RCL_perspectiveScale);\
printf(" RCL_wrap: %d\n",profile_RCL_wrap);\
printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); }
2018-08-31 15:38:02 +02:00
#else
2018-09-17 17:55:53 +02:00
#define RCL_profileCall(c)
2018-08-31 15:38:02 +02:00
#endif
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax)
2018-08-23 03:04:52 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_clamp);
2018-08-31 15:38:02 +02:00
2018-09-04 19:32:47 +02:00
if (value >= valueMin)
{
if (value <= valueMax)
return value;
else
return valueMax;
}
else
2018-08-23 03:04:52 +02:00
return valueMin;
}
2018-09-17 17:55:53 +02:00
static inline RCL_Unit RCL_absVal(RCL_Unit value)
2018-09-03 08:14:36 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_absVal);
2018-09-04 18:55:18 +02:00
2018-09-13 11:02:01 +02:00
return value >= 0 ? value : -1 * value;
2018-09-03 08:14:36 +02:00
}
2018-09-03 16:55:30 +02:00
/// Like mod, but behaves differently for negative values.
2018-09-17 17:55:53 +02:00
static inline RCL_Unit RCL_wrap(RCL_Unit value, RCL_Unit mod)
2018-09-03 16:55:30 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_wrap);
2018-09-04 19:32:47 +02:00
return value >= 0 ? (value % mod) : (mod + (value % mod) - 1);
2018-09-03 16:55:30 +02:00
}
2018-09-03 09:52:50 +02:00
/// Performs division, rounding down, NOT towards zero.
2018-09-17 17:55:53 +02:00
static inline RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor)
2018-09-03 09:52:50 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_divRoundDown);
2018-09-04 19:32:47 +02:00
return value / divisor - ((value >= 0) ? 0 : 1);
2018-09-03 09:52:50 +02:00
}
2018-08-23 09:25:34 +02:00
// Bhaskara's cosine approximation formula
2018-09-17 17:55:53 +02:00
#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)))
2018-08-23 09:25:34 +02:00
2018-09-17 17:55:53 +02:00
#if RCL_USE_COS_LUT == 1
2018-09-15 17:53:16 +02:00
2018-09-17 17:55:53 +02:00
#ifdef RCL_RAYCAST_TINY
const RCL_Unit cosLUT[64] =
2018-09-15 17:53:16 +02:00
{
16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
};
#else
2018-09-17 17:55:53 +02:00
const RCL_Unit cosLUT[64] =
2018-09-15 17:53:16 +02:00
{
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
2018-09-17 17:55:53 +02:00
#elif RCL_USE_COS_LUT == 2
const RCL_Unit cosLUT[128] =
2018-09-12 08:39:52 +02:00
{
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
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_cosInt(RCL_Unit input)
2018-08-23 09:25:34 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_cosInt);
2018-08-31 15:38:02 +02:00
2018-09-03 09:52:50 +02:00
// TODO: could be optimized with LUT
2018-09-17 17:55:53 +02:00
input = RCL_wrap(input,RCL_UNITS_PER_SQUARE);
2018-08-23 09:25:34 +02:00
2018-09-17 17:55:53 +02:00
#if RCL_USE_COS_LUT == 1
2018-09-15 17:53:16 +02:00
2018-09-17 17:55:53 +02:00
#ifdef RCL_RAYCAST_TINY
2018-09-15 17:53:16 +02:00
return cosLUT[input];
#else
return cosLUT[input / 16];
#endif
2018-09-17 17:55:53 +02:00
#elif RCL_USE_COS_LUT == 2
2018-09-12 08:39:52 +02:00
return cosLUT[input / 8];
#else
2018-09-17 17:55:53 +02:00
if (input < RCL_UNITS_PER_SQUARE / 4)
2018-08-23 09:25:34 +02:00
return trigHelper(input);
2018-09-17 17:55:53 +02:00
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);
2018-08-23 09:25:34 +02:00
else
2018-09-17 17:55:53 +02:00
return trigHelper(RCL_UNITS_PER_SQUARE - input);
2018-09-12 08:39:52 +02:00
#endif
2018-08-23 09:25:34 +02:00
}
#undef trigHelper
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_sinInt(RCL_Unit input)
2018-08-23 09:25:34 +02:00
{
2018-09-17 17:55:53 +02:00
return RCL_cosInt(input - RCL_UNITS_PER_SQUARE / 4);
2018-08-23 09:35:10 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_Vector2D RCL_angleToDirection(RCL_Unit angle)
2018-08-23 09:35:10 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_angleToDirection);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D result;
2018-08-23 09:35:10 +02:00
2018-09-17 17:55:53 +02:00
result.x = RCL_cosInt(angle);
result.y = -1 * RCL_sinInt(angle);
2018-08-23 09:35:10 +02:00
return result;
2018-08-23 09:25:34 +02:00
}
2018-09-17 17:55:53 +02:00
uint16_t RCL_sqrtInt(RCL_Unit value)
2018-08-23 00:50:19 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_sqrtInt);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
#ifdef RCL_RAYCAST_TINY
2018-09-14 15:00:31 +02:00
uint16_t result = 0;
uint16_t a = value;
uint16_t b = 1u << 14;
2018-09-02 13:58:46 +02:00
#else
2018-09-14 15:00:31 +02:00
uint32_t result = 0;
uint32_t a = value;
2018-09-13 16:13:59 +02:00
uint32_t b = 1u << 30;
2018-09-02 13:58:46 +02:00
#endif
2018-08-23 00:50:19 +02:00
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;
}
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2)
2018-08-23 00:50:19 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_dist);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit dx = p2.x - p1.x;
RCL_Unit dy = p2.y - p1.y;
2018-08-30 11:15:52 +02:00
2018-09-17 17:55:53 +02:00
#if RCL_USE_DIST_APPROX == 2
2018-09-14 14:40:31 +02:00
// octagonal approximation
2018-09-17 17:55:53 +02:00
dx = RCL_absVal(dx);
dy = RCL_absVal(dy);
2018-09-14 14:40:31 +02:00
return dy > dx ? dx / 2 + dy : dy / 2 + dx;
2018-09-17 17:55:53 +02:00
#elif RCL_USE_DIST_APPROX == 1
2018-09-14 14:40:31 +02:00
// more accurate approximation
2018-09-14 12:25:59 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit a, b, result;
2018-09-14 12:25:59 +02:00
dx = dx < 0 ? -1 * dx : dx;
dy = dy < 0 ? -1 * dy : dy;
if (dx < dy)
{
a = dy;
b = dx;
}
else
{
a = dx;
b = dy;
}
2018-09-14 15:18:34 +02:00
result = a + (44 * b) / 102;
2018-09-13 17:36:42 +02:00
2018-09-14 12:25:59 +02:00
if (a < (b << 4))
2018-09-14 15:18:34 +02:00
result -= (5 * a) / 128;
return result;
2018-09-13 17:36:42 +02:00
#else
2018-08-30 11:15:52 +02:00
dx = dx * dx;
dy = dy * dy;
2018-09-17 17:55:53 +02:00
return RCL_sqrtInt((RCL_Unit) (dx + dy));
2018-09-13 17:36:42 +02:00
#endif
2018-08-23 00:50:19 +02:00
}
2018-08-21 13:49:45 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_len(RCL_Vector2D v)
2018-08-30 14:44:14 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_len);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D zero;
2018-09-14 15:18:34 +02:00
zero.x = 0;
zero.y = 0;
2018-08-30 14:44:14 +02:00
2018-09-17 17:55:53 +02:00
return RCL_dist(zero,v);
2018-08-30 14:44:14 +02:00
}
2018-09-17 17:55:53 +02:00
static inline int8_t RCL_pointIsLeftOfRCL_Ray(RCL_Vector2D point, RCL_Ray ray)
2018-08-21 13:49:45 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_pointIsLeftOfRCL_Ray);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit dX = point.x - ray.start.x;
RCL_Unit dY = point.y - ray.start.y;
2018-08-21 13:49:45 +02:00
return (ray.direction.x * dY - ray.direction.y * dX) > 0;
// ^ Z component of cross-product
}
/**
2018-08-22 08:21:38 +02:00
Casts a ray within a single square, to collide with the square borders.
2018-09-06 14:04:36 +02:00
*/
2018-09-17 17:55:53 +02:00
void RCL_castRaySquare(RCL_Ray *localRay, RCL_Vector2D *nextCellOff,
RCL_Vector2D *collOff)
2018-08-21 13:49:45 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_castRaySquare);
2018-08-31 15:38:02 +02:00
2018-08-31 13:29:50 +02:00
nextCellOff->x = 0;
nextCellOff->y = 0;
2018-08-21 13:49:45 +02:00
2018-09-17 17:55:53 +02:00
RCL_Ray criticalLine;
2018-09-12 08:19:05 +02:00
criticalLine.start = localRay->start;
criticalLine.direction = localRay->direction;
2018-08-21 13:49:45 +02:00
#define helper(c1,c2,n)\
{\
2018-08-31 13:29:50 +02:00
nextCellOff->c1 = n;\
2018-09-12 08:19:05 +02:00
collOff->c1 = criticalLine.start.c1 - localRay->start.c1;\
2018-08-31 13:29:50 +02:00
collOff->c2 = \
2018-09-17 17:55:53 +02:00
(((RCL_Unit) collOff->c1) * localRay->direction.c2) /\
2018-09-12 08:19:05 +02:00
((localRay->direction.c1 == 0) ? 1 : localRay->direction.c1);\
2018-08-21 13:49:45 +02:00
}
#define helper2(n1,n2,c)\
2018-09-17 17:55:53 +02:00
if (RCL_pointIsLeftOfRCL_Ray(localRay->start,criticalLine) == c)\
2018-08-21 13:49:45 +02:00
helper(y,x,n1)\
else\
helper(x,y,n2)
2018-09-12 08:19:05 +02:00
if (localRay->direction.x > 0)
2018-08-21 13:49:45 +02:00
{
2018-09-17 17:55:53 +02:00
criticalLine.start.x = RCL_UNITS_PER_SQUARE - 1;
2018-08-21 13:49:45 +02:00
2018-09-12 08:19:05 +02:00
if (localRay->direction.y > 0)
2018-08-21 13:49:45 +02:00
{
// top right
2018-09-17 17:55:53 +02:00
criticalLine.start.y = RCL_UNITS_PER_SQUARE - 1;
2018-08-21 13:49:45 +02:00
helper2(1,1,1)
}
else
{
// bottom right
2018-08-31 10:59:32 +02:00
criticalLine.start.y = 0;
2018-08-21 13:49:45 +02:00
helper2(-1,1,0)
}
}
else
{
2018-08-31 10:59:32 +02:00
criticalLine.start.x = 0;
2018-08-21 13:49:45 +02:00
2018-09-12 08:19:05 +02:00
if (localRay->direction.y > 0)
2018-08-21 13:49:45 +02:00
{
// top left
2018-09-17 17:55:53 +02:00
criticalLine.start.y = RCL_UNITS_PER_SQUARE - 1;
2018-08-21 13:49:45 +02:00
helper2(1,-1,0)
}
else
{
// bottom left
2018-08-31 10:59:32 +02:00
criticalLine.start.y = 0;
2018-08-21 13:49:45 +02:00
helper2(-1,-1,1)
}
}
#undef helper2
#undef helper
2018-08-31 10:59:32 +02:00
2018-08-31 13:29:50 +02:00
collOff->x += nextCellOff->x;
collOff->y += nextCellOff->y;
2018-08-21 13:49:45 +02:00
}
2018-09-17 17:55:53 +02:00
void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
uint16_t *hitResultsLen, RCL_RayConstraints constraints)
2018-08-22 08:21:38 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_castRayMultiHit);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D initialPos = ray.start;
RCL_Vector2D currentPos = ray.start;
2018-08-23 02:36:51 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D currentSquare;
2018-08-23 00:50:19 +02:00
2018-09-17 17:55:53 +02:00
currentSquare.x = RCL_divRoundDown(ray.start.x, RCL_UNITS_PER_SQUARE);
currentSquare.y = RCL_divRoundDown(ray.start.y,RCL_UNITS_PER_SQUARE);
2018-09-02 21:43:44 +02:00
2018-08-23 02:36:51 +02:00
*hitResultsLen = 0;
2018-09-17 17:55:53 +02:00
RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
2018-08-23 00:50:19 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D no, co; // next cell offset, collision offset
2018-08-30 14:44:14 +02:00
2018-09-17 17:55:53 +02:00
no.x = 0; // just to supress a warning
2018-08-31 13:29:50 +02:00
no.y = 0;
2018-09-04 18:55:18 +02:00
co.x = 0;
co.y = 0;
2018-08-31 13:29:50 +02:00
2018-08-31 16:46:55 +02:00
for (uint16_t i = 0; i < constraints.maxSteps; ++i)
2018-08-22 08:21:38 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y);
2018-08-23 02:36:51 +02:00
if (currentType != squareType)
2018-08-23 00:50:19 +02:00
{
2018-08-23 02:36:51 +02:00
// collision
2018-09-17 17:55:53 +02:00
RCL_HitResult h;
2018-08-23 00:50:19 +02:00
2018-09-15 11:19:53 +02:00
h.arrayValue = currentType;
2018-09-14 20:17:15 +02:00
h.doorRoll = 0;
2018-08-23 02:36:51 +02:00
h.position = currentPos;
h.square = currentSquare;
2018-09-17 17:55:53 +02:00
h.distance = RCL_dist(initialPos,currentPos);
2018-08-23 02:36:51 +02:00
2018-09-05 16:26:13 +02:00
if (typeFunc != 0)
h.type = typeFunc(currentSquare.x,currentSquare.y);
2018-08-30 14:44:14 +02:00
if (no.y > 0)
2018-09-03 14:23:11 +02:00
{
2018-08-30 14:44:14 +02:00
h.direction = 0;
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
h.textureCoord = RCL_wrap(currentPos.x,RCL_UNITS_PER_SQUARE);
2018-09-16 14:24:32 +02:00
#endif
2018-09-03 14:23:11 +02:00
}
2018-08-30 14:44:14 +02:00
else if (no.x > 0)
2018-09-03 14:23:11 +02:00
{
2018-08-30 14:44:14 +02:00
h.direction = 1;
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
2018-09-16 14:24:32 +02:00
h.textureCoord =
2018-09-17 17:55:53 +02:00
RCL_wrap(RCL_UNITS_PER_SQUARE - currentPos.y,RCL_UNITS_PER_SQUARE);
2018-09-16 14:24:32 +02:00
#endif
2018-09-03 14:23:11 +02:00
}
2018-08-30 14:44:14 +02:00
else if (no.y < 0)
2018-09-03 14:23:11 +02:00
{
2018-08-30 14:44:14 +02:00
h.direction = 2;
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
2018-09-16 14:24:32 +02:00
h.textureCoord =
2018-09-17 17:55:53 +02:00
RCL_wrap(RCL_UNITS_PER_SQUARE - currentPos.x,RCL_UNITS_PER_SQUARE);
2018-09-16 14:24:32 +02:00
#endif
2018-09-03 14:23:11 +02:00
}
2018-08-30 14:44:14 +02:00
else
2018-09-03 14:23:11 +02:00
{
2018-08-30 14:44:14 +02:00
h.direction = 3;
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
h.textureCoord = RCL_wrap(currentPos.y,RCL_UNITS_PER_SQUARE);
2018-09-16 14:24:32 +02:00
#endif
2018-09-03 14:23:11 +02:00
}
2018-08-30 14:44:14 +02:00
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
if (_RCL_rollFunction != 0)
h.doorRoll = _RCL_rollFunction(currentSquare.x,currentSquare.y);
2018-09-16 14:24:32 +02:00
#endif
2018-09-14 20:17:15 +02:00
2018-08-23 02:36:51 +02:00
hitResults[*hitResultsLen] = h;
*hitResultsLen += 1;
squareType = currentType;
2018-08-31 13:29:50 +02:00
if (*hitResultsLen >= constraints.maxHits)
2018-08-23 02:36:51 +02:00
break;
2018-08-23 00:50:19 +02:00
}
2018-09-17 17:55:53 +02:00
ray.start.x = RCL_wrap(currentPos.x,RCL_UNITS_PER_SQUARE);
ray.start.y = RCL_wrap(currentPos.y,RCL_UNITS_PER_SQUARE);
2018-08-22 08:21:38 +02:00
2018-09-17 17:55:53 +02:00
RCL_castRaySquare(&ray,&no,&co);
2018-08-22 08:21:38 +02:00
2018-08-23 02:36:51 +02:00
currentSquare.x += no.x;
currentSquare.y += no.y;
2018-08-22 08:21:38 +02:00
2018-08-31 11:20:36 +02:00
// offset into the next cell
2018-08-23 02:36:51 +02:00
currentPos.x += co.x;
currentPos.y += co.y;
2018-08-22 08:21:38 +02:00
}
2018-08-23 02:36:51 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc)
2018-08-23 02:36:51 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_castRay);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_HitResult result;
uint16_t RCL_len;
RCL_RayConstraints c;
2018-08-31 13:29:50 +02:00
c.maxSteps = 1000;
c.maxHits = 1;
2018-08-23 02:36:51 +02:00
2018-09-17 17:55:53 +02:00
RCL_castRayMultiHit(ray,arrayFunc,0,&result,&RCL_len,c);
2018-08-23 02:36:51 +02:00
2018-09-17 17:55:53 +02:00
if (RCL_len == 0)
2018-08-23 02:36:51 +02:00
result.distance = -1;
2018-08-22 08:21:38 +02:00
return result;
}
2018-09-17 17:55:53 +02:00
void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
RCL_RayConstraints constraints)
2018-08-30 08:51:53 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_Vector2D dir1 =
RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF);
2018-08-30 08:51:53 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D dir2 =
RCL_angleToDirection(cam.direction + RCL_HORIZONTAL_FOV_HALF);
2018-08-30 08:51:53 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit dX = dir2.x - dir1.x;
RCL_Unit dY = dir2.y - dir1.y;
RCL_HitResult hits[constraints.maxHits];
2018-08-31 11:51:03 +02:00
uint16_t hitCount;
2018-08-30 08:51:53 +02:00
2018-09-17 17:55:53 +02:00
RCL_Ray r;
2018-08-31 13:29:50 +02:00
r.start = cam.position;
2018-08-30 08:51:53 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit currentDX = 0;
RCL_Unit currentDY = 0;
2018-09-15 17:56:59 +02:00
2018-09-15 17:53:16 +02:00
for (int16_t i = 0; i < cam.resolution.x; ++i)
2018-08-30 08:51:53 +02:00
{
2018-09-15 17:56:59 +02:00
r.direction.x = dir1.x + currentDX / cam.resolution.x;
r.direction.y = dir1.y + currentDY / cam.resolution.x;
2018-08-30 08:51:53 +02:00
2018-09-17 17:55:53 +02:00
RCL_castRayMultiHit(r,arrayFunc,typeFunction,hits,&hitCount,constraints);
2018-08-30 08:51:53 +02:00
2018-08-31 18:11:32 +02:00
columnFunc(hits,hitCount,i,r);
2018-09-15 17:56:59 +02:00
currentDX += dX;
currentDY += dY;
2018-08-30 08:51:53 +02:00
}
}
2018-09-06 14:04:36 +02:00
/**
Helper function that determines intersection with both ceiling and floor.
*/
2018-09-17 17:55:53 +02:00
RCL_Unit _floorCeilFunction(int16_t x, int16_t y)
2018-09-05 11:35:52 +02:00
{
2018-09-17 17:55:53 +02:00
// TODO: adjust also for RCL_RAYCAST_TINY
2018-09-05 11:35:52 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit f = _RCL_floorFunction(x,y);
2018-09-08 18:39:47 +02:00
2018-09-17 17:55:53 +02:00
if (_RCL_ceilFunction == 0)
2018-09-08 18:39:47 +02:00
return f;
2018-09-17 17:55:53 +02:00
RCL_Unit c = _RCL_ceilFunction(x,y);
2018-09-05 11:35:52 +02:00
2018-09-17 17:55:53 +02:00
#ifndef RCL_RAYCAST_TINY
2018-09-05 11:35:52 +02:00
return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff);
2018-09-13 16:13:59 +02:00
#else
return ((f & 0x00ff) << 8) | (c & 0x00ff);
#endif
2018-09-05 11:35:52 +02:00
}
2018-08-31 18:26:51 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit _floorHeightNotZeroFunction(int16_t x, int16_t y)
2018-09-14 17:46:31 +02:00
{
2018-09-17 17:55:53 +02:00
return _RCL_floorFunction(x,y) == 0 ? 0 :
2018-09-14 19:42:59 +02:00
(x & 0x00FF) | ((y & 0x00FF) << 8);
// ^ this makes collisions between all squares - needed for rolling doors
2018-09-14 17:46:31 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_adjustDistance(RCL_Unit distance, RCL_Camera *camera,
RCL_Ray *ray)
2018-09-11 08:09:34 +02:00
{
/* 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). */
2018-09-17 17:55:53 +02:00
RCL_Unit result =
2018-09-11 08:09:34 +02:00
(distance *
2018-09-17 17:55:53 +02:00
RCL_vectorsAngleCos(RCL_angleToDirection(camera->direction),
ray->direction)) / RCL_UNITS_PER_SQUARE;
2018-09-11 08:09:34 +02:00
return result == 0 ? 1 : result;
// ^ prevent division by zero
}
2018-09-17 17:55:53 +02:00
void _columnFunction(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
RCL_Ray ray)
2018-08-31 18:26:51 +02:00
{
2018-09-13 11:16:28 +02:00
// last written Y position, can never go backwards
2018-09-17 17:55:53 +02:00
RCL_Unit fPosY = _RCL_camera.resolution.y;
RCL_Unit cPosY = -1;
2018-09-13 10:09:21 +02:00
2018-09-13 11:16:28 +02:00
// world coordinates
2018-09-17 17:55:53 +02:00
RCL_Unit fZ1World = _RCL_startFloorHeight;
RCL_Unit cZ1World = _RCL_startCeil_Height;
2018-09-01 09:46:19 +02:00
2018-09-17 17:55:53 +02:00
RCL_PixelInfo p;
2018-09-01 12:04:29 +02:00
p.position.x = x;
2018-09-16 17:12:09 +02:00
p.texCoords.x = 0;
p.texCoords.y = 0;
2018-09-01 12:04:29 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit i;
2018-09-03 09:39:46 +02:00
2018-09-08 18:45:57 +02:00
// we'll be simulatenously drawing the floor and the ceiling now
2018-09-17 17:55:53 +02:00
for (RCL_Unit j = 0; j <= hitCount; ++j)
2018-09-13 11:16:28 +02:00
{ // ^ = add extra iteration for horizon plane
int8_t drawingHorizon = j == hitCount;
2018-09-05 19:49:39 +02:00
2018-09-17 17:55:53 +02:00
RCL_HitResult hit;
RCL_Unit distance;
2018-09-01 12:04:29 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit fWallHeight = 0, cWallHeight = 0;
RCL_Unit fZ2World = 0, cZ2World = 0;
RCL_Unit fZ1Screen = 0, cZ1Screen = 0;
RCL_Unit fZ2Screen = 0, cZ2Screen = 0;
2018-09-12 13:45:08 +02:00
2018-09-13 11:16:28 +02:00
if (!drawingHorizon)
2018-09-08 18:39:47 +02:00
{
2018-09-13 11:16:28 +02:00
hit = hits[j];
2018-09-17 17:55:53 +02:00
distance = RCL_adjustDistance(hit.distance,&_RCL_camera,&ray);
fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y);
fZ2World = fWallHeight - _RCL_camera.height;
fZ1Screen = _RCL_middleRow - RCL_perspectiveScale(
(fZ1World * _RCL_camera.resolution.y) /
RCL_UNITS_PER_SQUARE,distance);
fZ2Screen = _RCL_middleRow - RCL_perspectiveScale(
(fZ2World * _RCL_camera.resolution.y) /
RCL_UNITS_PER_SQUARE,distance);
if (_RCL_ceilFunction != 0)
2018-09-13 11:16:28 +02:00
{
2018-09-17 17:55:53 +02:00
cWallHeight = _RCL_ceilFunction(hit.square.x,hit.square.y);
cZ2World = cWallHeight - _RCL_camera.height;
cZ1Screen = _RCL_middleRow - RCL_perspectiveScale(
(cZ1World * _RCL_camera.resolution.y) /
RCL_UNITS_PER_SQUARE,distance);
cZ2Screen = _RCL_middleRow - RCL_perspectiveScale(
(cZ2World * _RCL_camera.resolution.y) /
RCL_UNITS_PER_SQUARE,distance);
2018-09-13 11:16:28 +02:00
}
}
else
{
2018-09-17 17:55:53 +02:00
fZ1Screen = _RCL_middleRow;
cZ1Screen = _RCL_middleRow + 1;
2018-09-12 13:45:08 +02:00
}
2018-09-02 23:26:02 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit limit;
2018-09-02 23:26:02 +02:00
2018-09-13 11:16:28 +02:00
#define VERTICAL_DEPTH_MULTIPLY 2
#define drawHorizontal(pref,l1,l2,comp,inc)\
2018-09-17 17:55:53 +02:00
p.depth += RCL_absVal(pref##Z1World) * VERTICAL_DEPTH_MULTIPLY;\
limit = RCL_clamp(pref##Z1Screen,l1,l2);\
2018-09-13 11:16:28 +02:00
for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\
{\
p.position.y = i;\
2018-09-17 17:55:53 +02:00
p.depth += _RCL_horizontalDepthStep;\
RCL_PIXEL_FUNCTION(&p);\
2018-09-13 11:16:28 +02:00
}\
if (pref##PosY comp limit)\
pref##PosY = limit;
2018-09-01 09:55:35 +02:00
2018-09-01 08:17:01 +02:00
p.isWall = 0;
2018-09-13 11:18:31 +02:00
p.isHorizon = drawingHorizon;
2018-08-31 18:26:51 +02:00
2018-09-13 11:16:28 +02:00
// draw floor until wall
p.isFloor = 1;
2018-09-17 17:55:53 +02:00
p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep;
drawHorizontal(f,cPosY + 1,_RCL_camera.resolution.y,>,-)
2018-09-13 11:16:28 +02:00
// ^ purposfully allow outside screen bounds here
2018-09-01 09:55:35 +02:00
2018-09-17 17:55:53 +02:00
if (_RCL_ceilFunction != 0 || drawingHorizon)
2018-08-31 19:13:15 +02:00
{
2018-09-13 11:16:28 +02:00
// draw ceiling until wall
p.isFloor = 0;
2018-09-17 17:55:53 +02:00
p.depth = (cPosY - _RCL_cHorizontalDepthStart) *
_RCL_horizontalDepthStep;
2018-09-13 11:16:28 +02:00
drawHorizontal(c,-1,fPosY - 1,<,+)
// ^ purposfully allow outside screen bounds here
2018-09-01 08:17:01 +02:00
}
2018-09-13 11:16:28 +02:00
#undef drawHorizontal
#undef VERTICAL_DEPTH_MULTIPLY
2018-09-02 23:26:02 +02:00
2018-09-13 11:16:28 +02:00
if (!drawingHorizon) // don't draw walls for horizon plane
2018-09-03 08:14:36 +02:00
{
2018-09-13 11:16:28 +02:00
#define drawVertical(pref,l1,l2,comp,inc)\
{\
2018-09-17 17:55:53 +02:00
limit = RCL_clamp(pref##Z2Screen,l1,l2);\
RCL_Unit wallLength = pref##Z2Screen - pref##Z1Screen - 1;\
2018-09-13 11:16:28 +02:00
wallLength = wallLength != 0 ? wallLength : 1;\
2018-09-17 17:55:53 +02:00
RCL_Unit wallPosition =\
RCL_absVal(pref##Z1Screen - pref##PosY) inc (-1);\
RCL_Unit coordStep = RCL_COMPUTE_WALL_TEXCOORDS ? \
RCL_UNITS_PER_SQUARE / wallLength : 1;\
p.texCoords.y = RCL_COMPUTE_WALL_TEXCOORDS ?\
2018-09-16 17:23:57 +02:00
wallPosition * coordStep : 0;\
2018-09-17 17:55:53 +02:00
if (coordStep < RCL_MIN_TEXTURE_STEP) /* two-version loop */ \
2018-09-16 17:21:34 +02:00
for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\
{ /* more expensive texture coord computing */\
p.position.y = i;\
p.hit = hit;\
2018-09-17 17:55:53 +02:00
if (RCL_COMPUTE_WALL_TEXCOORDS == 1)\
2018-09-16 17:21:34 +02:00
{\
p.texCoords.x = hit.textureCoord;\
2018-09-17 17:55:53 +02:00
p.texCoords.y = (wallPosition * RCL_UNITS_PER_SQUARE)\
/ wallLength;\
2018-09-16 17:21:34 +02:00
}\
wallPosition++;\
2018-09-17 17:55:53 +02:00
RCL_PIXEL_FUNCTION(&p);\
2018-09-16 17:21:34 +02:00
}\
else\
for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\
{ /* cheaper texture coord computing */\
p.position.y = i;\
p.hit = hit;\
2018-09-17 17:55:53 +02:00
if (RCL_COMPUTE_WALL_TEXCOORDS == 1)\
2018-09-16 17:21:34 +02:00
{\
p.texCoords.x = hit.textureCoord;\
p.texCoords.y += coordStep;\
}\
2018-09-17 17:55:53 +02:00
RCL_PIXEL_FUNCTION(&p);\
2018-09-16 17:12:09 +02:00
}\
2018-09-13 11:16:28 +02:00
if (pref##PosY comp limit)\
pref##PosY = limit;\
pref##Z1World = pref##Z2World; /* for the next iteration */\
}
p.isWall = 1;
p.depth = distance;
p.isFloor = 1;
2018-09-05 07:07:12 +02:00
2018-09-13 11:16:28 +02:00
// draw floor wall
2018-09-05 11:44:15 +02:00
2018-09-13 11:16:28 +02:00
if (fPosY > 0) // still pixels left?
2018-09-12 13:45:08 +02:00
{
2018-09-13 11:16:28 +02:00
p.isFloor = 1;
2018-09-17 17:55:53 +02:00
drawVertical(f,cPosY + 1,_RCL_camera.resolution.y,>,-)
2018-09-13 11:16:28 +02:00
} // ^ purposfully allow outside screen bounds here
2018-09-12 13:45:08 +02:00
2018-09-13 11:16:28 +02:00
// draw ceiling wall
2018-09-05 11:44:15 +02:00
2018-09-17 17:55:53 +02:00
if (_RCL_ceilFunction != 0 && cPosY < _RCL_camResYLimit) // pixels left?
2018-09-05 12:20:46 +02:00
{
2018-09-13 11:16:28 +02:00
p.isFloor = 0;
drawVertical(c,-1,fPosY - 1,<,+)
} // ^ puposfully allow outside screen bounds here
2018-09-05 12:20:46 +02:00
2018-09-13 11:16:28 +02:00
#undef drawVertical
2018-09-03 08:14:36 +02:00
}
2018-09-01 12:04:29 +02:00
}
2018-08-31 18:26:51 +02:00
}
2018-09-17 17:55:53 +02:00
void _columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
RCL_Ray ray)
2018-09-11 08:09:34 +02:00
{
int16_t y = 0;
2018-09-14 17:46:31 +02:00
int16_t wallHeightScreen = 0;
2018-09-11 08:24:48 +02:00
int16_t coordHelper = 0;
2018-09-17 17:55:53 +02:00
int16_t wallStart = _RCL_middleRow;
int16_t wallEnd = _RCL_middleRow;
2018-09-11 08:24:48 +02:00
int16_t heightOffset = 0;
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_dist = 1;
2018-09-11 08:09:34 +02:00
2018-09-17 17:55:53 +02:00
RCL_PixelInfo p;
2018-09-11 08:09:34 +02:00
p.position.x = x;
if (hitCount > 0)
{
2018-09-17 17:55:53 +02:00
RCL_HitResult hit = hits[0];
2018-09-14 19:42:59 +02:00
uint8_t goOn = 1;
2018-09-17 17:55:53 +02:00
if (_RCL_rollFunction != 0 && RCL_COMPUTE_WALL_TEXCOORDS == 1)
2018-09-14 19:42:59 +02:00
{
2018-09-15 11:19:53 +02:00
if (hit.arrayValue == 0)
{
// standing inside door square, looking out => move to the next hit
2018-09-14 19:42:59 +02:00
2018-09-15 11:19:53 +02:00
if (hitCount > 1)
hit = hits[1];
else
goOn = 0;
}
else
2018-09-14 19:42:59 +02:00
{
2018-09-15 11:19:53 +02:00
// normal hit, check the door roll
2018-09-14 19:42:59 +02:00
2018-09-15 11:56:20 +02:00
int8_t unrolled = hit.doorRoll >= 0 ?
hit.doorRoll > hit.textureCoord :
2018-09-17 17:55:53 +02:00
hit.textureCoord > RCL_UNITS_PER_SQUARE + hit.doorRoll;
2018-09-15 11:19:53 +02:00
if (unrolled)
2018-09-14 19:42:59 +02:00
{
2018-09-15 11:19:53 +02:00
goOn = 0;
if (hitCount > 1) /* should probably always be true (hit on square
exit) */
2018-09-14 19:42:59 +02:00
{
2018-09-15 11:19:53 +02:00
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;
}
2018-09-14 19:42:59 +02:00
}
}
}
}
2018-09-11 08:09:34 +02:00
p.hit = hit;
2018-09-14 17:46:31 +02:00
2018-09-14 19:42:59 +02:00
if (goOn)
{
2018-09-17 17:55:53 +02:00
RCL_dist = RCL_adjustDistance(hit.distance,&_RCL_camera,&ray);
2018-09-14 17:46:31 +02:00
2018-09-17 17:55:53 +02:00
int16_t wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y);
2018-09-14 17:46:31 +02:00
2018-09-17 17:55:53 +02:00
wallHeightScreen = RCL_perspectiveScale((wallHeightWorld *
_RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,RCL_dist);
2018-09-11 08:09:34 +02:00
2018-09-17 17:55:53 +02:00
int16_t RCL_normalizedWallHeight = wallHeightWorld != 0 ?
((RCL_UNITS_PER_SQUARE * wallHeightScreen) / wallHeightWorld) : 0;
2018-09-14 17:46:31 +02:00
2018-09-17 17:55:53 +02:00
heightOffset = RCL_perspectiveScale(_RCL_cameraHeightScreen,RCL_dist);
2018-09-11 08:24:48 +02:00
2018-09-17 17:55:53 +02:00
wallStart = _RCL_middleRow - wallHeightScreen + heightOffset +
RCL_normalizedWallHeight;
2018-09-11 08:24:48 +02:00
2018-09-14 19:42:59 +02:00
coordHelper = -1 * wallStart;
coordHelper = coordHelper >= 0 ? coordHelper : 0;
2018-09-11 08:24:48 +02:00
2018-09-18 17:52:22 +02:00
wallEnd = RCL_clamp(wallStart + wallHeightScreen,0,
_RCL_camera.resolution.y);
// ^ intentionally allow outside screen
2018-09-17 17:55:53 +02:00
wallStart = RCL_clamp(wallStart,0,_RCL_camResYLimit);
2018-09-14 19:42:59 +02:00
}
2018-09-11 08:24:48 +02:00
}
2018-09-11 08:09:34 +02:00
// draw ceiling
p.isWall = 0;
p.isFloor = 0;
p.isHorizon = 1;
p.depth = 1;
while (y < wallStart)
{
p.position.y = y;
2018-09-17 17:55:53 +02:00
RCL_PIXEL_FUNCTION(&p);
2018-09-11 08:09:34 +02:00
++y;
2018-09-17 17:55:53 +02:00
p.depth += _RCL_horizontalDepthStep;
2018-09-11 08:09:34 +02:00
}
// draw wall
p.isWall = 1;
p.isFloor = 1;
2018-09-17 17:55:53 +02:00
p.depth = RCL_dist;
2018-09-11 08:09:34 +02:00
2018-09-17 17:55:53 +02:00
#if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
2018-09-15 11:56:20 +02:00
p.hit.textureCoord -= p.hit.doorRoll;
#endif
2018-09-17 17:55:53 +02:00
RCL_Unit coordStep = 1;
2018-09-11 08:09:34 +02:00
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
2018-09-16 17:12:09 +02:00
p.texCoords.x = p.hit.textureCoord;
2018-09-17 17:55:53 +02:00
coordStep = RCL_UNITS_PER_SQUARE / wallHeightScreen;
2018-09-16 17:12:09 +02:00
p.texCoords.y = coordStep * coordHelper;
2018-09-16 14:24:32 +02:00
#endif
2018-09-11 08:09:34 +02:00
2018-09-17 17:55:53 +02:00
if (coordStep < RCL_MIN_TEXTURE_STEP) /* instead of branching inside a
critical loop, have two versions of
the loop and branch early (here) */
2018-09-16 17:12:09 +02:00
{
while (y < wallEnd)
{
// more expensive and accurate version of texture coords computation
2018-09-14 17:46:31 +02:00
2018-09-16 17:12:09 +02:00
p.position.y = y;
2018-09-17 17:55:53 +02:00
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
p.texCoords.y = (RCL_UNITS_PER_SQUARE * coordHelper) / wallHeightScreen;
2018-09-16 17:12:09 +02:00
#endif
2018-09-17 17:55:53 +02:00
RCL_PIXEL_FUNCTION(&p);
2018-09-16 17:12:09 +02:00
++coordHelper;
++y;
}
}
else
{
while (y < wallEnd)
{
// cheaper texture coord computation
p.position.y = y;
2018-09-17 17:55:53 +02:00
RCL_PIXEL_FUNCTION(&p);
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
2018-09-16 17:12:09 +02:00
p.texCoords.y += coordStep;
#endif
++y;
}
2018-09-11 08:09:34 +02:00
}
// draw floor
p.isWall = 0;
2018-09-17 17:55:53 +02:00
p.depth = (_RCL_camera.resolution.y - y) * _RCL_horizontalDepthStep + 1;
2018-09-11 08:09:34 +02:00
2018-09-18 10:25:14 +02:00
#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
RCL_Unit dx = p.hit.position.x - _RCL_camera.position.x;
RCL_Unit dy = p.hit.position.y - _RCL_camera.position.y;
2018-09-18 17:40:32 +02:00
RCL_Unit pixPos = y - _RCL_middleRow;
2018-09-18 10:25:14 +02:00
RCL_Unit rayCameraCos = RCL_vectorsAngleCos(
RCL_angleToDirection(_RCL_camera.direction),ray.direction);
#endif
2018-09-17 16:45:02 +02:00
2018-09-17 17:55:53 +02:00
while (y < _RCL_camera.resolution.y)
2018-09-11 08:09:34 +02:00
{
2018-09-18 09:40:17 +02:00
2018-09-18 10:25:14 +02:00
#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
RCL_Unit d = _RCL_floorPixelDistances[pixPos];
2018-09-18 09:40:17 +02:00
2018-09-18 10:25:14 +02:00
d = (d * RCL_UNITS_PER_SQUARE) / rayCameraCos;
// ^ inverse of RCL_adjustDistance(...)
2018-09-18 09:40:17 +02:00
2018-09-18 10:25:14 +02:00
p.texCoords.x = _RCL_camera.position.x + ((d * dx) / (p.hit.distance));
p.texCoords.y = _RCL_camera.position.y + ((d * dy) / (p.hit.distance));
pixPos++;
#endif
2018-09-18 09:40:17 +02:00
2018-09-11 08:09:34 +02:00
p.position.y = y;
2018-09-17 17:55:53 +02:00
RCL_PIXEL_FUNCTION(&p);
2018-09-18 17:52:22 +02:00
2018-09-11 08:09:34 +02:00
++y;
2018-09-18 17:52:22 +02:00
2018-09-17 17:55:53 +02:00
p.depth -= _RCL_horizontalDepthStep;
2018-09-17 15:05:38 +02:00
if (p.depth < 0) // just in case
p.depth = 0;
2018-09-11 08:09:34 +02:00
}
}
2018-09-17 17:55:53 +02:00
void RCL_render(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
RCL_RayConstraints constraints)
2018-08-31 18:26:51 +02:00
{
2018-09-17 17:55:53 +02:00
_RCL_floorFunction = floorHeightFunc;
_RCL_ceilFunction = ceilingHeightFunc;
_RCL_camera = cam;
_RCL_camResYLimit = cam.resolution.y - 1;
2018-09-07 20:31:30 +02:00
2018-09-08 07:23:21 +02:00
int16_t halfResY = cam.resolution.y / 2;
2018-09-17 17:55:53 +02:00
_RCL_middleRow = halfResY + cam.shear;
2018-09-08 07:23:21 +02:00
2018-09-17 17:55:53 +02:00
_RCL_fHorizontalDepthStart = _RCL_middleRow + halfResY;
_RCL_cHorizontalDepthStart = _RCL_middleRow - halfResY;
2018-09-07 20:31:30 +02:00
2018-09-17 17:55:53 +02:00
_RCL_startFloorHeight = floorHeightFunc(
RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height;
2018-09-05 11:35:52 +02:00
2018-09-17 17:55:53 +02:00
_RCL_startCeil_Height =
2018-09-05 12:20:46 +02:00
ceilingHeightFunc != 0 ?
ceilingHeightFunc(
2018-09-17 17:55:53 +02:00
RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height
: RCL_INFINITY;
2018-09-01 12:04:29 +02:00
// TODO
2018-09-17 17:55:53 +02:00
_RCL_horizontalDepthStep = (12 * RCL_UNITS_PER_SQUARE) / cam.resolution.y;
2018-09-01 12:04:29 +02:00
2018-09-17 17:55:53 +02:00
RCL_castRaysMultiHit(cam,_floorCeilFunction,typeFunction,
2018-09-05 16:26:13 +02:00
_columnFunction,constraints);
2018-08-31 18:26:51 +02:00
}
2018-09-17 17:55:53 +02:00
void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
RCL_RayConstraints constraints)
2018-09-11 08:09:34 +02:00
{
2018-09-17 17:55:53 +02:00
_RCL_floorFunction = floorHeightFunc;
_RCL_camera = cam;
_RCL_camResYLimit = cam.resolution.y - 1;
_RCL_middleRow = cam.resolution.y / 2;
_RCL_rollFunction = rollFunc;
2018-09-11 08:09:34 +02:00
2018-09-17 17:55:53 +02:00
_RCL_cameraHeightScreen =
(_RCL_camera.resolution.y * (_RCL_camera.height - RCL_UNITS_PER_SQUARE)) /
RCL_UNITS_PER_SQUARE;
2018-09-11 08:24:48 +02:00
2018-09-11 08:09:34 +02:00
// TODO
2018-09-17 17:55:53 +02:00
_RCL_horizontalDepthStep = (12 * RCL_UNITS_PER_SQUARE) / cam.resolution.y;
2018-09-11 08:09:34 +02:00
2018-09-14 19:42:59 +02:00
constraints.maxHits =
2018-09-17 17:55:53 +02:00
_RCL_rollFunction == 0 ?
2018-09-14 19:42:59 +02:00
1 : // no door => 1 hit is enough
3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
2018-09-11 08:09:34 +02:00
2018-09-18 10:25:14 +02:00
#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
uint16_t halfResY = cam.resolution.y / 2;
RCL_Unit floorPixelDistances[halfResY]; /* for each vertical floor pixel,
this will contain precomputed
distance to the camera */
RCL_Unit camHeightScreenSize =
(((cam.height >> 6) << 6) * // prevent weird floor movement with rounding
cam.resolution.y) / RCL_UNITS_PER_SQUARE;
for (uint16_t i = 0; i < halfResY; ++i) // precompute the distances
floorPixelDistances[i] =
RCL_perspectiveScaleInverse(camHeightScreenSize,i);
// pass to _columnFunctionSimple
_RCL_floorPixelDistances = floorPixelDistances;
#endif
2018-09-17 17:55:53 +02:00
RCL_castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc,
2018-09-14 17:46:31 +02:00
_columnFunctionSimple, constraints);
2018-09-18 10:25:14 +02:00
#if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
_RCL_floorPixelDistances = 0;
#endif
2018-09-11 08:09:34 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_Vector2D RCL_normalize(RCL_Vector2D v)
2018-08-30 18:31:08 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_normalize);
2018-08-30 18:31:08 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D result;
RCL_Unit l = RCL_len(v);
2018-09-04 19:35:30 +02:00
l = l != 0 ? l : 1;
2018-09-02 13:58:46 +02:00
2018-09-17 17:55:53 +02:00
result.x = (v.x * RCL_UNITS_PER_SQUARE) / l;
result.y = (v.y * RCL_UNITS_PER_SQUARE) / l;
2018-08-30 18:31:08 +02:00
return result;
}
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2)
2018-08-30 18:31:08 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_vectorsAngleCos);
2018-08-31 15:38:02 +02:00
2018-09-17 17:55:53 +02:00
v1 = RCL_normalize(v1);
v2 = RCL_normalize(v2);
2018-08-30 18:31:08 +02:00
2018-09-17 17:55:53 +02:00
return (v1.x * v2.x + v1.y * v2.y) / RCL_UNITS_PER_SQUARE;
2018-08-30 18:31:08 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
RCL_Camera camera)
2018-09-04 11:42:27 +02:00
{
// TODO: precompute some stuff that's constant in the frame
2018-09-17 17:55:53 +02:00
RCL_PixelInfo result;
2018-09-04 11:42:27 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit d = RCL_dist(worldPosition,camera.position);
2018-09-04 11:42:27 +02:00
2018-09-17 17:55:53 +02:00
RCL_Vector2D toPoint;
2018-09-04 11:42:27 +02:00
toPoint.x = worldPosition.x - camera.position.x;
toPoint.y = worldPosition.y - camera.position.y;
2018-09-17 17:55:53 +02:00
RCL_Vector2D cameraDir = RCL_angleToDirection(camera.direction);
2018-09-04 11:42:27 +02:00
result.depth = // adjusted distance
2018-09-17 17:55:53 +02:00
(d * RCL_vectorsAngleCos(cameraDir,toPoint)) / RCL_UNITS_PER_SQUARE;
2018-09-04 11:42:27 +02:00
result.position.y = camera.resolution.y / 2 -
(camera.resolution.y *
2018-09-17 17:55:53 +02:00
RCL_perspectiveScale(height - camera.height,result.depth)) / RCL_UNITS_PER_SQUARE
2018-09-08 07:10:09 +02:00
+ camera.shear;
2018-09-04 11:42:27 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit middleColumn = camera.resolution.x / 2;
2018-09-04 11:42:27 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit a = RCL_sqrtInt(d * d - result.depth * result.depth);
2018-09-04 11:42:27 +02:00
2018-09-17 17:55:53 +02:00
RCL_Ray r;
2018-09-04 11:42:27 +02:00
r.start = camera.position;
r.direction = cameraDir;
2018-09-17 17:55:53 +02:00
if (!RCL_pointIsLeftOfRCL_Ray(worldPosition,r))
2018-09-04 11:42:27 +02:00
a *= -1;
2018-09-17 17:55:53 +02:00
RCL_Unit cos = RCL_cosInt(RCL_HORIZONTAL_FOV_HALF);
2018-09-04 11:42:27 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit b = (result.depth * RCL_sinInt(RCL_HORIZONTAL_FOV_HALF)) /
(cos == 0 ? 1 : cos);
2018-09-11 09:07:24 +02:00
// sin/cos = tan
2018-09-04 11:42:27 +02:00
2018-09-12 12:09:34 +02:00
result.position.x = (a * middleColumn) / (b == 0 ? 1 : b);
2018-09-04 13:38:04 +02:00
result.position.x = middleColumn - result.position.x;
2018-09-04 11:42:27 +02:00
return result;
}
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees)
2018-08-30 08:51:53 +02:00
{
2018-09-17 17:55:53 +02:00
return (degrees * RCL_UNITS_PER_SQUARE) / 360;
2018-08-30 08:51:53 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_perspectiveScale(RCL_Unit originalSize, RCL_Unit distance)
2018-08-30 08:51:53 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_profileCall(RCL_perspectiveScale);
2018-09-04 18:55:18 +02:00
2018-09-04 19:32:47 +02:00
return distance != 0 ?
2018-09-17 17:55:53 +02:00
(originalSize * RCL_UNITS_PER_SQUARE) /
((RCL_VERTICAL_FOV * 2 * distance) / RCL_UNITS_PER_SQUARE)
2018-09-04 19:32:47 +02:00
: 0;
2018-08-30 08:51:53 +02:00
}
2018-09-17 17:55:53 +02:00
RCL_Unit RCL_perspectiveScaleInverse(RCL_Unit originalSize,
RCL_Unit scaledSize)
2018-09-17 15:05:38 +02:00
{
return scaledSize != 0 ?
2018-09-17 17:55:53 +02:00
(originalSize * RCL_UNITS_PER_SQUARE) /
((RCL_VERTICAL_FOV * 2 * scaledSize) / RCL_UNITS_PER_SQUARE)
2018-09-17 15:05:38 +02:00
: 0;
}
2018-09-17 17:55:53 +02:00
void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force)
2018-09-06 17:41:09 +02:00
{
// TODO: have the cam coll parameters precomputed as macros? => faster
2018-09-08 07:28:47 +02:00
int8_t movesInPlane = planeOffset.x != 0 || planeOffset.y != 0;
int16_t xSquareNew, ySquareNew;
2018-09-06 17:41:09 +02:00
2018-09-12 07:55:29 +02:00
if (movesInPlane || force)
2018-09-08 07:28:47 +02:00
{
2018-09-17 17:55:53 +02:00
RCL_Vector2D corner; // BBox corner in the movement direction
RCL_Vector2D cornerNew;
2018-09-08 07:28:47 +02:00
int16_t xDir = planeOffset.x > 0 ? 1 : (planeOffset.x < 0 ? -1 : 0);
int16_t yDir = planeOffset.y > 0 ? 1 : (planeOffset.y < 0 ? -1 : 0);
2018-09-17 17:55:53 +02:00
corner.x = camera->position.x + xDir * RCL_CAMERA_COLL_RADIUS;
corner.y = camera->position.y + yDir * RCL_CAMERA_COLL_RADIUS;
2018-09-06 17:41:09 +02:00
2018-09-17 17:55:53 +02:00
int16_t xSquare = RCL_divRoundDown(corner.x,RCL_UNITS_PER_SQUARE);
int16_t ySquare = RCL_divRoundDown(corner.y,RCL_UNITS_PER_SQUARE);
2018-09-06 17:41:09 +02:00
2018-09-08 07:28:47 +02:00
cornerNew.x = corner.x + planeOffset.x;
cornerNew.y = corner.y + planeOffset.y;
2018-09-06 17:41:09 +02:00
2018-09-17 17:55:53 +02:00
xSquareNew = RCL_divRoundDown(cornerNew.x,RCL_UNITS_PER_SQUARE);
ySquareNew = RCL_divRoundDown(cornerNew.y,RCL_UNITS_PER_SQUARE);
2018-09-06 17:41:09 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit bottomLimit = camera->height - RCL_CAMERA_COLL_HEIGHT_BELOW +
RCL_CAMERA_COLL_STEP_HEIGHT;
RCL_Unit topLimit = camera->height + RCL_CAMERA_COLL_HEIGHT_ABOVE;
2018-09-06 19:17:31 +02:00
2018-09-08 07:28:47 +02:00
// checks a single square for collision against the camera
#define collCheck(dir,s1,s2)\
2018-09-08 18:07:36 +02:00
if (computeHeight)\
2018-09-06 19:17:31 +02:00
{\
2018-09-17 17:55:53 +02:00
RCL_Unit height = floorHeightFunc(s1,s2);\
2018-09-08 07:28:47 +02:00
if (height > bottomLimit)\
2018-09-11 17:19:50 +02:00
dir##Collides = 1;\
2018-09-08 07:28:47 +02:00
else if (ceilingHeightFunc != 0)\
{\
height = ceilingHeightFunc(s1,s2);\
if (height < topLimit)\
2018-09-11 17:19:50 +02:00
dir##Collides = 1;\
2018-09-08 07:28:47 +02:00
}\
2018-09-06 19:17:31 +02:00
}\
2018-09-08 18:07:36 +02:00
else\
2018-09-17 17:55:53 +02:00
dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
2018-09-07 16:42:55 +02:00
2018-09-08 07:28:47 +02:00
// check a collision against non-diagonal square
#define collCheckOrtho(dir,dir2,s1,s2,x)\
if (dir##SquareNew != dir##Square)\
2018-09-15 12:10:53 +02:00
{\
2018-09-08 07:28:47 +02:00
collCheck(dir,s1,s2)\
2018-09-15 12:10:53 +02:00
}\
2018-09-08 07:28:47 +02:00
if (!dir##Collides)\
{ /* now also check for coll on the neighbouring square */ \
2018-09-17 17:55:53 +02:00
int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
2018-09-08 07:28:47 +02:00
if (dir2##Square2 != dir2##Square)\
{\
if (x)\
collCheck(dir,dir##SquareNew,dir2##Square2)\
else\
collCheck(dir,dir2##Square2,dir##SquareNew)\
}\
}
2018-09-07 16:42:55 +02:00
2018-09-11 15:29:58 +02:00
int8_t xCollides = 0;
2018-09-08 07:28:47 +02:00
collCheckOrtho(x,y,xSquareNew,ySquare,1)
2018-09-07 16:42:55 +02:00
2018-09-11 15:29:58 +02:00
int8_t yCollides = 0;
2018-09-08 07:28:47 +02:00
collCheckOrtho(y,x,xSquare,ySquareNew,0)
2018-09-07 16:42:55 +02:00
2018-09-08 07:28:47 +02:00
#define collHandle(dir)\
if (dir##Collides)\
2018-09-17 17:55:53 +02:00
cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
dir##Dir;\
2018-09-06 17:41:09 +02:00
2018-09-08 10:08:14 +02:00
if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
2018-09-08 07:28:47 +02:00
collision can't happen */
2018-09-07 16:42:55 +02:00
{
2018-09-08 07:28:47 +02:00
if (xSquare != xSquareNew && ySquare != ySquareNew) // corner?
{
2018-09-11 15:29:58 +02:00
int8_t xyCollides = 0;
2018-09-08 07:28:47 +02:00
collCheck(xy,xSquareNew,ySquareNew)
if (xyCollides)
2018-09-08 10:08:14 +02:00
{
2018-09-08 10:53:15 +02:00
// normally should slide, but let's KISS
cornerNew = corner;
2018-09-08 10:08:14 +02:00
}
2018-09-08 07:28:47 +02:00
}
2018-09-07 16:42:55 +02:00
}
2018-09-06 17:41:09 +02:00
2018-09-08 10:08:14 +02:00
collHandle(x)
collHandle(y)
2018-09-08 07:28:47 +02:00
#undef collCheck
#undef collHandle
2018-09-06 17:41:09 +02:00
2018-09-17 17:55:53 +02:00
camera->position.x = cornerNew.x - xDir * RCL_CAMERA_COLL_RADIUS;
camera->position.y = cornerNew.y - yDir * RCL_CAMERA_COLL_RADIUS;
2018-09-08 07:28:47 +02:00
}
2018-09-06 17:41:09 +02:00
2018-09-12 07:55:29 +02:00
if (computeHeight && (movesInPlane || heightOffset != 0 || force))
2018-09-08 07:28:47 +02:00
{
camera->height += heightOffset;
2018-09-17 17:55:53 +02:00
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);
2018-09-08 17:55:46 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1);
RCL_Unit topLimit = ceilingHeightFunc != 0 ?
ceilingHeightFunc(xSquare1,ySquare1) : RCL_INFINITY;
2018-09-08 17:55:46 +02:00
2018-09-17 17:55:53 +02:00
RCL_Unit height;
2018-09-08 17:55:46 +02:00
2018-09-09 16:29:48 +02:00
#define checkSquares(s1,s2)\
{\
height = floorHeightFunc(xSquare##s1,ySquare##s2);\
bottomLimit = bottomLimit < height ? height : bottomLimit;\
height = ceilingHeightFunc != 0 ?\
2018-09-17 17:55:53 +02:00
ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
2018-09-09 16:29:48 +02:00
topLimit = topLimit > height ? height : topLimit;\
2018-09-08 17:55:46 +02:00
}
2018-09-08 07:28:47 +02:00
2018-09-09 16:29:48 +02:00
if (xSquare2 != xSquare1)
checkSquares(2,1)
2018-09-08 17:55:46 +02:00
if (ySquare2 != ySquare1)
2018-09-09 16:29:48 +02:00
checkSquares(1,2)
2018-09-08 17:55:46 +02:00
if (xSquare2 != xSquare1 && ySquare2 != ySquare1)
2018-09-09 16:29:48 +02:00
checkSquares(2,2)
2018-09-08 17:55:46 +02:00
2018-09-17 17:55:53 +02:00
camera->height = RCL_clamp(camera->height,
bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW,
topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE);
2018-09-09 16:29:48 +02:00
#undef checkSquares
2018-09-08 07:28:47 +02:00
}
2018-09-06 17:41:09 +02:00
}
2018-09-17 17:55:53 +02:00
void RCL_initCamera(RCL_Camera *camera)
2018-09-15 14:35:21 +02:00
{
camera->position.x = 0;
camera->position.y = 0;
camera->direction = 0;
camera->resolution.x = 20;
camera->resolution.y = 15;
camera->shear = 0;
2018-09-17 17:55:53 +02:00
camera->height = RCL_UNITS_PER_SQUARE;
2018-09-15 14:35:21 +02:00
}
2018-09-17 17:55:53 +02:00
void RCL_initRayConstraints(RCL_RayConstraints *constraints)
2018-09-15 14:35:21 +02:00
{
constraints->maxHits = 1;
constraints->maxSteps = 20;
}
2018-08-23 02:46:40 +02:00
#endif