2018-08-23 02:46:40 +02:00
|
|
|
#ifndef RAYCASTLIB_H
|
|
|
|
#define RAYCASTLIB_H
|
|
|
|
|
2018-08-21 13:49:45 +02:00
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
/**
|
|
|
|
author: Miloslav "drummyfish" Ciz
|
|
|
|
license: CC0
|
|
|
|
|
|
|
|
- Game field's bottom left corner is at [0,0].
|
|
|
|
- X axis goes right.
|
|
|
|
- Y axis goes up.
|
|
|
|
- Each game square is UNITS_PER_SQUARE * UNITS_PER_SQUARE.
|
2018-08-23 09:35:10 +02:00
|
|
|
- Angles are in Units, 0 means pointing right (x+) and positively rotates
|
|
|
|
clockwise, a full angle has UNITS_PER_SQUARE Units.
|
2018-08-21 13:49:45 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define UNITS_PER_SQUARE 1024
|
|
|
|
|
2018-08-23 00:50:19 +02:00
|
|
|
typedef int32_t Unit; /**< Smallest spatial unit, there is UNITS_PER_SQUARE
|
2018-08-21 13:49:45 +02:00
|
|
|
units in a square's length. */
|
|
|
|
|
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);\
|
|
|
|
printf(" dist: %d", h.distance);\
|
2018-08-23 09:35:10 +02:00
|
|
|
printf(" texcoord: %d", h.textureCoord);}\
|
2018-08-23 03:04:52 +02:00
|
|
|
|
2018-08-21 13:49:45 +02:00
|
|
|
/// Position in 2D space.
|
|
|
|
typedef struct
|
|
|
|
{
|
2018-08-23 00:50:19 +02:00
|
|
|
int32_t y;
|
|
|
|
int32_t x;
|
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
|
|
|
|
{
|
|
|
|
Vector2D square; ///< Collided square coordinates.
|
|
|
|
Vector2D position; ///< Exact collision position in Units.
|
|
|
|
Unit distance; /**< Euclidean distance to the hit position, or -1 if
|
|
|
|
no collision happened. */
|
2018-08-23 00:50:19 +02:00
|
|
|
Unit textureCoord; /**< Normalized (0 to UNITS_PER_SQUARE - 1) texture
|
|
|
|
coordinate. */
|
2018-08-22 08:21:38 +02:00
|
|
|
} HitResult;
|
|
|
|
|
2018-08-23 00:50:19 +02:00
|
|
|
/**
|
|
|
|
Casts a single ray and returns the first collision result.
|
|
|
|
|
|
|
|
@param Ray Ray to be cast.
|
|
|
|
@param arrayFunc Function that for x and y array coordinates (in squares, NOT
|
|
|
|
Units) returns a type of square (just a number) - transition
|
|
|
|
between two squares of different types (values) is considered
|
|
|
|
a collision).
|
|
|
|
@param maxSteps Maximum number of steps (in squares) to trace the ray.
|
|
|
|
@return The first collision result.
|
|
|
|
*/
|
|
|
|
|
|
|
|
HitResult castRay(Ray ray, int16_t (*arrayFunc)(int16_t, int16_t),
|
|
|
|
uint16_t maxSteps);
|
2018-08-21 13:49:45 +02:00
|
|
|
|
2018-08-23 02:36:51 +02:00
|
|
|
/**
|
|
|
|
Casts a single ray and returns a list of collisions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void castRayMultiHit(Ray ray, int16_t (*arrayFunc)(int16_t, int16_t),
|
|
|
|
uint16_t maxSteps, HitResult *hitResults, uint16_t *hitResultsLen,
|
|
|
|
uint16_t maxHits);
|
|
|
|
|
2018-08-23 09:35:10 +02:00
|
|
|
Vector2D angleToDirection(Unit angle);
|
2018-08-23 09:25:34 +02:00
|
|
|
Unit cosInt(Unit input);
|
|
|
|
Unit sinInt(Unit input);
|
|
|
|
uint16_t sqrtInt(uint32_t value);
|
|
|
|
Unit dist(Vector2D p1, Vector2D p2);
|
|
|
|
|
2018-08-21 13:49:45 +02:00
|
|
|
//=============================================================================
|
|
|
|
// privates
|
|
|
|
|
2018-08-23 03:04:52 +02:00
|
|
|
Unit clamp(Unit value, Unit valueMin, Unit valueMax)
|
|
|
|
{
|
|
|
|
if (value < valueMin)
|
|
|
|
return valueMin;
|
|
|
|
|
|
|
|
if (value > valueMax)
|
|
|
|
return valueMax;
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2018-08-23 09:25:34 +02:00
|
|
|
// Bhaskara's cosine approximation formula
|
|
|
|
#define trigHelper(x) (UNITS_PER_SQUARE *\
|
|
|
|
(UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
|
|
|
|
(UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 + (x) * (x)))
|
|
|
|
|
|
|
|
Unit cosInt(Unit input)
|
|
|
|
{
|
|
|
|
input = input % UNITS_PER_SQUARE;
|
|
|
|
|
|
|
|
if (input < 0)
|
|
|
|
input = UNITS_PER_SQUARE + input;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
#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)
|
|
|
|
{
|
|
|
|
Vector2D result;
|
|
|
|
|
|
|
|
result.x = cosInt(angle);
|
|
|
|
result.y = -1 * sinInt(angle);
|
|
|
|
|
|
|
|
return result;
|
2018-08-23 09:25:34 +02:00
|
|
|
}
|
|
|
|
|
2018-08-23 00:50:19 +02:00
|
|
|
uint16_t sqrtInt(uint32_t value)
|
|
|
|
{
|
|
|
|
uint32_t result = 0;
|
|
|
|
|
|
|
|
uint32_t a = value;
|
|
|
|
uint32_t b = 1u << 30;
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
Unit dx = p2.x - p1.x;
|
|
|
|
Unit dy = p2.y - p1.y;
|
|
|
|
return sqrtInt(((uint16_t) dx * dx) + ((uint16_t) dy * dy));
|
|
|
|
}
|
2018-08-21 13:49:45 +02:00
|
|
|
|
|
|
|
int8_t pointIsLeftOfRay(Vector2D point, Ray ray)
|
|
|
|
{
|
|
|
|
int dX = point.x - ray.start.x;
|
|
|
|
int dY = point.y - ray.start.y;
|
|
|
|
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-08-21 13:49:45 +02:00
|
|
|
*/
|
2018-08-22 08:21:38 +02:00
|
|
|
void castRaySquare(Ray localRay, Vector2D *nextCellOffset,
|
2018-08-21 13:49:45 +02:00
|
|
|
Vector2D *collisionPointOffset)
|
|
|
|
{
|
|
|
|
nextCellOffset->x = 0;
|
|
|
|
nextCellOffset->y = 0;
|
|
|
|
|
2018-08-22 08:21:38 +02:00
|
|
|
Ray criticalLine = localRay;
|
2018-08-21 13:49:45 +02:00
|
|
|
|
|
|
|
#define helper(c1,c2,n)\
|
|
|
|
{\
|
|
|
|
nextCellOffset->c1 = n;\
|
2018-08-22 08:21:38 +02:00
|
|
|
collisionPointOffset->c1 = criticalLine.start.c1 - localRay.start.c1;\
|
2018-08-23 03:04:52 +02:00
|
|
|
collisionPointOffset->c2 = clamp(\
|
2018-08-22 08:21:38 +02:00
|
|
|
(collisionPointOffset->c1 * localRay.direction.c2) /\
|
2018-08-23 03:04:52 +02:00
|
|
|
(localRay.direction.c1 == 0 ? 1 : localRay.direction.c1),\
|
|
|
|
0,UNITS_PER_SQUARE - 1);\
|
2018-08-21 13:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#define helper2(n1,n2,c)\
|
2018-08-22 08:21:38 +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-08-22 08:21:38 +02:00
|
|
|
if (localRay.direction.x > 0)
|
2018-08-21 13:49:45 +02:00
|
|
|
{
|
2018-08-22 08:21:38 +02:00
|
|
|
criticalLine.start.x = UNITS_PER_SQUARE;
|
2018-08-21 13:49:45 +02:00
|
|
|
|
2018-08-22 08:21:38 +02:00
|
|
|
if (localRay.direction.y > 0)
|
2018-08-21 13:49:45 +02:00
|
|
|
{
|
|
|
|
// top right
|
2018-08-22 08:21:38 +02:00
|
|
|
criticalLine.start.y = UNITS_PER_SQUARE;
|
2018-08-21 13:49:45 +02:00
|
|
|
helper2(1,1,1)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// bottom right
|
2018-08-22 08:21:38 +02:00
|
|
|
criticalLine.start.y = -1;
|
2018-08-21 13:49:45 +02:00
|
|
|
helper2(-1,1,0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-08-22 08:21:38 +02:00
|
|
|
criticalLine.start.x = -1;
|
2018-08-21 13:49:45 +02:00
|
|
|
|
2018-08-22 08:21:38 +02:00
|
|
|
if (localRay.direction.y > 0)
|
2018-08-21 13:49:45 +02:00
|
|
|
{
|
|
|
|
// top left
|
2018-08-22 08:21:38 +02:00
|
|
|
criticalLine.start.y = UNITS_PER_SQUARE;
|
2018-08-21 13:49:45 +02:00
|
|
|
helper2(1,-1,0)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// bottom left
|
2018-08-22 08:21:38 +02:00
|
|
|
criticalLine.start.y = -1;
|
2018-08-21 13:49:45 +02:00
|
|
|
helper2(-1,-1,1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef helper2
|
|
|
|
#undef helper
|
|
|
|
}
|
|
|
|
|
2018-08-23 02:36:51 +02:00
|
|
|
void castRayMultiHit(Ray ray, int16_t (*arrayFunc)(int16_t, int16_t),
|
|
|
|
uint16_t maxSteps, HitResult *hitResults, uint16_t *hitResultsLen,
|
|
|
|
uint16_t maxHits)
|
2018-08-22 08:21:38 +02:00
|
|
|
{
|
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-08-23 02:36:51 +02:00
|
|
|
currentSquare.x = ray.start.x / UNITS_PER_SQUARE;
|
|
|
|
currentSquare.y = ray.start.y / UNITS_PER_SQUARE;
|
2018-08-22 08:21:38 +02:00
|
|
|
|
2018-08-23 02:36:51 +02:00
|
|
|
*hitResultsLen = 0;
|
|
|
|
|
|
|
|
int16_t squareType = arrayFunc(currentSquare.x,currentSquare.y);
|
2018-08-23 00:50:19 +02:00
|
|
|
|
2018-08-22 08:21:38 +02:00
|
|
|
for (uint16_t i = 0; i < maxSteps; ++i)
|
|
|
|
{
|
2018-08-23 02:36:51 +02:00
|
|
|
int16_t currentType = arrayFunc(currentSquare.x,currentSquare.y);
|
|
|
|
|
|
|
|
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-08-23 02:36:51 +02:00
|
|
|
h.position = currentPos;
|
|
|
|
h.square = currentSquare;
|
|
|
|
h.distance = dist(initialPos,currentPos);
|
|
|
|
|
|
|
|
hitResults[*hitResultsLen] = h;
|
|
|
|
|
|
|
|
*hitResultsLen += 1;
|
|
|
|
|
|
|
|
squareType = currentType;
|
|
|
|
|
|
|
|
if (*hitResultsLen >= maxHits)
|
|
|
|
break;
|
2018-08-23 00:50:19 +02:00
|
|
|
}
|
|
|
|
|
2018-08-23 02:36:51 +02:00
|
|
|
ray.start.x = currentPos.x % UNITS_PER_SQUARE;
|
|
|
|
ray.start.y = currentPos.y % UNITS_PER_SQUARE;
|
2018-08-22 08:21:38 +02:00
|
|
|
|
|
|
|
Vector2D no, co;
|
|
|
|
|
|
|
|
castRaySquare(ray,&no,&co);
|
|
|
|
|
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-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
|
|
|
}
|
|
|
|
|
|
|
|
HitResult castRay(Ray ray, int16_t (*arrayFunc)(int16_t, int16_t),
|
|
|
|
uint16_t maxSteps)
|
|
|
|
{
|
|
|
|
HitResult result;
|
|
|
|
uint16_t len;
|
|
|
|
|
|
|
|
castRayMultiHit(ray,arrayFunc,maxSteps,&result,&len,1);
|
|
|
|
|
|
|
|
if (len == 0)
|
|
|
|
result.distance = -1;
|
2018-08-22 08:21:38 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-08-23 02:46:40 +02:00
|
|
|
#endif
|