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

1550 lines
42 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-01 07:27:17 +02:00
raycastlib - Small C header-only raycasting library for embedded and low
performance computers, such as Arduino. Only uses integer math and stdint
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 07:15:06 +02:00
Before includinf the library efine PIXEL_FUNCTION to the name of the
function (with PixelFunction signature) that will render your pixels!
2018-08-21 13:49:45 +02:00
author: Miloslav "drummyfish" Ciz
license: CC0
2018-09-11 09:07:24 +02:00
version: 0.1
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.
- Each game square is UNITS_PER_SQUARE * UNITS_PER_SQUARE points.
2018-08-23 09:35:10 +02:00
- Angles are in Units, 0 means pointing right (x+) and positively rotates
2018-09-12 08:39:52 +02:00
clockwise. A full angle has UNITS_PER_SQUARE Units.
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-14 12:25:59 +02:00
#ifndef RAYCAST_TINY /** Turns on super efficient version of this library. Only
use if neccesarry, looks ugly. */
2018-09-05 12:20:46 +02:00
#define UNITS_PER_SQUARE 1024 ///< N. of Units in a side of a spatial square.
2018-09-17 08:26:25 +02:00
typedef int32_t Unit; /**< Smaller spatial unit, there is UNITS_PER_SQUARE
2018-09-01 07:27:17 +02:00
units in a square's length. This effectively
serves the purpose of a fixed-point arithmetic. */
2018-09-08 18:39:47 +02:00
#define UNIT_INFINITY 5000000;
2018-09-02 13:58:46 +02:00
#else
2018-09-17 11:27:25 +02:00
#define UNITS_PER_SQUARE 32
2018-09-05 12:20:46 +02:00
typedef int16_t Unit;
2018-09-14 15:18:34 +02:00
#define UNIT_INFINITY 32000;
2018-09-14 14:40:31 +02:00
#define USE_DIST_APPROX 2
2018-09-02 13:58:46 +02:00
#endif
2018-08-21 13:49:45 +02:00
2018-09-16 14:24:32 +02:00
#ifndef COMPUTE_WALL_TEXCOORDS
#define COMPUTE_WALL_TEXCOORDS 1
#endif
2018-09-12 08:39:52 +02:00
#ifndef USE_COS_LUT
#define USE_COS_LUT 0 /**< type of look up table for cos function:
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-14 12:25:59 +02:00
#ifndef USE_DIST_APPROX
2018-09-15 11:56:20 +02:00
#define 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-15 11:56:20 +02:00
#ifndef ROLL_TEXTURE_COORDS
#define ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also roll
the texture coordinates along (mostly
desired for doors). */
#endif
2018-09-05 16:26:13 +02:00
#ifndef VERTICAL_FOV
2018-09-03 16:38:49 +02:00
#define VERTICAL_FOV (UNITS_PER_SQUARE / 2)
2018-09-05 16:26:13 +02:00
#endif
2018-09-03 16:38:49 +02:00
2018-09-08 07:23:21 +02:00
#ifndef HORIZONTAL_FOV
#define HORIZONTAL_FOV (UNITS_PER_SQUARE / 4)
#endif
#define HORIZONTAL_FOV_HALF (HORIZONTAL_FOV / 2)
2018-09-08 07:52:55 +02:00
#ifndef CAMERA_COLL_RADIUS
#define CAMERA_COLL_RADIUS UNITS_PER_SQUARE / 4
#endif
#ifndef CAMERA_COLL_HEIGHT_BELOW
#define CAMERA_COLL_HEIGHT_BELOW UNITS_PER_SQUARE
#endif
#ifndef CAMERA_COLL_HEIGHT_ABOVE
#define CAMERA_COLL_HEIGHT_ABOVE (UNITS_PER_SQUARE / 3)
#endif
#ifndef CAMERA_COLL_STEP_HEIGHT
#define CAMERA_COLL_STEP_HEIGHT (UNITS_PER_SQUARE / 2)
#endif
2018-09-16 17:12:09 +02:00
#ifndef MIN_TEXTURE_STEP
#define 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
expensive way of computing texture coords. */
#endif
2018-08-23 03:04:52 +02:00
#define logVector2D(v)\
printf("[%d,%d]\n",v.x,v.y);
2018-08-23 09:35:10 +02:00
#define logRay(r){\
2018-08-23 03:04:52 +02:00
printf("ray:\n");\
printf(" start: ");\
logVector2D(r.start);\
printf(" dir: ");\
2018-08-23 09:35:10 +02:00
logVector2D(r.direction);}\
2018-08-23 03:04:52 +02:00
2018-08-23 09:35:10 +02:00
#define logHitResult(h){\
2018-08-23 03:04:52 +02:00
printf("hit:\n");\
printf(" sqaure: ");\
logVector2D(h.square);\
printf(" pos: ");\
logVector2D(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-04 13:01:14 +02:00
#define logPixelInfo(p){\
printf("pixel:\n");\
printf(" position: ");\
logVector2D(p.position);\
printf(" depth: %d\n", p.depth);\
printf(" wall: %d\n", p.isWall);\
printf(" hit: ");\
logHitResult(p.hit);\
}\
2018-08-21 13:49:45 +02:00
/// Position in 2D space.
typedef struct
{
2018-09-04 18:55:18 +02:00
Unit x;
2018-09-15 17:06:23 +02:00
Unit y;
2018-08-21 13:49:45 +02:00
} Vector2D;
typedef struct
{
Vector2D start;
Vector2D direction;
} Ray;
2018-08-22 08:21:38 +02:00
typedef struct
{
2018-09-06 14:04:36 +02:00
Unit distance; /**< Euclidean distance to the hit position, or -1 if
2018-09-15 11:19:53 +02:00
no collision happened. */
2018-09-15 17:06:23 +02:00
uint8_t direction; ///< Direction of hit.
2018-09-05 16:26:13 +02:00
Unit textureCoord; ///< Normalized (0 to UNITS_PER_SQUARE - 1) tex coord.
2018-09-15 17:06:23 +02:00
Vector2D square; ///< Collided square coordinates.
Vector2D position; ///< Exact collision position in Units.
2018-09-15 11:19:53 +02:00
Unit arrayValue; /** Value returned by array function (most often this
will be the floor height). */
Unit type; /**< Integer identifying type of square (number
returned by type function, e.g. texture index).*/
2018-09-14 20:17:15 +02:00
Unit doorRoll; ///< Holds value of door roll.
2018-08-22 08:21:38 +02:00
} HitResult;
2018-08-31 11:51:03 +02:00
typedef struct
{
2018-08-31 13:29:50 +02:00
Vector2D position;
Unit direction;
Vector2D resolution;
2018-09-08 07:10:09 +02:00
int16_t shear; /* Shear offset in pixels (0 => no shear), can simulate
looking up/down. */
2018-08-31 13:29:50 +02:00
Unit height;
} Camera;
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-03 14:23:11 +02:00
Vector2D position; ///< On-screen position.
2018-09-05 19:08:38 +02:00
int8_t isWall; ///< Whether the pixel is a wall or a floor/ceiling.
int8_t isFloor; ///< Whether the pixel is floor or ceiling.
2018-09-10 13:35:39 +02:00
int8_t isHorizon; ///< Whether the pixel is floor going towards horizon.
2018-09-03 14:23:11 +02:00
Unit depth; ///< Corrected depth.
HitResult hit; ///< Corresponding ray hit.
2018-09-16 17:12:09 +02:00
Vector2D texCoords; /**< Normalized (0 to UNITS_PER_SQUARE - 1) texture
coordinates. */
2018-08-31 11:51:03 +02:00
} PixelInfo;
2018-09-17 07:15:06 +02:00
void PIXEL_FUNCTION (PixelInfo *p);
2018-08-31 13:29:50 +02:00
typedef struct
{
uint16_t maxHits;
uint16_t maxSteps;
} RayConstraints;
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-01 09:07:49 +02:00
typedef Unit (*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-11 14:38:10 +02:00
typedef void (*PixelFunction)(PixelInfo *info);
2018-08-31 11:51:03 +02:00
2018-09-01 07:27:17 +02:00
typedef void
(*ColumnFunction)(HitResult *hits, uint16_t hitCount, uint16_t x, Ray ray);
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-08-31 13:29:50 +02:00
HitResult castRay(Ray ray, 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-04 11:42:27 +02:00
PixelInfo mapToScreen(Vector2D worldPosition, Unit height, Camera camera);
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-05 16:26:13 +02:00
void castRayMultiHit(Ray ray, ArrayFunction arrayFunc, ArrayFunction typeFunc,
HitResult *hitResults, uint16_t *hitResultsLen, RayConstraints constraints);
2018-08-23 02:36:51 +02:00
2018-08-23 09:35:10 +02:00
Vector2D angleToDirection(Unit angle);
2018-09-09 17:20:44 +02:00
/**
Cos function.
@param input to cos in Units (UNITS_PER_SQUARE = 2 * pi = 360 degrees)
@return normalized output in Units (from -UNITS_PER_SQUARE to UNITS_PER_SQUARE)
*/
2018-08-23 09:25:34 +02:00
Unit cosInt(Unit input);
2018-09-09 17:20:44 +02:00
2018-08-23 09:25:34 +02:00
Unit sinInt(Unit input);
2018-08-30 18:31:08 +02:00
/// Normalizes given vector to have UNITS_PER_SQUARE length.
Vector2D normalize(Vector2D v);
/// Computes a cos of an angle between two vectors.
Unit vectorsAngleCos(Vector2D v1, Vector2D v2);
2018-09-13 15:22:16 +02:00
uint16_t sqrtInt(Unit value);
2018-08-23 09:25:34 +02:00
Unit dist(Vector2D p1, Vector2D p2);
2018-08-30 14:44:14 +02:00
Unit len(Vector2D v);
2018-08-23 09:25:34 +02:00
2018-08-30 08:51:53 +02:00
/**
2018-09-06 14:04:36 +02:00
Converts an angle in whole degrees to an angle in Units that this library
uses.
2018-09-09 17:20:44 +02:00
*/
2018-08-30 08:51:53 +02:00
Unit degreesToUnitsAngle(int16_t degrees);
///< Computes the change in size of an object due to perspective.
2018-09-03 16:38:49 +02:00
Unit perspectiveScale(Unit originalSize, Unit distance);
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-08-31 18:11:32 +02:00
void castRaysMultiHit(Camera cam, ArrayFunction arrayFunc,
2018-09-05 16:26:13 +02:00
ArrayFunction typeFunction, ColumnFunction columnFunc,
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-06 14:04:36 +02:00
@param cam camera whose view to render
@param floorHeightFunc function that returns floor height (in Units)
@param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
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-05 16:26:13 +02:00
void render(Camera cam, ArrayFunction floorHeightFunc,
ArrayFunction ceilingHeightFunc, ArrayFunction typeFunction,
2018-09-17 07:15:06 +02:00
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
simpler and faster than render(...) and is meant to be rendering scenes
with simple 1-intersection raycasting. The render(...) function can give more
accurate results than this function, so it's to be considered even for simple
scenes.
2018-09-12 17:37:15 +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
@param rollFunc function that for given square says its door roll in Units
(0 = no roll, UNITS_PER_SQUARE = full roll right, -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-11 08:09:34 +02:00
void renderSimple(Camera cam, ArrayFunction floorHeightFunc,
2018-09-17 07:15:06 +02:00
ArrayFunction typeFunc, ArrayFunction rollFunc,
2018-09-14 19:42:59 +02:00
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-06 17:41:09 +02:00
void moveCameraWithCollision(Camera *camera, Vector2D planeOffset,
Unit heightOffset, ArrayFunction floorHeightFunc,
2018-09-12 07:55:29 +02:00
ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force);
2018-09-06 17:41:09 +02:00
2018-09-15 14:35:21 +02:00
void initCamera(Camera *camera);
void initRayConstraints(RayConstraints *constraints);
2018-08-21 13:49:45 +02:00
//=============================================================================
// privates
2018-09-14 20:17:15 +02:00
// global helper variables, for precomputing stuff etc.
Camera _camera;
Unit _horizontalDepthStep = 0;
Unit _startFloorHeight = 0;
Unit _startCeilHeight = 0;
Unit _camResYLimit = 0;
Unit _middleRow = 0;
ArrayFunction _floorFunction = 0;
ArrayFunction _ceilFunction = 0;
Unit _fHorizontalDepthStart = 0;
Unit _cHorizontalDepthStart = 0;
int16_t _cameraHeightScreen = 0;
ArrayFunction _rollFunction = 0; // says door rolling
2018-08-31 15:38:02 +02:00
#ifdef RAYCASTLIB_PROFILE
// function call counters for profiling
2018-09-04 18:55:18 +02:00
uint32_t profile_sqrtInt = 0;
uint32_t profile_clamp = 0;
uint32_t profile_cosInt = 0;
uint32_t profile_angleToDirection = 0;
uint32_t profile_dist = 0;
uint32_t profile_len = 0;
uint32_t profile_pointIsLeftOfRay = 0;
uint32_t profile_castRaySquare = 0;
uint32_t profile_castRayMultiHit = 0;
uint32_t profile_castRay = 0;
uint32_t profile_absVal = 0;
uint32_t profile_normalize = 0;
uint32_t profile_vectorsAngleCos = 0;
uint32_t profile_perspectiveScale = 0;
2018-09-04 19:32:47 +02:00
uint32_t profile_wrap = 0;
uint32_t profile_divRoundDown = 0;
2018-08-31 15:38:02 +02:00
#define profileCall(c) profile_##c += 1
#define printProfile() {\
printf("profile:\n");\
2018-08-31 16:46:55 +02:00
printf(" sqrtInt: %d\n",profile_sqrtInt);\
printf(" clamp: %d\n",profile_clamp);\
printf(" cosInt: %d\n",profile_cosInt);\
printf(" angleToDirection: %d\n",profile_angleToDirection);\
printf(" dist: %d\n",profile_dist);\
printf(" len: %d\n",profile_len);\
printf(" pointIsLeftOfRay: %d\n",profile_pointIsLeftOfRay);\
printf(" castRaySquare: %d\n",profile_castRaySquare);\
printf(" castRayMultiHit : %d\n",profile_castRayMultiHit);\
printf(" castRay: %d\n",profile_castRay);\
printf(" normalize: %d\n",profile_normalize);\
2018-09-04 18:55:18 +02:00
printf(" vectorsAngleCos: %d\n",profile_vectorsAngleCos);\
printf(" absVal: %d\n",profile_absVal);\
2018-09-04 19:32:47 +02:00
printf(" perspectiveScale: %d\n",profile_perspectiveScale);\
printf(" wrap: %d\n",profile_wrap);\
printf(" divRoundDown: %d\n",profile_divRoundDown); }
2018-08-31 15:38:02 +02:00
#else
#define profileCall(c)
#endif
2018-08-23 03:04:52 +02:00
Unit clamp(Unit value, Unit valueMin, Unit valueMax)
{
2018-08-31 15:38:02 +02:00
profileCall(clamp);
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 10:54:22 +02:00
static inline Unit absVal(Unit value)
2018-09-03 08:14:36 +02:00
{
2018-09-04 18:55:18 +02:00
profileCall(absVal);
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 10:54:22 +02:00
static inline Unit wrap(Unit value, Unit mod)
2018-09-03 16:55:30 +02:00
{
2018-09-04 19:32:47 +02:00
profileCall(wrap);
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 10:54:22 +02:00
static inline Unit divRoundDown(Unit value, Unit divisor)
2018-09-03 09:52:50 +02:00
{
2018-09-04 19:32:47 +02:00
profileCall(divRoundDown);
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-02 13:58:46 +02:00
#define trigHelper(x) (((Unit) UNITS_PER_SQUARE) *\
2018-08-23 09:25:34 +02:00
(UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
(UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 + (x) * (x)))
2018-09-12 08:39:52 +02:00
#if USE_COS_LUT == 1
2018-09-15 17:53:16 +02:00
#ifdef RAYCAST_TINY
const Unit cosLUT[64] =
{
16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
};
#else
const Unit cosLUT[64] =
{
1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
-199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
-1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
-199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
};
#endif
2018-09-12 08:39:52 +02:00
#elif USE_COS_LUT == 2
const Unit cosLUT[128] =
{
1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
-199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
-822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
-1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
-758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
-100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
};
#endif
2018-08-23 09:25:34 +02:00
Unit cosInt(Unit input)
{
2018-08-31 15:38:02 +02:00
profileCall(cosInt);
2018-09-03 09:52:50 +02:00
// TODO: could be optimized with LUT
2018-09-03 16:55:30 +02:00
input = wrap(input,UNITS_PER_SQUARE);
2018-08-23 09:25:34 +02:00
2018-09-12 08:39:52 +02:00
#if USE_COS_LUT == 1
2018-09-15 17:53:16 +02:00
#ifdef RAYCAST_TINY
return cosLUT[input];
#else
return cosLUT[input / 16];
#endif
2018-09-12 08:39:52 +02:00
#elif USE_COS_LUT == 2
return cosLUT[input / 8];
#else
2018-08-23 09:25:34 +02:00
if (input < UNITS_PER_SQUARE / 4)
return trigHelper(input);
else if (input < UNITS_PER_SQUARE / 2)
return -1 * trigHelper(UNITS_PER_SQUARE / 2 - input);
else if (input < 3 * UNITS_PER_SQUARE / 4)
return -1 * trigHelper(input - UNITS_PER_SQUARE / 2);
else
return trigHelper(UNITS_PER_SQUARE - input);
2018-09-12 08:39:52 +02:00
#endif
2018-08-23 09:25:34 +02:00
}
#undef trigHelper
Unit sinInt(Unit input)
{
2018-08-23 09:35:10 +02:00
return cosInt(input - UNITS_PER_SQUARE / 4);
}
Vector2D angleToDirection(Unit angle)
{
2018-08-31 15:38:02 +02:00
profileCall(angleToDirection);
2018-08-23 09:35:10 +02:00
Vector2D result;
result.x = cosInt(angle);
result.y = -1 * sinInt(angle);
return result;
2018-08-23 09:25:34 +02:00
}
2018-09-13 15:22:16 +02:00
uint16_t sqrtInt(Unit value)
2018-08-23 00:50:19 +02:00
{
2018-08-31 15:38:02 +02:00
profileCall(sqrtInt);
2018-09-02 13:58:46 +02:00
#ifdef 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;
}
Unit dist(Vector2D p1, Vector2D p2)
{
2018-08-31 15:38:02 +02:00
profileCall(dist);
2018-09-13 15:22:16 +02:00
Unit dx = p2.x - p1.x;
Unit dy = p2.y - p1.y;
2018-08-30 11:15:52 +02:00
2018-09-14 14:40:31 +02:00
#if USE_DIST_APPROX == 2
// octagonal approximation
dx = absVal(dx);
dy = absVal(dy);
return dy > dx ? dx / 2 + dy : dy / 2 + dx;
#elif USE_DIST_APPROX == 1
// more accurate approximation
2018-09-14 12:25:59 +02:00
Unit a, b, result;
dx = dx < 0 ? -1 * dx : dx;
dy = dy < 0 ? -1 * dy : dy;
if (dx < dy)
{
a = dy;
b = dx;
}
else
{
a = dx;
b = dy;
}
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-13 15:22:16 +02:00
return sqrtInt((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-08-30 14:44:14 +02:00
Unit len(Vector2D v)
{
2018-08-31 15:38:02 +02:00
profileCall(len);
2018-09-14 15:18:34 +02:00
Vector2D zero;
zero.x = 0;
zero.y = 0;
2018-08-30 14:44:14 +02:00
2018-09-14 15:18:34 +02:00
return dist(zero,v);
2018-08-30 14:44:14 +02:00
}
2018-09-17 10:54:22 +02:00
static inline int8_t pointIsLeftOfRay(Vector2D point, Ray ray)
2018-08-21 13:49:45 +02:00
{
2018-08-31 15:38:02 +02:00
profileCall(pointIsLeftOfRay);
2018-08-31 16:46:55 +02:00
Unit dX = point.x - ray.start.x;
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-12 08:19:05 +02:00
void castRaySquare(Ray *localRay, Vector2D *nextCellOff, Vector2D *collOff)
2018-08-21 13:49:45 +02:00
{
2018-08-31 15:38:02 +02:00
profileCall(castRaySquare);
2018-08-31 13:29:50 +02:00
nextCellOff->x = 0;
nextCellOff->y = 0;
2018-08-21 13:49:45 +02:00
2018-09-12 08:19:05 +02:00
Ray criticalLine;
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-13 15:22:16 +02:00
(((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-12 08:19:05 +02:00
if (pointIsLeftOfRay(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-08-31 10:59:32 +02:00
criticalLine.start.x = 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-08-31 10:59:32 +02:00
criticalLine.start.y = 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-08-31 10:59:32 +02:00
criticalLine.start.y = 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-05 16:26:13 +02:00
void castRayMultiHit(Ray ray, ArrayFunction arrayFunc, ArrayFunction typeFunc,
HitResult *hitResults, uint16_t *hitResultsLen, RayConstraints constraints)
2018-08-22 08:21:38 +02:00
{
2018-08-31 15:38:02 +02:00
profileCall(castRayMultiHit);
2018-08-23 00:50:19 +02:00
Vector2D initialPos = ray.start;
2018-08-23 02:36:51 +02:00
Vector2D currentPos = ray.start;
Vector2D currentSquare;
2018-08-23 00:50:19 +02:00
2018-09-03 09:52:50 +02:00
currentSquare.x = divRoundDown(ray.start.x, UNITS_PER_SQUARE);
currentSquare.y = divRoundDown(ray.start.y,UNITS_PER_SQUARE);
2018-09-02 21:43:44 +02:00
2018-08-23 02:36:51 +02:00
*hitResultsLen = 0;
2018-09-05 11:35:52 +02:00
Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
2018-08-23 00:50:19 +02:00
2018-08-30 14:44:14 +02:00
Vector2D no, co; // next cell offset, collision offset
2018-08-31 13:29:50 +02:00
no.x = 0; // just to supress a warning
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-05 11:35:52 +02:00
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
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;
h.distance = dist(initialPos,currentPos);
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-16 14:24:32 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
h.textureCoord = wrap(currentPos.x,UNITS_PER_SQUARE);
#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-16 14:24:32 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
h.textureCoord =
wrap(UNITS_PER_SQUARE - currentPos.y,UNITS_PER_SQUARE);
#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-16 14:24:32 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
h.textureCoord =
wrap(UNITS_PER_SQUARE - currentPos.x,UNITS_PER_SQUARE);
#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-16 14:24:32 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
h.textureCoord = wrap(currentPos.y,UNITS_PER_SQUARE);
#endif
2018-09-03 14:23:11 +02:00
}
2018-08-30 14:44:14 +02:00
2018-09-16 14:24:32 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
2018-09-14 20:17:15 +02:00
if (_rollFunction != 0)
h.doorRoll = _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-03 16:55:30 +02:00
ray.start.x = wrap(currentPos.x,UNITS_PER_SQUARE);
ray.start.y = wrap(currentPos.y,UNITS_PER_SQUARE);
2018-08-22 08:21:38 +02:00
2018-09-12 08:19:05 +02:00
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-08-31 13:29:50 +02:00
HitResult castRay(Ray ray, ArrayFunction arrayFunc)
2018-08-23 02:36:51 +02:00
{
2018-08-31 15:38:02 +02:00
profileCall(castRay);
2018-08-23 02:36:51 +02:00
HitResult result;
2018-09-14 15:18:34 +02:00
uint16_t len;
2018-08-31 13:29:50 +02:00
RayConstraints c;
c.maxSteps = 1000;
c.maxHits = 1;
2018-08-23 02:36:51 +02:00
2018-09-05 16:26:13 +02:00
castRayMultiHit(ray,arrayFunc,0,&result,&len,c);
2018-08-23 02:36:51 +02:00
if (len == 0)
result.distance = -1;
2018-08-22 08:21:38 +02:00
return result;
}
2018-08-31 18:11:32 +02:00
void castRaysMultiHit(Camera cam, ArrayFunction arrayFunc,
2018-09-05 16:26:13 +02:00
ArrayFunction typeFunction, ColumnFunction columnFunc,
RayConstraints constraints)
2018-08-30 08:51:53 +02:00
{
2018-09-08 07:23:21 +02:00
Vector2D dir1 = angleToDirection(cam.direction - HORIZONTAL_FOV_HALF);
Vector2D dir2 = angleToDirection(cam.direction + HORIZONTAL_FOV_HALF);
2018-08-30 08:51:53 +02:00
Unit dX = dir2.x - dir1.x;
Unit dY = dir2.y - dir1.y;
2018-08-31 13:29:50 +02:00
HitResult hits[constraints.maxHits];
2018-08-31 11:51:03 +02:00
uint16_t hitCount;
2018-08-30 08:51:53 +02:00
Ray r;
2018-08-31 13:29:50 +02:00
r.start = cam.position;
2018-08-30 08:51:53 +02:00
2018-09-15 17:56:59 +02:00
Unit currentDX = 0;
Unit currentDY = 0;
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-05 16:26:13 +02:00
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-05 11:35:52 +02:00
Unit _floorCeilFunction(int16_t x, int16_t y)
{
// TODO: adjust also for RAYCAST_TINY
Unit f = _floorFunction(x,y);
2018-09-08 18:39:47 +02:00
if (_ceilFunction == 0)
return f;
Unit c = _ceilFunction(x,y);
2018-09-05 11:35:52 +02:00
2018-09-13 16:13:59 +02:00
#ifndef 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-14 17:46:31 +02:00
Unit _floorHeightNotZeroFunction(int16_t x, int16_t y)
{
2018-09-14 19:42:59 +02:00
return _floorFunction(x,y) == 0 ? 0 :
(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-12 08:19:05 +02:00
Unit adjustDistance(Unit distance, Camera *camera, 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). */
Unit result =
(distance *
2018-09-12 08:19:05 +02:00
vectorsAngleCos(angleToDirection(camera->direction),ray->direction)) /
2018-09-11 08:09:34 +02:00
UNITS_PER_SQUARE;
return result == 0 ? 1 : result;
// ^ prevent division by zero
}
2018-08-31 18:26:51 +02:00
void _columnFunction(HitResult *hits, uint16_t hitCount, uint16_t x, Ray ray)
{
2018-09-13 11:16:28 +02:00
// last written Y position, can never go backwards
2018-09-13 15:22:16 +02:00
Unit fPosY = _camera.resolution.y;
Unit cPosY = -1;
2018-09-13 10:09:21 +02:00
2018-09-13 11:16:28 +02:00
// world coordinates
Unit fZ1World = _startFloorHeight;
Unit cZ1World = _startCeilHeight;
2018-09-01 09:46:19 +02:00
2018-09-01 12:04:29 +02:00
PixelInfo p;
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-13 15:22:16 +02:00
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-13 15:22:16 +02:00
for (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-13 11:16:28 +02:00
HitResult hit;
Unit distance;
2018-09-01 12:04:29 +02:00
2018-09-15 12:10:53 +02:00
Unit fWallHeight = 0, cWallHeight = 0;
Unit fZ2World = 0, cZ2World = 0;
Unit fZ1Screen = 0, cZ1Screen = 0;
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];
distance = adjustDistance(hit.distance,&_camera,&ray);
2018-09-03 14:23:11 +02:00
2018-09-13 11:16:28 +02:00
fWallHeight = _floorFunction(hit.square.x,hit.square.y);
fZ2World = fWallHeight - _camera.height;
fZ1Screen = _middleRow - perspectiveScale(
(fZ1World * _camera.resolution.y) / UNITS_PER_SQUARE,distance);
fZ2Screen = _middleRow - perspectiveScale(
(fZ2World * _camera.resolution.y) / UNITS_PER_SQUARE,distance);
2018-09-05 19:49:39 +02:00
2018-09-13 11:16:28 +02:00
if (_ceilFunction != 0)
{
cWallHeight = _ceilFunction(hit.square.x,hit.square.y);
cZ2World = cWallHeight - _camera.height;
cZ1Screen = _middleRow - perspectiveScale(
(cZ1World * _camera.resolution.y) / UNITS_PER_SQUARE,distance);
cZ2Screen = _middleRow - perspectiveScale(
(cZ2World * _camera.resolution.y) / UNITS_PER_SQUARE,distance);
}
}
else
{
fZ1Screen = _middleRow;
cZ1Screen = _middleRow + 1;
2018-09-12 13:45:08 +02:00
}
2018-09-02 23:26:02 +02:00
2018-09-13 11:16:28 +02:00
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)\
p.depth += absVal(pref##Z1World) * VERTICAL_DEPTH_MULTIPLY;\
limit = clamp(pref##Z1Screen,l1,l2);\
for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\
{\
p.position.y = i;\
p.depth += _horizontalDepthStep;\
2018-09-17 07:15:06 +02:00
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;
p.depth = (_fHorizontalDepthStart - fPosY) * _horizontalDepthStep;
drawHorizontal(f,cPosY + 1,_camera.resolution.y,>,-)
// ^ purposfully allow outside screen bounds here
2018-09-01 09:55:35 +02:00
2018-09-13 11:16:28 +02:00
if (_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;
p.depth = (cPosY - _cHorizontalDepthStart) * _horizontalDepthStep;
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)\
{\
limit = clamp(pref##Z2Screen,l1,l2);\
Unit wallLength = pref##Z2Screen - pref##Z1Screen - 1;\
wallLength = wallLength != 0 ? wallLength : 1;\
2018-09-13 19:00:28 +02:00
Unit wallPosition = absVal(pref##Z1Screen - pref##PosY) inc (-1);\
2018-09-16 17:23:57 +02:00
Unit coordStep = COMPUTE_WALL_TEXCOORDS ? \
UNITS_PER_SQUARE / wallLength : 1;\
p.texCoords.y = COMPUTE_WALL_TEXCOORDS ?\
wallPosition * coordStep : 0;\
2018-09-16 17:21:34 +02:00
if (coordStep < MIN_TEXTURE_STEP) /* two versions of the loop */ \
for (i = pref##PosY inc 1; i comp##= limit; inc##inc i)\
{ /* more expensive texture coord computing */\
p.position.y = i;\
p.hit = hit;\
if (COMPUTE_WALL_TEXCOORDS == 1)\
{\
p.texCoords.x = hit.textureCoord;\
p.texCoords.y = (wallPosition * UNITS_PER_SQUARE)/wallLength;\
}\
wallPosition++;\
2018-09-17 07:15:06 +02:00
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;\
if (COMPUTE_WALL_TEXCOORDS == 1)\
{\
p.texCoords.x = hit.textureCoord;\
p.texCoords.y += coordStep;\
}\
2018-09-17 07:15:06 +02:00
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;
drawVertical(f,cPosY + 1,_camera.resolution.y,>,-)
} // ^ 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-13 11:16:28 +02:00
if (_ceilFunction != 0 && cPosY < _camResYLimit) // still 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-11 08:09:34 +02:00
void _columnFunctionSimple(HitResult *hits, uint16_t hitCount, uint16_t x,
Ray ray)
{
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;
int16_t wallStart = _middleRow;
int16_t wallEnd = _middleRow;
int16_t heightOffset = 0;
2018-09-11 08:09:34 +02:00
Unit dist = 1;
PixelInfo p;
p.position.x = x;
if (hitCount > 0)
{
HitResult hit = hits[0];
2018-09-14 19:42:59 +02:00
uint8_t goOn = 1;
2018-09-16 14:24:32 +02:00
if (_rollFunction != 0 && 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 :
hit.textureCoord > 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)
{
dist = adjustDistance(hit.distance,&_camera,&ray);
2018-09-14 17:46:31 +02:00
2018-09-14 19:42:59 +02:00
int16_t wallHeightWorld = _floorFunction(hit.square.x,hit.square.y);
2018-09-14 17:46:31 +02:00
2018-09-14 19:42:59 +02:00
wallHeightScreen = perspectiveScale((wallHeightWorld *
_camera.resolution.y) / UNITS_PER_SQUARE,dist);
2018-09-11 08:09:34 +02:00
2018-09-15 14:48:01 +02:00
int16_t normalizedWallHeight = wallHeightWorld != 0 ?
((UNITS_PER_SQUARE * wallHeightScreen) / wallHeightWorld) : 0;
2018-09-14 17:46:31 +02:00
2018-09-14 19:42:59 +02:00
heightOffset = perspectiveScale(_cameraHeightScreen,dist);
2018-09-11 08:24:48 +02:00
2018-09-14 19:42:59 +02:00
wallStart = _middleRow - wallHeightScreen + heightOffset +
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-14 19:42:59 +02:00
wallEnd = clamp(wallStart + wallHeightScreen,0,_camResYLimit);
wallStart = clamp(wallStart,0,_camResYLimit);
}
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 07:15:06 +02:00
PIXEL_FUNCTION(&p);
2018-09-11 08:09:34 +02:00
++y;
2018-09-13 11:02:01 +02:00
p.depth += _horizontalDepthStep;
2018-09-11 08:09:34 +02:00
}
// draw wall
p.isWall = 1;
p.isFloor = 1;
p.depth = dist;
2018-09-16 14:24:32 +02:00
#if ROLL_TEXTURE_COORDS == 1 && COMPUTE_WALL_TEXCOORDS == 1
2018-09-15 11:56:20 +02:00
p.hit.textureCoord -= p.hit.doorRoll;
#endif
2018-09-16 17:12:09 +02:00
Unit coordStep = 1;
2018-09-11 08:09:34 +02:00
2018-09-16 14:24:32 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
2018-09-16 17:12:09 +02:00
p.texCoords.x = p.hit.textureCoord;
coordStep = UNITS_PER_SQUARE / wallHeightScreen;
p.texCoords.y = coordStep * coordHelper;
2018-09-16 14:24:32 +02:00
#endif
2018-09-11 08:09:34 +02:00
2018-09-16 17:12:09 +02:00
if (coordStep < MIN_TEXTURE_STEP) /* instead of branching inside a critical
loop, have two versions of the loop and
branch early (here) */
{
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;
#if COMPUTE_WALL_TEXCOORDS == 1
p.texCoords.y = (UNITS_PER_SQUARE * coordHelper) / wallHeightScreen;
#endif
2018-09-17 07:15:06 +02:00
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 07:15:06 +02:00
PIXEL_FUNCTION(&p);
2018-09-16 17:12:09 +02:00
#if COMPUTE_WALL_TEXCOORDS == 1
p.texCoords.y += coordStep;
#endif
++y;
}
2018-09-11 08:09:34 +02:00
}
// draw floor
p.isWall = 0;
2018-09-17 12:06:14 +02:00
p.depth = (_middleRow - y) * _horizontalDepthStep;
2018-09-11 08:09:34 +02:00
while (y < _camera.resolution.y)
{
p.position.y = y;
2018-09-17 07:15:06 +02:00
PIXEL_FUNCTION(&p);
2018-09-11 08:09:34 +02:00
++y;
2018-09-13 11:02:01 +02:00
p.depth -= _horizontalDepthStep;
2018-09-11 08:09:34 +02:00
}
}
2018-09-05 16:26:13 +02:00
void render(Camera cam, ArrayFunction floorHeightFunc,
ArrayFunction ceilingHeightFunc, ArrayFunction typeFunction,
2018-09-17 07:15:06 +02:00
RayConstraints constraints)
2018-08-31 18:26:51 +02:00
{
2018-09-05 11:35:52 +02:00
_floorFunction = floorHeightFunc;
_ceilFunction = ceilingHeightFunc;
2018-08-31 18:26:51 +02:00
_camera = cam;
2018-09-01 13:39:06 +02:00
_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;
_middleRow = halfResY + cam.shear;
2018-09-13 11:02:01 +02:00
_fHorizontalDepthStart = _middleRow + halfResY;
_cHorizontalDepthStart = _middleRow - halfResY;
2018-09-07 20:31:30 +02:00
2018-09-05 11:35:52 +02:00
_startFloorHeight = floorHeightFunc(
divRoundDown(cam.position.x,UNITS_PER_SQUARE),
divRoundDown(cam.position.y,UNITS_PER_SQUARE)) -1 * cam.height;
2018-09-05 12:20:46 +02:00
_startCeilHeight =
ceilingHeightFunc != 0 ?
ceilingHeightFunc(
divRoundDown(cam.position.x,UNITS_PER_SQUARE),
divRoundDown(cam.position.y,UNITS_PER_SQUARE)) -1 * cam.height
: UNIT_INFINITY;
2018-09-01 12:04:29 +02:00
// TODO
2018-09-13 11:02:01 +02:00
_horizontalDepthStep = (12 * UNITS_PER_SQUARE) / cam.resolution.y;
2018-09-01 12:04:29 +02:00
2018-09-05 16:26:13 +02:00
castRaysMultiHit(cam,_floorCeilFunction,typeFunction,
_columnFunction,constraints);
2018-08-31 18:26:51 +02:00
}
2018-09-11 08:09:34 +02:00
void renderSimple(Camera cam, ArrayFunction floorHeightFunc,
2018-09-17 07:15:06 +02:00
ArrayFunction typeFunc, ArrayFunction rollFunc,
2018-09-14 19:42:59 +02:00
RayConstraints constraints)
2018-09-11 08:09:34 +02:00
{
_floorFunction = floorHeightFunc;
_camera = cam;
_camResYLimit = cam.resolution.y - 1;
_middleRow = cam.resolution.y / 2;
2018-09-14 19:42:59 +02:00
_rollFunction = rollFunc;
2018-09-11 08:09:34 +02:00
2018-09-11 08:24:48 +02:00
_cameraHeightScreen =
(_camera.resolution.y * (_camera.height - UNITS_PER_SQUARE)) /
UNITS_PER_SQUARE;
2018-09-11 08:09:34 +02:00
// TODO
2018-09-13 11:02:01 +02:00
_horizontalDepthStep = (12 * UNITS_PER_SQUARE) / cam.resolution.y;
2018-09-11 08:09:34 +02:00
2018-09-14 19:42:59 +02:00
constraints.maxHits =
_rollFunction == 0 ?
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-14 17:46:31 +02:00
castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc,
_columnFunctionSimple, constraints);
2018-09-11 08:09:34 +02:00
}
2018-08-30 18:31:08 +02:00
Vector2D normalize(Vector2D v)
{
2018-08-31 15:38:02 +02:00
profileCall(normalize);
2018-08-30 18:31:08 +02:00
Vector2D result;
Unit l = len(v);
2018-09-04 19:35:30 +02:00
l = l != 0 ? l : 1;
2018-09-02 13:58:46 +02:00
2018-08-30 18:31:08 +02:00
result.x = (v.x * UNITS_PER_SQUARE) / l;
result.y = (v.y * UNITS_PER_SQUARE) / l;
return result;
}
Unit vectorsAngleCos(Vector2D v1, Vector2D v2)
{
2018-08-31 15:38:02 +02:00
profileCall(vectorsAngleCos);
2018-08-30 18:31:08 +02:00
v1 = normalize(v1);
v2 = normalize(v2);
return (v1.x * v2.x + v1.y * v2.y) / UNITS_PER_SQUARE;
}
2018-09-04 11:42:27 +02:00
PixelInfo mapToScreen(Vector2D worldPosition, Unit height, Camera camera)
{
// TODO: precompute some stuff that's constant in the frame
PixelInfo result;
Unit d = dist(worldPosition,camera.position);
Vector2D toPoint;
toPoint.x = worldPosition.x - camera.position.x;
toPoint.y = worldPosition.y - camera.position.y;
Vector2D cameraDir = angleToDirection(camera.direction);
result.depth = // adjusted distance
(d * vectorsAngleCos(cameraDir,toPoint)) / UNITS_PER_SQUARE;
result.position.y = camera.resolution.y / 2 -
(camera.resolution.y *
2018-09-08 07:10:09 +02:00
perspectiveScale(height - camera.height,result.depth)) / UNITS_PER_SQUARE
+ camera.shear;
2018-09-04 11:42:27 +02:00
Unit middleColumn = camera.resolution.x / 2;
Unit a = sqrtInt(d * d - result.depth * result.depth);
Ray r;
r.start = camera.position;
r.direction = cameraDir;
if (!pointIsLeftOfRay(worldPosition,r))
a *= -1;
2018-09-08 07:23:21 +02:00
Unit cos = cosInt(HORIZONTAL_FOV_HALF);
2018-09-04 11:42:27 +02:00
Unit b =
2018-09-11 09:07:24 +02:00
(result.depth * sinInt(HORIZONTAL_FOV_HALF)) / (cos == 0 ? 1 : cos);
// 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-08-30 08:51:53 +02:00
Unit degreesToUnitsAngle(int16_t degrees)
{
return (degrees * UNITS_PER_SQUARE) / 360;
}
2018-09-03 16:38:49 +02:00
Unit perspectiveScale(Unit originalSize, Unit distance)
2018-08-30 08:51:53 +02:00
{
2018-09-04 18:55:18 +02:00
profileCall(perspectiveScale);
2018-09-04 19:32:47 +02:00
return distance != 0 ?
(originalSize * UNITS_PER_SQUARE) /
((VERTICAL_FOV * 2 * distance) / UNITS_PER_SQUARE)
: 0;
2018-08-30 08:51:53 +02:00
}
2018-09-06 17:41:09 +02:00
void moveCameraWithCollision(Camera *camera, Vector2D planeOffset,
Unit heightOffset, ArrayFunction floorHeightFunc,
2018-09-12 07:55:29 +02:00
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
{
Vector2D corner; // BBox corner in the movement direction
Vector2D cornerNew;
int16_t xDir = planeOffset.x > 0 ? 1 : (planeOffset.x < 0 ? -1 : 0);
int16_t yDir = planeOffset.y > 0 ? 1 : (planeOffset.y < 0 ? -1 : 0);
2018-09-08 07:52:55 +02:00
corner.x = camera->position.x + xDir * CAMERA_COLL_RADIUS;
corner.y = camera->position.y + yDir * CAMERA_COLL_RADIUS;
2018-09-06 17:41:09 +02:00
2018-09-08 07:28:47 +02:00
int16_t xSquare = divRoundDown(corner.x,UNITS_PER_SQUARE);
int16_t ySquare = divRoundDown(corner.y,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-08 07:28:47 +02:00
xSquareNew = divRoundDown(cornerNew.x,UNITS_PER_SQUARE);
ySquareNew = divRoundDown(cornerNew.y,UNITS_PER_SQUARE);
2018-09-06 17:41:09 +02:00
2018-09-08 07:52:55 +02:00
Unit bottomLimit = camera->height - CAMERA_COLL_HEIGHT_BELOW +
2018-09-08 11:11:43 +02:00
CAMERA_COLL_STEP_HEIGHT;
2018-09-08 07:52:55 +02:00
Unit topLimit = camera->height + 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-08 07:28:47 +02:00
Unit height = floorHeightFunc(s1,s2);\
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\
dir##Collides = floorHeightFunc(s1,s2) > 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 */ \
int16_t dir2##Square2 = divRoundDown(corner.dir2 - dir2##Dir *\
2018-09-08 10:20:11 +02:00
CAMERA_COLL_RADIUS * 2,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)\
cornerNew.dir = (dir##Square) * UNITS_PER_SQUARE + UNITS_PER_SQUARE / 2\
+ dir##Dir * (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-08 07:52:55 +02:00
camera->position.x = cornerNew.x - xDir * CAMERA_COLL_RADIUS;
camera->position.y = cornerNew.y - yDir * 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-08 17:55:46 +02:00
int16_t xSquare1 =
divRoundDown(camera->position.x - CAMERA_COLL_RADIUS,UNITS_PER_SQUARE);
int16_t xSquare2 =
divRoundDown(camera->position.x + CAMERA_COLL_RADIUS,UNITS_PER_SQUARE);
int16_t ySquare1 =
divRoundDown(camera->position.y - CAMERA_COLL_RADIUS,UNITS_PER_SQUARE);
int16_t ySquare2 =
divRoundDown(camera->position.y + CAMERA_COLL_RADIUS,UNITS_PER_SQUARE);
Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1);
2018-09-09 16:29:48 +02:00
Unit topLimit = ceilingHeightFunc != 0 ?
ceilingHeightFunc(xSquare1,ySquare1) : UNIT_INFINITY;
2018-09-08 17:55:46 +02:00
Unit height;
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 ?\
ceilingHeightFunc(xSquare##s1,ySquare##s2) : UNIT_INFINITY;\
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-08 07:28:47 +02:00
camera->height = clamp(camera->height,
2018-09-08 17:55:46 +02:00
bottomLimit + CAMERA_COLL_HEIGHT_BELOW,
topLimit - 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-15 14:35:21 +02:00
void initCamera(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 = UNITS_PER_SQUARE;
}
void initRayConstraints(RayConstraints *constraints)
{
constraints->maxHits = 1;
constraints->maxSteps = 20;
}
2018-08-23 02:46:40 +02:00
#endif