5 raycastlib (RCL) - Small C header-only raycasting library for embedded and
6 low performance computers, such as Arduino. Only uses integer math and stdint
9 Check the defines below to fine-tune accuracy vs performance! Don't forget
10 to compile with optimizations.
12 Before including the library define RCL_PIXEL_FUNCTION to the name of the
13 function (with RCL_PixelFunction signature) that will render your pixels!
15 - All public (and most private) library identifiers start with RCL_.
16 - Game field's bottom left corner is at [0,0].
17 - X axis goes right in the ground plane.
18 - Y axis goes up in the ground plane.
19 - Height means the Z (vertical) coordinate.
20 - Each game square is RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE points.
21 - Angles are in RCL_Units, 0 means pointing right (x+) and positively rotates
22 clockwise. A full angle has RCL_UNITS_PER_SQUARE RCL_Units.
23 - Most things are normalized with RCL_UNITS_PER_SQUARE (sin, cos, vector
24 unit length, texture coordinates etc.).
25 - Screen coordinates are normal: [0,0] = top left, x goes right, y goes down.
27 author: Miloslav "drummyfish" Ciz
31 Version numbering: major.minor[d], id 'd' is appended, this is a
32 in-development version based on the previous stable major.minor version. Two
33 'd' versions with the same version number, .e.g. 1.0d, may be different.
38 #ifndef RCL_RAYCAST_TINY /** Turns on super efficient version of this library.
39 Only use if neccesarry, looks ugly. Also not done
41 #define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a
42 spatial square, i.e. the fixed point
44 typedef int32_t RCL_Unit
; /**< Smallest spatial unit, there is
45 RCL_UNITS_PER_SQUARE units in a square's
46 length. This effectively serves the purpose of
47 a fixed-point arithmetic. */
48 #define RCL_INFINITY 2000000000
50 #define RCL_UNITS_PER_SQUARE 32
51 typedef int16_t RCL_Unit
;
52 #define RCL_INFINITY 30000
53 #define RCL_USE_DIST_APPROX 2
56 #define RCL_U RCL_UNITS_PER_SQUARE ///< shorthand for RCL_UNITS_PER_SQUARE
58 #ifndef RCL_COMPUTE_WALL_TEXCOORDS
59 #define RCL_COMPUTE_WALL_TEXCOORDS 1
62 #ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
63 #define RCL_COMPUTE_FLOOR_TEXCOORDS 0
66 #ifndef RCL_FLOOR_TEXCOORDS_HEIGHT
67 #define RCL_FLOOR_TEXCOORDS_HEIGHT 0 /** If RCL_COMPUTE_FLOOR_TEXCOORDS == 1,
68 this says for what height level the
69 texture coords will be computed for
70 (for simplicity/performance only one
74 #ifndef RCL_USE_COS_LUT
75 #define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
81 #ifndef RCL_USE_DIST_APPROX
82 #define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use:
83 0: none (compute full Euclidean distance)
84 1: accurate approximation
85 2: octagonal approximation (LQ) */
88 #ifndef RCL_RECTILINEAR
89 #define RCL_RECTILINEAR 1 /**< Whether to use rectilinear perspective (normally
90 used), or curvilinear perspective (fish eye). */
93 #ifndef RCL_TEXTURE_VERTICAL_STRETCH
94 #define RCL_TEXTURE_VERTICAL_STRETCH 1 /**< Whether textures should be
95 stretched to wall height (possibly
96 slightly slower if on). */
99 #ifndef RCL_COMPUTE_FLOOR_DEPTH
100 #define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for
101 floor pixels - turns this off if not
105 #ifndef RCL_COMPUTE_CEILING_DEPTH
106 #define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for
110 #ifndef RCL_ROLL_TEXTURE_COORDS
111 #define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
112 roll the texture coordinates along (mostly
113 desired for doors). */
116 #ifndef RCL_VERTICAL_FOV
117 #define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 3)
120 #define RCL_VERTICAL_FOV_TAN (RCL_VERTICAL_FOV * 4) ///< tan approximation
122 #ifndef RCL_HORIZONTAL_FOV
123 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
126 #define RCL_HORIZONTAL_FOV_TAN (RCL_HORIZONTAL_FOV * 4)
128 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
130 #ifndef RCL_CAMERA_COLL_RADIUS
131 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
134 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
135 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
138 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
139 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
142 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
143 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
146 #ifndef RCL_TEXTURE_INTERPOLATION_SCALE
147 #define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed
148 poit vertical texture coord
149 computation. This should be power
150 of two! Higher number can look more
151 accurate but may cause overflow. */
154 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
155 horizon has (the floor
157 approximated with the
160 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
161 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
162 difference when approximating floor/ceil
166 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
167 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
168 #define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions.
169 #define RCL_zeroClamp(x) ((x) * ((x) >= 0))
170 #define RCL_likely(cond) __builtin_expect(!!(cond),1)
171 #define RCL_unlikely(cond) __builtin_expect(!!(cond),0)
173 #define RCL_logV2D(v)\
174 printf("[%d,%d]\n",v.x,v.y);
176 #define RCL_logRay(r){\
179 RCL_logV2D(r.start);\
181 RCL_logV2D(r.direction);}
183 #define RCL_logHitResult(h){\
185 printf(" square: ");\
186 RCL_logV2D(h.square);\
188 RCL_logV2D(h.position);\
189 printf(" dist: %d\n", h.distance);\
190 printf(" dir: %d\n", h.direction);\
191 printf(" texcoord: %d\n", h.textureCoord);}
193 #define RCL_logPixelInfo(p){\
195 printf(" position: ");\
196 RCL_logV2D(p.position);\
197 printf(" texCoord: ");\
198 RCL_logV2D(p.texCoords);\
199 printf(" depth: %d\n", p.depth);\
200 printf(" height: %d\n", p.height);\
201 printf(" wall: %d\n", p.isWall);\
203 RCL_logHitResult(p.hit);\
206 #define RCL_logCamera(c){\
207 printf("camera:\n");\
208 printf(" position: ");\
209 RCL_logV2D(c.position);\
210 printf(" height: %d\n",c.height);\
211 printf(" direction: %d\n",c.direction);\
212 printf(" shear: %d\n",c.shear);\
213 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
216 /// Position in 2D space.
226 RCL_Vector2D direction
;
231 RCL_Unit distance
; /**< Distance to the hit position, or -1 if no
232 collision happened. If RCL_RECTILINEAR != 0, then
233 the distance is perpendicular to the projection
234 plane (fish eye correction), otherwise it is
235 the straight distance to the ray start
237 uint8_t direction
; /**< Direction of hit. The convention for angle
238 units is explained above. */
239 RCL_Unit textureCoord
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
240 texture coordinate (horizontal). */
241 RCL_Vector2D square
; ///< Collided square coordinates.
242 RCL_Vector2D position
; ///< Exact collision position in RCL_Units.
243 RCL_Unit arrayValue
; /** Value returned by array function (most often
244 this will be the floor height). */
245 RCL_Unit type
; /**< Integer identifying type of square (number
246 returned by type function, e.g. texture
248 RCL_Unit doorRoll
; ///< Holds value of door roll.
253 RCL_Vector2D position
;
254 RCL_Unit direction
; // TODO: rename to "angle" to keep consistency
255 RCL_Vector2D resolution
;
256 int16_t shear
; /**< Shear offset in pixels (0 => no shear), can simulate
262 Holds an information about a single rendered pixel (for a pixel function
263 that works as a fragment shader).
267 RCL_Vector2D position
; ///< On-screen position.
268 int8_t isWall
; ///< Whether the pixel is a wall or a floor/ceiling.
269 int8_t isFloor
; ///< Whether the pixel is floor or ceiling.
270 int8_t isHorizon
; ///< If the pixel belongs to horizon segment.
271 RCL_Unit depth
; ///< Corrected depth.
272 RCL_Unit wallHeight
;///< Only for wall pixels, says its height.
273 RCL_Unit height
; ///< World height (mostly for floor).
274 RCL_HitResult hit
; ///< Corresponding ray hit.
275 RCL_Vector2D texCoords
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
276 texture coordinates. */
279 void RCL_PIXEL_FUNCTION (RCL_PixelInfo
*pixel
);
285 } RCL_RayConstraints
;
288 Function used to retrieve some information about cells of the rendered scene.
289 It should return a characteristic of given square as an integer (e.g. square
290 height, texture index, ...) - between squares that return different numbers
291 there is considered to be a collision.
293 This function should be as fast as possible as it will typically be called
296 typedef RCL_Unit (*RCL_ArrayFunction
)(int16_t x
, int16_t y
);
298 TODO: maybe array functions should be replaced by defines of funtion names
299 like with pixelFunc? Could be more efficient than function pointers.
303 Function that renders a single pixel at the display. It is handed an info
304 about the pixel it should draw.
306 This function should be as fast as possible as it will typically be called
309 typedef void (*RCL_PixelFunction
)(RCL_PixelInfo
*info
);
312 (*RCL_ColumnFunction
)(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
316 Simple-interface function to cast a single ray.
318 @return The first collision result.
320 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
);
323 Casts a 3D ray in 3D environment with floor and optional ceiling
324 (ceilingHeightFunc can be 0). This can be useful for hitscan shooting,
325 visibility checking etc.
327 @return normalized ditance (0 to RCL_UNITS_PER_SQUARE) along the ray at which
328 the environment was hit, RCL_UNITS_PER_SQUARE means nothing was hit
330 RCL_Unit
RCL_castRay3D(
331 RCL_Vector2D pos1
, RCL_Unit height1
, RCL_Vector2D pos2
, RCL_Unit height2
,
332 RCL_ArrayFunction floorHeightFunc
, RCL_ArrayFunction ceilingHeightFunc
,
333 RCL_RayConstraints constraints
);
336 Maps a single point in the world to the screen (2D position + depth).
338 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
342 Casts a single ray and returns a list of collisions.
344 @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
345 distance is divided by the ray direction vector length (to correct
347 @param arrayFunc function that will be used to determine collisions (hits)
348 with the ray (squares for which this function returns different values
349 are considered to have a collision between them), this will typically
350 be a function returning floor height
351 @param typeFunc optional (can be 0) function - if provided, it will be used
352 to mark the hit result with the number returned by this function
353 (it can be e.g. a texture index)
354 @param hitResults array in which the hit results will be stored (has to be
355 preallocated with at space for at least as many hit results as
356 maxHits specified with the constraints parameter)
357 @param hitResultsLen in this variable the number of hit results will be
359 @param constraints specifies constraints for the ray cast
361 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
362 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
363 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
);
365 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
);
370 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
371 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
372 RCL_UNITS_PER_SQUARE)
374 RCL_Unit
RCL_cos(RCL_Unit input
);
376 RCL_Unit
RCL_sin(RCL_Unit input
);
378 RCL_Unit
RCL_tan(RCL_Unit input
);
380 RCL_Unit
RCL_ctg(RCL_Unit input
);
382 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
383 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
);
385 /// Computes a cos of an angle between two vectors.
386 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
);
388 uint16_t RCL_sqrt(RCL_Unit value
);
389 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
);
390 RCL_Unit
RCL_len(RCL_Vector2D v
);
393 Converts an angle in whole degrees to an angle in RCL_Units that this library
396 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
);
398 ///< Computes the change in size of an object due to perspective (vertical FOV).
399 RCL_Unit
RCL_perspectiveScaleVertical(RCL_Unit originalSize
, RCL_Unit distance
);
401 RCL_Unit
RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize
,
402 RCL_Unit scaledSize
);
405 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize
, RCL_Unit distance
);
407 RCL_Unit
RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize
,
408 RCL_Unit scaledSize
);
411 Casts rays for given camera view and for each hit calls a user provided
414 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
415 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
416 RCL_RayConstraints constraints
);
419 Using provided functions, renders a complete complex (multilevel) camera
422 This function should render each screen pixel exactly once.
424 function rendering summary:
425 - performance: slower
428 - different wall heights: yes
429 - floor/ceiling textures: no
430 - floor geometry: yes, multilevel
431 - ceiling geometry: yes (optional), multilevel
433 - camera shearing: yes
434 - rendering order: left-to-right, not specifically ordered vertically
436 @param cam camera whose view to render
437 @param floorHeightFunc function that returns floor height (in RCL_Units)
438 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
439 0 (no ceiling will be rendered)
440 @param typeFunction function that says a type of square (e.g. its texture
441 index), can be 0 (no type in hit result)
442 @param pixelFunc callback function to draw a single pixel on screen
443 @param constraints constraints for each cast ray
445 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
446 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
447 RCL_RayConstraints constraints
);
450 Renders given camera view, with help of provided functions. This function is
451 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
454 function rendering summary:
455 - performance: faster
458 - different wall heights: yes
459 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
460 - floor geometry: no (just flat floor, with depth information)
461 - ceiling geometry: no (just flat ceiling, with depth information)
463 - camera shearing: no
464 - rendering order: left-to-right, top-to-bottom
466 Additionally this function supports rendering rolling doors.
468 This function should render each screen pixel exactly once.
470 @param rollFunc function that for given square says its door roll in
471 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
472 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
473 rendering should also be faster as fewer intersections will be tested)
475 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
476 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
477 RCL_RayConstraints constraints
);
480 Function that moves given camera and makes it collide with walls and
481 potentially also floor and ceilings. It's meant to help implement player
484 @param camera camera to move
485 @param planeOffset offset to move the camera in
486 @param heightOffset height offset to move the camera in
487 @param floorHeightFunc function used to retrieve the floor height
488 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
489 (camera won't collide with ceiling)
490 @param computeHeight whether to compute height - if false (0), floor and
491 ceiling functions won't be used and the camera will
492 only collide horizontally with walls (good for simpler
494 @param force if true, forces to recompute collision even if position doesn't
497 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
498 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
499 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
);
501 void RCL_initCamera(RCL_Camera
*camera
);
502 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
);
504 //=============================================================================
507 #define _RCL_UNUSED(what) (void)(what);
509 // global helper variables, for precomputing stuff etc.
510 RCL_Camera _RCL_camera
;
511 RCL_Unit _RCL_horizontalDepthStep
= 0;
512 RCL_Unit _RCL_startFloorHeight
= 0;
513 RCL_Unit _RCL_startCeil_Height
= 0;
514 RCL_Unit _RCL_camResYLimit
= 0;
515 RCL_Unit _RCL_middleRow
= 0;
516 RCL_ArrayFunction _RCL_floorFunction
= 0;
517 RCL_ArrayFunction _RCL_ceilFunction
= 0;
518 RCL_Unit _RCL_fHorizontalDepthStart
= 0;
519 RCL_Unit _RCL_cHorizontalDepthStart
= 0;
520 int16_t _RCL_cameraHeightScreen
= 0;
521 RCL_ArrayFunction _RCL_rollFunction
= 0; // says door rolling
522 RCL_Unit
*_RCL_floorPixelDistances
= 0;
523 RCL_Unit _RCL_fovCorrectionFactors
[2] = {0,0}; //correction for hor/vert fov
525 RCL_Unit
RCL_clamp(RCL_Unit value
, RCL_Unit valueMin
, RCL_Unit valueMax
)
527 if (value
>= valueMin
)
529 if (value
<= valueMax
)
538 static inline RCL_Unit
RCL_abs(RCL_Unit value
)
540 return value
* (((value
>= 0) << 1) - 1);
543 /// Like mod, but behaves differently for negative values.
544 static inline RCL_Unit
RCL_wrap(RCL_Unit value
, RCL_Unit mod
)
546 RCL_Unit cmp
= value
< 0;
547 return cmp
* mod
+ (value
% mod
) - cmp
;
550 /// Performs division, rounding down, NOT towards zero.
551 static inline RCL_Unit
RCL_divRoundDown(RCL_Unit value
, RCL_Unit divisor
)
553 return value
/ divisor
- ((value
>= 0) ? 0 : 1);
556 // Bhaskara's cosine approximation formula
557 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
558 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
559 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
561 #if RCL_USE_COS_LUT == 1
563 #ifdef RCL_RAYCAST_TINY
564 const RCL_Unit cosLUT
[64] =
566 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
569 const RCL_Unit cosLUT
[64] =
571 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
572 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
573 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
574 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
578 #elif RCL_USE_COS_LUT == 2
579 const RCL_Unit cosLUT
[128] =
581 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
582 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
583 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
584 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
585 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
586 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
587 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
588 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
592 RCL_Unit
RCL_cos(RCL_Unit input
)
594 input
= RCL_wrap(input
,RCL_UNITS_PER_SQUARE
);
596 #if RCL_USE_COS_LUT == 1
598 #ifdef RCL_RAYCAST_TINY
599 return cosLUT
[input
];
601 return cosLUT
[input
/ 16];
604 #elif RCL_USE_COS_LUT == 2
605 return cosLUT
[input
/ 8];
607 if (input
< RCL_UNITS_PER_SQUARE
/ 4)
608 return trigHelper(input
);
609 else if (input
< RCL_UNITS_PER_SQUARE
/ 2)
610 return -1 * trigHelper(RCL_UNITS_PER_SQUARE
/ 2 - input
);
611 else if (input
< 3 * RCL_UNITS_PER_SQUARE
/ 4)
612 return -1 * trigHelper(input
- RCL_UNITS_PER_SQUARE
/ 2);
614 return trigHelper(RCL_UNITS_PER_SQUARE
- input
);
620 RCL_Unit
RCL_sin(RCL_Unit input
)
622 return RCL_cos(input
- RCL_UNITS_PER_SQUARE
/ 4);
625 RCL_Unit
RCL_tan(RCL_Unit input
)
627 return (RCL_sin(input
) * RCL_UNITS_PER_SQUARE
) / RCL_nonZero(RCL_cos(input
)
630 return (RCL_sin(input
) * RCL_UNITS_PER_SQUARE
) / RCL_nonZero(RCL_cos(input
));
633 RCL_Unit
RCL_ctg(RCL_Unit input
)
635 return (RCL_cos(input
) * RCL_UNITS_PER_SQUARE
) / RCL_sin(input
);
638 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
)
642 result
.x
= RCL_cos(angle
);
643 result
.y
= -1 * RCL_sin(angle
);
648 uint16_t RCL_sqrt(RCL_Unit value
)
650 #ifdef RCL_RAYCAST_TINY
653 uint16_t b
= 1u << 14;
657 uint32_t b
= 1u << 30;
668 result
= result
+ 2 * b
;
678 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
)
680 RCL_Unit dx
= p2
.x
- p1
.x
;
681 RCL_Unit dy
= p2
.y
- p1
.y
;
683 #if RCL_USE_DIST_APPROX == 2
684 // octagonal approximation
689 return dy
> dx
? dx
/ 2 + dy
: dy
/ 2 + dx
;
690 #elif RCL_USE_DIST_APPROX == 1
691 // more accurate approximation
693 RCL_Unit a
, b
, result
;
695 dx
= ((dx
< 0) * 2 - 1) * dx
;
696 dy
= ((dy
< 0) * 2 - 1) * dy
;
709 result
= a
+ (44 * b
) / 102;
712 result
-= (5 * a
) / 128;
719 return RCL_sqrt((RCL_Unit
) (dx
+ dy
));
723 RCL_Unit
RCL_len(RCL_Vector2D v
)
729 return RCL_dist(zero
,v
);
732 static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point
, RCL_Ray ray
)
734 RCL_Unit dX
= point
.x
- ray
.start
.x
;
735 RCL_Unit dY
= point
.y
- ray
.start
.y
;
736 return (ray
.direction
.x
* dY
- ray
.direction
.y
* dX
) > 0;
737 // ^ Z component of cross-product
740 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
741 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
742 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
)
744 RCL_Vector2D currentPos
= ray
.start
;
745 RCL_Vector2D currentSquare
;
747 currentSquare
.x
= RCL_divRoundDown(ray
.start
.x
,RCL_UNITS_PER_SQUARE
);
748 currentSquare
.y
= RCL_divRoundDown(ray
.start
.y
,RCL_UNITS_PER_SQUARE
);
752 RCL_Unit squareType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
755 RCL_Vector2D nextSideDist
; // dist. from start to the next side in given axis
757 RCL_Vector2D step
; // -1 or 1 for each axis
758 int8_t stepHorizontal
= 0; // whether the last step was hor. or vert.
763 RCL_Unit dirVecLengthNorm
= RCL_len(ray
.direction
) * RCL_UNITS_PER_SQUARE
;
765 delta
.x
= RCL_abs(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.x
));
766 delta
.y
= RCL_abs(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.y
));
770 if (ray
.direction
.x
< 0)
773 nextSideDist
.x
= (RCL_wrap(ray
.start
.x
,RCL_UNITS_PER_SQUARE
) * delta
.x
) /
774 RCL_UNITS_PER_SQUARE
;
780 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.x
,RCL_UNITS_PER_SQUARE
)) *
781 delta
.x
) / RCL_UNITS_PER_SQUARE
;
784 if (ray
.direction
.y
< 0)
787 nextSideDist
.y
= (RCL_wrap(ray
.start
.y
,RCL_UNITS_PER_SQUARE
) * delta
.y
) /
788 RCL_UNITS_PER_SQUARE
;
794 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.y
,RCL_UNITS_PER_SQUARE
)) *
795 delta
.y
) / RCL_UNITS_PER_SQUARE
;
800 #define RECIP_SCALE 65536
802 RCL_Unit rayDirXRecip
= RECIP_SCALE
/ RCL_nonZero(ray
.direction
.x
);
803 RCL_Unit rayDirYRecip
= RECIP_SCALE
/ RCL_nonZero(ray
.direction
.y
);
804 // ^ we precompute reciprocals to avoid divisions in the loop
806 for (uint16_t i
= 0; i
< constraints
.maxSteps
; ++i
)
808 RCL_Unit currentType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
810 if (RCL_unlikely(currentType
!= squareType
))
816 h
.arrayValue
= currentType
;
818 h
.position
= currentPos
;
819 h
.square
= currentSquare
;
823 h
.position
.x
= currentSquare
.x
* RCL_UNITS_PER_SQUARE
;
829 h
.position
.x
+= RCL_UNITS_PER_SQUARE
;
832 RCL_Unit diff
= h
.position
.x
- ray
.start
.x
;
834 h
.position
.y
= // avoid division by multiplying with reciprocal
835 ray
.start
.y
+ (ray
.direction
.y
* diff
* rayDirXRecip
) / RECIP_SCALE
;
838 /* Here we compute the fish eye corrected distance (perpendicular to
839 the projection plane) as the Euclidean distance (of hit from camera
840 position) divided by the length of the ray direction vector. This can
841 be computed without actually computing Euclidean distances as a
842 hypothenuse A (distance) divided by hypothenuse B (length) is equal to
843 leg A (distance along principal axis) divided by leg B (length along
844 the same principal axis). */
846 #define CORRECT(dir1,dir2)\
847 RCL_Unit tmp = diff / 4; /* 4 to prevent overflow */ \
848 h.distance = ((tmp / 8) != 0) ? /* prevent a bug with small dists */ \
849 ((tmp * RCL_UNITS_PER_SQUARE * rayDir ## dir1 ## Recip) / (RECIP_SCALE / 4)):\
850 RCL_abs(h.position.dir2 - ray.start.dir2);
854 #endif // RCL_RECTILINEAR
858 h
.position
.y
= currentSquare
.y
* RCL_UNITS_PER_SQUARE
;
864 h
.position
.y
+= RCL_UNITS_PER_SQUARE
;
867 RCL_Unit diff
= h
.position
.y
- ray
.start
.y
;
870 ray
.start
.x
+ (ray
.direction
.x
* diff
* rayDirYRecip
) / RECIP_SCALE
;
874 CORRECT(Y
,x
) // same as above but for different axis
878 #endif // RCL_RECTILINEAR
882 h
.distance
= RCL_dist(h
.position
,ray
.start
);
885 h
.type
= typeFunc(currentSquare
.x
,currentSquare
.y
);
887 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
890 case 0: h
.textureCoord
=
891 RCL_wrap(-1 * h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
893 case 1: h
.textureCoord
=
894 RCL_wrap(h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
896 case 2: h
.textureCoord
=
897 RCL_wrap(h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
899 case 3: h
.textureCoord
=
900 RCL_wrap(-1 * h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
902 default: h
.textureCoord
= 0; break;
905 if (_RCL_rollFunction
!= 0)
907 h
.doorRoll
= _RCL_rollFunction(currentSquare
.x
,currentSquare
.y
);
909 if (h
.direction
== 0 || h
.direction
== 1)
917 hitResults
[*hitResultsLen
] = h
;
921 squareType
= currentType
;
923 if (*hitResultsLen
>= constraints
.maxHits
)
929 if (nextSideDist
.x
< nextSideDist
.y
)
931 nextSideDist
.x
+= delta
.x
;
932 currentSquare
.x
+= step
.x
;
937 nextSideDist
.y
+= delta
.y
;
938 currentSquare
.y
+= step
.y
;
944 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
)
946 RCL_HitResult result
;
948 RCL_RayConstraints c
;
953 RCL_castRayMultiHit(ray
,arrayFunc
,0,&result
,&len
,c
);
956 result
.distance
= -1;
961 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
962 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
963 RCL_RayConstraints constraints
)
966 RCL_angleToDirection(cam
.direction
- RCL_HORIZONTAL_FOV_HALF
);
969 RCL_angleToDirection(cam
.direction
+ RCL_HORIZONTAL_FOV_HALF
);
971 /* We scale the side distances so that the middle one is
972 RCL_UNITS_PER_SQUARE, which has to be this way. */
974 RCL_Unit cos
= RCL_nonZero(RCL_cos(RCL_HORIZONTAL_FOV_HALF
));
976 dir1
.x
= (dir1
.x
* RCL_UNITS_PER_SQUARE
) / cos
;
977 dir1
.y
= (dir1
.y
* RCL_UNITS_PER_SQUARE
) / cos
;
979 dir2
.x
= (dir2
.x
* RCL_UNITS_PER_SQUARE
) / cos
;
980 dir2
.y
= (dir2
.y
* RCL_UNITS_PER_SQUARE
) / cos
;
982 RCL_Unit dX
= dir2
.x
- dir1
.x
;
983 RCL_Unit dY
= dir2
.y
- dir1
.y
;
985 RCL_HitResult hits
[constraints
.maxHits
];
989 r
.start
= cam
.position
;
991 RCL_Unit currentDX
= 0;
992 RCL_Unit currentDY
= 0;
994 for (int16_t i
= 0; i
< cam
.resolution
.x
; ++i
)
996 /* Here by linearly interpolating the direction vector its length changes,
997 which in result achieves correcting the fish eye effect (computing
998 perpendicular distance). */
1000 r
.direction
.x
= dir1
.x
+ currentDX
/ cam
.resolution
.x
;
1001 r
.direction
.y
= dir1
.y
+ currentDY
/ cam
.resolution
.x
;
1003 RCL_castRayMultiHit(r
,arrayFunc
,typeFunction
,hits
,&hitCount
,constraints
);
1005 columnFunc(hits
,hitCount
,i
,r
);
1013 Helper function that determines intersection with both ceiling and floor.
1015 RCL_Unit
_RCL_floorCeilFunction(int16_t x
, int16_t y
)
1017 RCL_Unit f
= _RCL_floorFunction(x
,y
);
1019 if (_RCL_ceilFunction
== 0)
1022 RCL_Unit c
= _RCL_ceilFunction(x
,y
);
1024 #ifndef RCL_RAYCAST_TINY
1025 return ((f
& 0x0000ffff) << 16) | (c
& 0x0000ffff);
1027 return ((f
& 0x00ff) << 8) | (c
& 0x00ff);
1031 RCL_Unit
_floorHeightNotZeroFunction(int16_t x
, int16_t y
)
1033 return _RCL_floorFunction(x
,y
) == 0 ? 0 :
1034 RCL_nonZero((x
& 0x00FF) | ((y
& 0x00FF) << 8));
1035 // ^ this makes collisions between all squares - needed for rolling doors
1038 RCL_Unit
RCL_adjustDistance(RCL_Unit distance
, RCL_Camera
*camera
,
1041 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1042 possibly be computed more efficiently by not computing Euclidean
1043 distance at all, but rather compute the distance of the collision
1044 point from the projection plane (line). */
1048 RCL_vectorsAngleCos(RCL_angleToDirection(camera
->direction
),
1049 ray
->direction
)) / RCL_UNITS_PER_SQUARE
;
1051 return RCL_nonZero(result
);
1052 // ^ prevent division by zero
1055 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1056 static inline int16_t _RCL_drawHorizontalColumn(
1059 RCL_Unit limit1
, // TODO: int16_t?
1061 RCL_Unit verticalOffset
,
1063 int8_t computeDepth
,
1064 int8_t computeCoords
,
1065 int16_t depthIncrementMultiplier
,
1067 RCL_PixelInfo
*pixelInfo
1072 RCL_Unit depthIncrement
;
1076 pixelInfo
->isWall
= 0;
1078 int16_t limit
= RCL_clamp(yTo
,limit1
,limit2
);
1080 RCL_Unit depth
= 0; /* TODO: this is for clamping depth to 0 so that we don't
1081 have negative depths, but we should do it more
1082 elegantly and efficiently */
1086 /* for performance reasons have different version of the critical loop
1087 to be able to branch early */
1088 #define loop(doDepth,doCoords)\
1090 if (doDepth) /*constant condition - compiler should optimize it out*/\
1092 depth = pixelInfo->depth + RCL_abs(verticalOffset) *\
1093 RCL_VERTICAL_DEPTH_MULTIPLY;\
1094 depthIncrement = depthIncrementMultiplier *\
1095 _RCL_horizontalDepthStep;\
1097 if (doCoords) /*constant condition - compiler should optimize it out*/\
1099 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1100 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1102 for (int16_t i = yCurrent + increment;\
1103 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1106 pixelInfo->position.y = i;\
1107 if (doDepth) /*constant condition - compiler should optimize it out*/\
1109 depth += depthIncrement;\
1110 pixelInfo->depth = RCL_zeroClamp(depth); \
1111 /* ^ int comparison is fast, it is not braching! (= test instr.) */\
1113 if (doCoords) /*constant condition - compiler should optimize it out*/\
1115 RCL_Unit d = _RCL_floorPixelDistances[i];\
1116 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1117 pixelInfo->texCoords.x =\
1118 _RCL_camera.position.x + ((d * dx) / d2);\
1119 pixelInfo->texCoords.y =\
1120 _RCL_camera.position.y + ((d * dy) / d2);\
1122 RCL_PIXEL_FUNCTION(pixelInfo);\
1126 if (computeDepth
) // branch early
1146 /// Helper for drawing walls. Returns the last drawn pixel position.
1147 static inline int16_t _RCL_drawWall(
1151 RCL_Unit limit1
, // TODO: int16_t?
1155 RCL_PixelInfo
*pixelInfo
1160 height
= RCL_abs(height
);
1162 pixelInfo
->isWall
= 1;
1164 RCL_Unit limit
= RCL_clamp(yTo
,limit1
,limit2
);
1166 RCL_Unit wallLength
= RCL_nonZero(RCL_abs(yTo
- yFrom
- 1));
1168 RCL_Unit wallPosition
= RCL_abs(yFrom
- yCurrent
) - increment
;
1170 RCL_Unit heightScaled
= height
* RCL_TEXTURE_INTERPOLATION_SCALE
;
1171 _RCL_UNUSED(heightScaled
);
1173 RCL_Unit coordStepScaled
= RCL_COMPUTE_WALL_TEXCOORDS
?
1174 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1175 ((RCL_UNITS_PER_SQUARE
* RCL_TEXTURE_INTERPOLATION_SCALE
) / wallLength
)
1177 (heightScaled
/ wallLength
)
1181 pixelInfo
->texCoords
.y
= RCL_COMPUTE_WALL_TEXCOORDS
?
1182 (wallPosition
* coordStepScaled
) : 0;
1186 coordStepScaled
*= -1;
1187 pixelInfo
->texCoords
.y
=
1188 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1189 (RCL_UNITS_PER_SQUARE
* RCL_TEXTURE_INTERPOLATION_SCALE
)
1190 - pixelInfo
->texCoords
.y
;
1192 heightScaled
- pixelInfo
->texCoords
.y
;
1197 // with floor wall, don't start under 0
1198 pixelInfo
->texCoords
.y
= RCL_zeroClamp(pixelInfo
->texCoords
.y
);
1201 RCL_Unit textureCoordScaled
= pixelInfo
->texCoords
.y
;
1203 for (RCL_Unit i
= yCurrent
+ increment
;
1204 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1207 pixelInfo
->position
.y
= i
;
1209 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1210 pixelInfo
->texCoords
.y
=
1211 textureCoordScaled
/ RCL_TEXTURE_INTERPOLATION_SCALE
;
1213 textureCoordScaled
+= coordStepScaled
;
1216 RCL_PIXEL_FUNCTION(pixelInfo
);
1222 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1223 static inline void _RCL_makeInfiniteHit(RCL_HitResult
*hit
, RCL_Ray
*ray
)
1225 hit
->distance
= RCL_UNITS_PER_SQUARE
* RCL_UNITS_PER_SQUARE
;
1226 /* ^ horizon is at infinity, but we can't use too big infinity
1227 (RCL_INFINITY) because it would overflow in the following mult. */
1228 hit
->position
.x
= (ray
->direction
.x
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1229 hit
->position
.y
= (ray
->direction
.y
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1232 hit
->textureCoord
= 0;
1233 hit
->arrayValue
= 0;
1238 void _RCL_columnFunctionComplex(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
1241 // last written Y position, can never go backwards
1242 RCL_Unit fPosY
= _RCL_camera
.resolution
.y
;
1243 RCL_Unit cPosY
= -1;
1245 // world coordinates (relative to camera height though)
1246 RCL_Unit fZ1World
= _RCL_startFloorHeight
;
1247 RCL_Unit cZ1World
= _RCL_startCeil_Height
;
1256 // we'll be simulatenously drawing the floor and the ceiling now
1257 for (RCL_Unit j
= 0; j
<= hitCount
; ++j
)
1258 { // ^ = add extra iteration for horizon plane
1259 int8_t drawingHorizon
= j
== hitCount
;
1262 RCL_Unit distance
= 1;
1264 RCL_Unit fWallHeight
= 0, cWallHeight
= 0;
1265 RCL_Unit fZ2World
= 0, cZ2World
= 0;
1266 RCL_Unit fZ1Screen
= 0, cZ1Screen
= 0;
1267 RCL_Unit fZ2Screen
= 0, cZ2Screen
= 0;
1269 if (!drawingHorizon
)
1272 distance
= RCL_nonZero(hit
.distance
);
1275 fWallHeight
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1276 fZ2World
= fWallHeight
- _RCL_camera
.height
;
1277 fZ1Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1278 (fZ1World
* _RCL_camera
.resolution
.y
) /
1279 RCL_UNITS_PER_SQUARE
,distance
);
1280 fZ2Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1281 (fZ2World
* _RCL_camera
.resolution
.y
) /
1282 RCL_UNITS_PER_SQUARE
,distance
);
1284 if (_RCL_ceilFunction
!= 0)
1286 cWallHeight
= _RCL_ceilFunction(hit
.square
.x
,hit
.square
.y
);
1287 cZ2World
= cWallHeight
- _RCL_camera
.height
;
1288 cZ1Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1289 (cZ1World
* _RCL_camera
.resolution
.y
) /
1290 RCL_UNITS_PER_SQUARE
,distance
);
1291 cZ2Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1292 (cZ2World
* _RCL_camera
.resolution
.y
) /
1293 RCL_UNITS_PER_SQUARE
,distance
);
1298 fZ1Screen
= _RCL_middleRow
;
1299 cZ1Screen
= _RCL_middleRow
+ 1;
1300 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1306 p
.isHorizon
= drawingHorizon
;
1308 // draw floor until wall
1310 p
.height
= fZ1World
+ _RCL_camera
.height
;
1313 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1314 p
.depth
= (_RCL_fHorizontalDepthStart
- fPosY
) * _RCL_horizontalDepthStep
;
1319 limit
= _RCL_drawHorizontalColumn(fPosY
,fZ1Screen
,cPosY
+ 1,
1320 _RCL_camera
.resolution
.y
,fZ1World
,-1,RCL_COMPUTE_FLOOR_DEPTH
,
1321 // ^ purposfully allow outside screen bounds
1322 RCL_COMPUTE_FLOOR_TEXCOORDS
&& p
.height
== RCL_FLOOR_TEXCOORDS_HEIGHT
,
1328 if (_RCL_ceilFunction
!= 0 || drawingHorizon
)
1330 // draw ceiling until wall
1332 p
.height
= cZ1World
+ _RCL_camera
.height
;
1334 #if RCL_COMPUTE_CEILING_DEPTH == 1
1335 p
.depth
= (cPosY
- _RCL_cHorizontalDepthStart
) *
1336 _RCL_horizontalDepthStep
;
1339 limit
= _RCL_drawHorizontalColumn(cPosY
,cZ1Screen
,
1340 -1,fPosY
- 1,cZ1World
,1,RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1341 // ^ purposfully allow outside screen bounds here
1347 if (!drawingHorizon
) // don't draw walls for horizon plane
1352 p
.texCoords
.x
= hit
.textureCoord
;
1353 p
.height
= fZ1World
+ _RCL_camera
.height
;
1354 p
.wallHeight
= fWallHeight
;
1358 if (fPosY
> 0) // still pixels left?
1362 limit
= _RCL_drawWall(fPosY
,fZ1Screen
,fZ2Screen
,cPosY
+ 1,
1363 _RCL_camera
.resolution
.y
,
1364 // ^ purposfully allow outside screen bounds here
1365 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1366 RCL_UNITS_PER_SQUARE
1376 fZ1World
= fZ2World
; // for the next iteration
1377 } // ^ purposfully allow outside screen bounds here
1379 // draw ceiling wall
1381 if (_RCL_ceilFunction
!= 0 && cPosY
< _RCL_camResYLimit
) // pixels left?
1384 p
.height
= cZ1World
+ _RCL_camera
.height
;
1385 p
.wallHeight
= cWallHeight
;
1387 limit
= _RCL_drawWall(cPosY
,cZ1Screen
,cZ2Screen
,
1389 // ^ puposfully allow outside screen bounds here
1390 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1391 RCL_UNITS_PER_SQUARE
1400 cZ1World
= cZ2World
; // for the next iteration
1401 } // ^ puposfully allow outside screen bounds here
1406 void _RCL_columnFunctionSimple(RCL_HitResult
*hits
, uint16_t hitCount
,
1407 uint16_t x
, RCL_Ray ray
)
1410 RCL_Unit wallHeightScreen
= 0;
1411 RCL_Unit wallStart
= _RCL_middleRow
;
1417 p
.wallHeight
= RCL_UNITS_PER_SQUARE
;
1421 RCL_HitResult hit
= hits
[0];
1425 if (_RCL_rollFunction
!= 0 && RCL_COMPUTE_WALL_TEXCOORDS
== 1)
1427 if (hit
.arrayValue
== 0)
1429 // standing inside door square, looking out => move to the next hit
1438 // normal hit, check the door roll
1440 RCL_Unit texCoordMod
= hit
.textureCoord
% RCL_UNITS_PER_SQUARE
;
1442 int8_t unrolled
= hit
.doorRoll
>= 0 ?
1443 (hit
.doorRoll
> texCoordMod
) :
1444 (texCoordMod
> RCL_UNITS_PER_SQUARE
+ hit
.doorRoll
);
1450 if (hitCount
> 1) /* should probably always be true (hit on square
1453 if (hit
.direction
% 2 != hits
[1].direction
% 2)
1455 // hit on the inner side
1459 else if (hitCount
> 2)
1461 // hit on the opposite side
1474 dist
= hit
.distance
;
1476 RCL_Unit wallHeightWorld
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1478 if (wallHeightWorld
< 0)
1480 /* We can't just do wallHeightWorld = max(0,wallHeightWorld) because
1481 we would be processing an actual hit with height 0, which shouldn't
1482 ever happen, so we assign some arbitrary height. */
1484 wallHeightWorld
= RCL_UNITS_PER_SQUARE
;
1487 RCL_Unit worldPointTop
= wallHeightWorld
- _RCL_camera
.height
;
1488 RCL_Unit worldPointBottom
= -1 * _RCL_camera
.height
;
1490 wallStart
= _RCL_middleRow
-
1491 (RCL_perspectiveScaleVertical(worldPointTop
,dist
)
1492 * _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1494 int16_t wallEnd
= _RCL_middleRow
-
1495 (RCL_perspectiveScaleVertical(worldPointBottom
,dist
)
1496 * _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1498 wallHeightScreen
= wallEnd
- wallStart
;
1500 if (wallHeightScreen
<= 0) // can happen because of rounding errors
1501 wallHeightScreen
= 1;
1506 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1515 p
.height
= RCL_UNITS_PER_SQUARE
;
1517 y
= _RCL_drawHorizontalColumn(-1,wallStart
,-1,_RCL_middleRow
,_RCL_camera
.height
,1,
1518 RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1527 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1528 p
.hit
.textureCoord
-= p
.hit
.doorRoll
;
1531 p
.texCoords
.x
= p
.hit
.textureCoord
;
1534 RCL_Unit limit
= _RCL_drawWall(y
,wallStart
,wallStart
+ wallHeightScreen
- 1,
1535 -1,_RCL_camResYLimit
,p
.hit
.arrayValue
,1,&p
);
1537 y
= RCL_max(y
,limit
); // take max, in case no wall was drawn
1538 y
= RCL_max(y
,wallStart
);
1544 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1545 p
.depth
= (_RCL_camera
.resolution
.y
- y
) * _RCL_horizontalDepthStep
+ 1;
1548 _RCL_drawHorizontalColumn(y
,_RCL_camResYLimit
,-1,_RCL_camResYLimit
,
1549 _RCL_camera
.height
,1,RCL_COMPUTE_FLOOR_DEPTH
,RCL_COMPUTE_FLOOR_TEXCOORDS
,
1554 Precomputes a distance from camera to the floor at each screen row into an
1555 array (must be preallocated with sufficient (camera.resolution.y) length).
1557 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera
,
1558 RCL_Unit
*dest
, uint16_t startIndex
)
1560 RCL_Unit camHeightScreenSize
=
1561 (camera
.height
* camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1563 for (uint16_t i
= startIndex
; i
< camera
.resolution
.y
; ++i
)
1564 dest
[i
] = RCL_perspectiveScaleVerticalInverse(camHeightScreenSize
,
1565 RCL_abs(i
- _RCL_middleRow
));
1568 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1569 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
1570 RCL_RayConstraints constraints
)
1572 _RCL_floorFunction
= floorHeightFunc
;
1573 _RCL_ceilFunction
= ceilingHeightFunc
;
1575 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1577 uint16_t halfResY
= cam
.resolution
.y
/ 2;
1579 _RCL_middleRow
= halfResY
+ cam
.shear
;
1581 _RCL_fHorizontalDepthStart
= _RCL_middleRow
+ halfResY
;
1582 _RCL_cHorizontalDepthStart
= _RCL_middleRow
- halfResY
;
1584 _RCL_startFloorHeight
= floorHeightFunc(
1585 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1586 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
;
1588 _RCL_startCeil_Height
=
1589 ceilingHeightFunc
!= 0 ?
1591 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1592 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
1595 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1597 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1598 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1599 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,0);
1600 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1603 RCL_castRaysMultiHit(cam
,_RCL_floorCeilFunction
,typeFunction
,
1604 _RCL_columnFunctionComplex
,constraints
);
1607 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1608 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
1609 RCL_RayConstraints constraints
)
1611 _RCL_floorFunction
= floorHeightFunc
;
1613 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1614 _RCL_middleRow
= cam
.resolution
.y
/ 2;
1615 _RCL_rollFunction
= rollFunc
;
1617 _RCL_cameraHeightScreen
=
1618 (_RCL_camera
.resolution
.y
* (_RCL_camera
.height
- RCL_UNITS_PER_SQUARE
)) /
1619 RCL_UNITS_PER_SQUARE
;
1621 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1623 constraints
.maxHits
=
1624 _RCL_rollFunction
== 0 ?
1625 1 : // no door => 1 hit is enough
1626 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1628 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1629 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1630 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,_RCL_middleRow
);
1631 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1634 RCL_castRaysMultiHit(cam
,_floorHeightNotZeroFunction
,typeFunc
,
1635 _RCL_columnFunctionSimple
, constraints
);
1637 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1638 _RCL_floorPixelDistances
= 0;
1642 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
)
1644 RCL_Vector2D result
;
1645 RCL_Unit l
= RCL_len(v
);
1648 result
.x
= (v
.x
* RCL_UNITS_PER_SQUARE
) / l
;
1649 result
.y
= (v
.y
* RCL_UNITS_PER_SQUARE
) / l
;
1654 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
)
1656 v1
= RCL_normalize(v1
);
1657 v2
= RCL_normalize(v2
);
1659 return (v1
.x
* v2
.x
+ v1
.y
* v2
.y
) / RCL_UNITS_PER_SQUARE
;
1663 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
1666 RCL_PixelInfo result
;
1668 RCL_Vector2D toPoint
;
1670 toPoint
.x
= worldPosition
.x
- camera
.position
.x
;
1671 toPoint
.y
= worldPosition
.y
- camera
.position
.y
;
1673 RCL_Unit middleColumn
= camera
.resolution
.x
/ 2;
1675 // rotate the point to camera space (y left/right, x forw/backw)
1677 RCL_Unit cos
= RCL_cos(camera
.direction
);
1678 RCL_Unit sin
= RCL_sin(camera
.direction
);
1680 RCL_Unit tmp
= toPoint
.x
;
1682 toPoint
.x
= (toPoint
.x
* cos
- toPoint
.y
* sin
) / RCL_UNITS_PER_SQUARE
;
1683 toPoint
.y
= (tmp
* sin
+ toPoint
.y
* cos
) / RCL_UNITS_PER_SQUARE
;
1685 result
.depth
= toPoint
.x
;
1687 result
.position
.x
= middleColumn
-
1688 (RCL_perspectiveScaleHorizontal(toPoint
.y
,result
.depth
) * middleColumn
) /
1689 RCL_UNITS_PER_SQUARE
;
1692 (RCL_perspectiveScaleVertical(height
- camera
.height
,result
.depth
)
1693 * camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1695 result
.position
.y
= camera
.resolution
.y
/ 2 - result
.position
.y
+ camera
.shear
;
1700 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
)
1702 return (degrees
* RCL_UNITS_PER_SQUARE
) / 360;
1706 Ugly temporary hack to solve mapping to screen. This function computes
1707 (approximately, usin a table) a divisor needed for FOV correction.
1709 RCL_Unit
_RCL_fovCorrectionFactor(RCL_Unit fov
)
1712 {1,208,408,692,1024,1540,2304,5376,30000};
1714 fov
= RCL_min(RCL_UNITS_PER_SQUARE
/ 2 - 1,fov
);
1716 uint8_t index
= fov
/ 64;
1717 uint32_t t
= ((fov
- index
* 64) * RCL_UNITS_PER_SQUARE
) / 64;
1718 uint32_t v1
= table
[index
];
1719 uint32_t v2
= table
[index
+ 1];
1721 return v1
+ ((v2
- v1
) * t
) / RCL_UNITS_PER_SQUARE
;
1724 RCL_Unit
RCL_perspectiveScaleVertical(RCL_Unit originalSize
, RCL_Unit distance
)
1726 if (_RCL_fovCorrectionFactors
[1] == 0)
1727 _RCL_fovCorrectionFactors
[1] = _RCL_fovCorrectionFactor(RCL_VERTICAL_FOV
);
1729 return distance
!= 0 ? ((originalSize
* RCL_UNITS_PER_SQUARE
) /
1730 RCL_nonZero((_RCL_fovCorrectionFactors
[1] * distance
) / RCL_UNITS_PER_SQUARE
)
1734 RCL_Unit
RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize
,
1735 RCL_Unit scaledSize
)
1737 if (_RCL_fovCorrectionFactors
[1] == 0)
1738 _RCL_fovCorrectionFactors
[1] = _RCL_fovCorrectionFactor(RCL_VERTICAL_FOV
);
1740 return scaledSize
!= 0 ?
1742 ((originalSize
* RCL_UNITS_PER_SQUARE
) /
1743 RCL_nonZero((_RCL_fovCorrectionFactors
[1] * scaledSize
)
1744 / RCL_UNITS_PER_SQUARE
)) : RCL_INFINITY
;
1748 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize
, RCL_Unit distance
)
1750 if (_RCL_fovCorrectionFactors
[0] == 0)
1751 _RCL_fovCorrectionFactors
[0] = _RCL_fovCorrectionFactor(RCL_HORIZONTAL_FOV
);
1753 return distance
!= 0 ?
1754 ((originalSize
* RCL_UNITS_PER_SQUARE
) /
1755 RCL_nonZero((_RCL_fovCorrectionFactors
[0] * distance
) / RCL_UNITS_PER_SQUARE
)
1759 RCL_Unit
RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize
,
1760 RCL_Unit scaledSize
)
1762 // TODO: probably doesn't work
1764 return scaledSize
!= 0 ?
1765 (originalSize
* RCL_UNITS_PER_SQUARE
+ RCL_UNITS_PER_SQUARE
/ 2) /
1766 ((RCL_HORIZONTAL_FOV_TAN
* 2 * scaledSize
) / RCL_UNITS_PER_SQUARE
)
1770 RCL_Unit
RCL_castRay3D(
1771 RCL_Vector2D pos1
, RCL_Unit height1
, RCL_Vector2D pos2
, RCL_Unit height2
,
1772 RCL_ArrayFunction floorHeightFunc
, RCL_ArrayFunction ceilingHeightFunc
,
1773 RCL_RayConstraints constraints
)
1775 RCL_HitResult hits
[constraints
.maxHits
];
1784 ray
.direction
.x
= pos2
.x
- pos1
.x
;
1785 ray
.direction
.y
= pos2
.y
- pos1
.y
;
1787 distance
= RCL_nonZero(RCL_len(ray
.direction
));
1789 ray
.direction
= RCL_normalize(ray
.direction
);
1791 RCL_Unit heightDiff
= height2
- height1
;
1793 RCL_castRayMultiHit(ray
,floorHeightFunc
,0,hits
,&numHits
,constraints
);
1795 RCL_Unit result
= RCL_UNITS_PER_SQUARE
;
1797 int16_t squareX
= RCL_divRoundDown(pos1
.x
,RCL_UNITS_PER_SQUARE
);
1798 int16_t squareY
= RCL_divRoundDown(pos1
.y
,RCL_UNITS_PER_SQUARE
);
1800 RCL_Unit startHeight
= floorHeightFunc(squareX
,squareY
);
1802 #define checkHits(comp,res) \
1804 RCL_Unit currentHeight = startHeight; \
1805 for (uint16_t i = 0; i < numHits; ++i) \
1807 if (hits[i].distance > distance) \
1809 RCL_Unit h = hits[i].arrayValue; \
1810 if ((currentHeight comp h ? currentHeight : h) \
1811 comp (height1 + (hits[i].distance * heightDiff) / distance)) \
1813 res = (hits[i].distance * RCL_UNITS_PER_SQUARE) / distance; \
1816 currentHeight = h; \
1822 if (ceilingHeightFunc
!= 0)
1824 RCL_Unit result2
= RCL_UNITS_PER_SQUARE
;
1826 startHeight
= ceilingHeightFunc(squareX
,squareY
);
1828 RCL_castRayMultiHit(ray
,ceilingHeightFunc
,0,hits
,&numHits
,constraints
);
1830 checkHits(<,result2
)
1832 if (result2
< result
)
1841 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
1842 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
1843 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
)
1845 int8_t movesInPlane
= planeOffset
.x
!= 0 || planeOffset
.y
!= 0;
1847 if (movesInPlane
|| force
)
1849 int16_t xSquareNew
, ySquareNew
;
1851 RCL_Vector2D corner
; // BBox corner in the movement direction
1852 RCL_Vector2D cornerNew
;
1854 int16_t xDir
= planeOffset
.x
> 0 ? 1 : -1;
1855 int16_t yDir
= planeOffset
.y
> 0 ? 1 : -1;
1857 corner
.x
= camera
->position
.x
+ xDir
* RCL_CAMERA_COLL_RADIUS
;
1858 corner
.y
= camera
->position
.y
+ yDir
* RCL_CAMERA_COLL_RADIUS
;
1860 int16_t xSquare
= RCL_divRoundDown(corner
.x
,RCL_UNITS_PER_SQUARE
);
1861 int16_t ySquare
= RCL_divRoundDown(corner
.y
,RCL_UNITS_PER_SQUARE
);
1863 cornerNew
.x
= corner
.x
+ planeOffset
.x
;
1864 cornerNew
.y
= corner
.y
+ planeOffset
.y
;
1866 xSquareNew
= RCL_divRoundDown(cornerNew
.x
,RCL_UNITS_PER_SQUARE
);
1867 ySquareNew
= RCL_divRoundDown(cornerNew
.y
,RCL_UNITS_PER_SQUARE
);
1869 RCL_Unit bottomLimit
= -1 * RCL_INFINITY
;
1870 RCL_Unit topLimit
= RCL_INFINITY
;
1872 RCL_Unit currCeilHeight
= RCL_INFINITY
;
1876 bottomLimit
= camera
->height
- RCL_CAMERA_COLL_HEIGHT_BELOW
+
1877 RCL_CAMERA_COLL_STEP_HEIGHT
;
1879 topLimit
= camera
->height
+ RCL_CAMERA_COLL_HEIGHT_ABOVE
;
1881 if (ceilingHeightFunc
!= 0)
1882 currCeilHeight
= ceilingHeightFunc(xSquare
,ySquare
);
1885 // checks a single square for collision against the camera
1886 #define collCheck(dir,s1,s2)\
1889 RCL_Unit height = floorHeightFunc(s1,s2);\
1890 if (height > bottomLimit || \
1891 currCeilHeight - height < \
1892 RCL_CAMERA_COLL_HEIGHT_BELOW + RCL_CAMERA_COLL_HEIGHT_ABOVE)\
1894 else if (ceilingHeightFunc != 0)\
1896 RCL_Unit height2 = ceilingHeightFunc(s1,s2);\
1897 if ((height2 < topLimit) || ((height2 - height) < \
1898 (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))\
1903 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1905 // check collision against non-diagonal square
1906 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1907 if (dir##SquareNew != dir##Square)\
1909 collCheck(dir,s1,s2)\
1911 if (!dir##Collides)\
1912 { /* now also check for coll on the neighbouring square */ \
1913 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1914 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1915 if (dir2##Square2 != dir2##Square)\
1918 collCheck(dir,dir##SquareNew,dir2##Square2)\
1920 collCheck(dir,dir2##Square2,dir##SquareNew)\
1924 int8_t xCollides
= 0;
1925 collCheckOrtho(x
,y
,xSquareNew
,ySquare
,1)
1927 int8_t yCollides
= 0;
1928 collCheckOrtho(y
,x
,xSquare
,ySquareNew
,0)
1930 if (xCollides
|| yCollides
)
1934 #define collHandle(dir)\
1936 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1937 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1947 /* Player collides without moving in the plane; this can happen e.g. on
1948 elevators due to vertical only movement. This code can get executed
1951 RCL_Vector2D squarePos
;
1952 RCL_Vector2D newPos
;
1954 squarePos
.x
= xSquare
* RCL_UNITS_PER_SQUARE
;
1955 squarePos
.y
= ySquare
* RCL_UNITS_PER_SQUARE
;
1958 RCL_max(squarePos
.x
+ RCL_CAMERA_COLL_RADIUS
+ 1,
1959 RCL_min(squarePos
.x
+ RCL_UNITS_PER_SQUARE
- RCL_CAMERA_COLL_RADIUS
- 1,
1960 camera
->position
.x
));
1963 RCL_max(squarePos
.y
+ RCL_CAMERA_COLL_RADIUS
+ 1,
1964 RCL_min(squarePos
.y
+ RCL_UNITS_PER_SQUARE
- RCL_CAMERA_COLL_RADIUS
- 1,
1965 camera
->position
.y
));
1967 cornerNew
.x
= corner
.x
+ (newPos
.x
- camera
->position
.x
);
1968 cornerNew
.y
= corner
.y
+ (newPos
.y
- camera
->position
.y
);
1973 /* If no non-diagonal collision is detected, a diagonal/corner collision
1974 can still happen, check it here. */
1976 if (xSquare
!= xSquareNew
&& ySquare
!= ySquareNew
)
1978 int8_t xyCollides
= 0;
1979 collCheck(xy
,xSquareNew
,ySquareNew
)
1983 // normally should slide, but let's KISS and simply stop any movement
1991 camera
->position
.x
= cornerNew
.x
- xDir
* RCL_CAMERA_COLL_RADIUS
;
1992 camera
->position
.y
= cornerNew
.y
- yDir
* RCL_CAMERA_COLL_RADIUS
;
1995 if (computeHeight
&& (movesInPlane
|| (heightOffset
!= 0) || force
))
1997 camera
->height
+= heightOffset
;
1999 int16_t xSquare1
= RCL_divRoundDown(camera
->position
.x
-
2000 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
2002 int16_t xSquare2
= RCL_divRoundDown(camera
->position
.x
+
2003 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
2005 int16_t ySquare1
= RCL_divRoundDown(camera
->position
.y
-
2006 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
2008 int16_t ySquare2
= RCL_divRoundDown(camera
->position
.y
+
2009 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
2011 RCL_Unit bottomLimit
= floorHeightFunc(xSquare1
,ySquare1
);
2012 RCL_Unit topLimit
= ceilingHeightFunc
!= 0 ?
2013 ceilingHeightFunc(xSquare1
,ySquare1
) : RCL_INFINITY
;
2017 #define checkSquares(s1,s2)\
2019 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
2020 bottomLimit = RCL_max(bottomLimit,height);\
2021 height = ceilingHeightFunc != 0 ?\
2022 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
2023 topLimit = RCL_min(topLimit,height);\
2026 if (xSquare2
!= xSquare1
)
2029 if (ySquare2
!= ySquare1
)
2032 if (xSquare2
!= xSquare1
&& ySquare2
!= ySquare1
)
2035 camera
->height
= RCL_clamp(camera
->height
,
2036 bottomLimit
+ RCL_CAMERA_COLL_HEIGHT_BELOW
,
2037 topLimit
- RCL_CAMERA_COLL_HEIGHT_ABOVE
);
2043 void RCL_initCamera(RCL_Camera
*camera
)
2045 camera
->position
.x
= 0;
2046 camera
->position
.y
= 0;
2047 camera
->direction
= 0;
2048 camera
->resolution
.x
= 20;
2049 camera
->resolution
.y
= 15;
2051 camera
->height
= RCL_UNITS_PER_SQUARE
;
2054 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
)
2056 constraints
->maxHits
= 1;
2057 constraints
->maxSteps
= 20;