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
34 #ifndef RCL_RAYCAST_TINY /** Turns on super efficient version of this library.
35 Only use if neccesarry, looks ugly. Also not done
37 #define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a
39 typedef int32_t RCL_Unit
; /**< Smallest spatial unit, there is
40 RCL_UNITS_PER_SQUARE units in a square's
41 length. This effectively serves the purpose of
42 a fixed-point arithmetic. */
43 #define RCL_INFINITY 2000000000
45 #define RCL_UNITS_PER_SQUARE 32
46 typedef int16_t RCL_Unit
;
47 #define RCL_INFINITY 30000
48 #define RCL_USE_DIST_APPROX 2
51 #ifndef RCL_COMPUTE_WALL_TEXCOORDS
52 #define RCL_COMPUTE_WALL_TEXCOORDS 1
55 #ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
56 #define RCL_COMPUTE_FLOOR_TEXCOORDS 0
59 #ifndef RCL_FLOOR_TEXCOORDS_HEIGHT
60 #define RCL_FLOOR_TEXCOORDS_HEIGHT 0 /** If RCL_COMPUTE_FLOOR_TEXCOORDS == 1,
61 this says for what height level the
62 texture coords will be computed for
63 (for simplicity/performance only one
67 #ifndef RCL_USE_COS_LUT
68 #define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
74 #ifndef RCL_USE_DIST_APPROX
75 #define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use:
76 0: none (compute full Euclidean distance)
77 1: accurate approximation
78 2: octagonal approximation (LQ) */
81 #ifndef RCL_RECTILINEAR
82 #define RCL_RECTILINEAR 1 /**< Whether to use rectilinear perspective (normally
83 used), or curvilinear perspective (fish eye). */
86 #ifndef RCL_TEXTURE_VERTICAL_STRETCH
87 #define RCL_TEXTURE_VERTICAL_STRETCH 1 /**< Whether textures should be
88 stretched to wall height (possibly
89 slightly slower if on). */
92 #ifndef RCL_COMPUTE_FLOOR_DEPTH
93 #define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for
94 floor pixels - turns this off if not
98 #ifndef RCL_COMPUTE_CEILING_DEPTH
99 #define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for
103 #ifndef RCL_ROLL_TEXTURE_COORDS
104 #define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
105 roll the texture coordinates along (mostly
106 desired for doors). */
109 #ifndef RCL_VERTICAL_FOV
110 #define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 5)
113 #define RCL_VERTICAL_FOV_TAN (RCL_VERTICAL_FOV * 4) ///< tan approximation
115 #ifndef RCL_HORIZONTAL_FOV
116 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
119 #define RCL_HORIZONTAL_FOV_TAN (RCL_VERTICAL_FOV * 4)
121 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
123 #ifndef RCL_CAMERA_COLL_RADIUS
124 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
127 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
128 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
131 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
132 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
135 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
136 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
139 #ifndef RCL_TEXTURE_INTERPOLATION_SCALE
140 #define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed
141 poit vertical texture coord
142 computation. This should be power
143 of two! Higher number can look more
144 accurate but may cause overflow. */
147 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
148 horizon has (the floor
150 approximated with the
153 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
154 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
155 difference when approximating floor/ceil
159 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
160 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
161 #define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions.
162 #define RCL_zeroClamp(x) ((x) * ((x) >= 0))
163 #define RCL_likely(cond) __builtin_expect(!!(cond),1)
164 #define RCL_unlikely(cond) __builtin_expect(!!(cond),0)
166 #define RCL_logV2D(v)\
167 printf("[%d,%d]\n",v.x,v.y);
169 #define RCL_logRay(r){\
172 RCL_logV2D(r.start);\
174 RCL_logV2D(r.direction);}
176 #define RCL_logHitResult(h){\
178 printf(" square: ");\
179 RCL_logV2D(h.square);\
181 RCL_logV2D(h.position);\
182 printf(" dist: %d\n", h.distance);\
183 printf(" dir: %d\n", h.direction);\
184 printf(" texcoord: %d\n", h.textureCoord);}
186 #define RCL_logPixelInfo(p){\
188 printf(" position: ");\
189 RCL_logV2D(p.position);\
190 printf(" texCoord: ");\
191 RCL_logV2D(p.texCoords);\
192 printf(" depth: %d\n", p.depth);\
193 printf(" height: %d\n", p.height);\
194 printf(" wall: %d\n", p.isWall);\
196 RCL_logHitResult(p.hit);\
199 #define RCL_logCamera(c){\
200 printf("camera:\n");\
201 printf(" position: ");\
202 RCL_logV2D(c.position);\
203 printf(" height: %d\n",c.height);\
204 printf(" direction: %d\n",c.direction);\
205 printf(" shear: %d\n",c.shear);\
206 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
209 /// Position in 2D space.
219 RCL_Vector2D direction
;
224 RCL_Unit distance
; /**< Distance to the hit position, or -1 if no
225 collision happened. If RCL_RECTILINEAR != 0, then
226 the distance is perpendicular to the projection
227 plane (fish eye correction), otherwise it is
228 the straight distance to the ray start
230 uint8_t direction
; /**< Direction of hit. The convention for angle
231 units is explained above. */
232 RCL_Unit textureCoord
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
233 texture coordinate (horizontal). */
234 RCL_Vector2D square
; ///< Collided square coordinates.
235 RCL_Vector2D position
; ///< Exact collision position in RCL_Units.
236 RCL_Unit arrayValue
; /** Value returned by array function (most often
237 this will be the floor height). */
238 RCL_Unit type
; /**< Integer identifying type of square (number
239 returned by type function, e.g. texture
241 RCL_Unit doorRoll
; ///< Holds value of door roll.
246 RCL_Vector2D position
;
247 RCL_Unit direction
; // TODO: rename to "angle" to keep consistency
248 RCL_Vector2D resolution
;
249 int16_t shear
; /**< Shear offset in pixels (0 => no shear), can simulate
255 Holds an information about a single rendered pixel (for a pixel function
256 that works as a fragment shader).
260 RCL_Vector2D position
; ///< On-screen position.
261 int8_t isWall
; ///< Whether the pixel is a wall or a floor/ceiling.
262 int8_t isFloor
; ///< Whether the pixel is floor or ceiling.
263 int8_t isHorizon
; ///< If the pixel belongs to horizon segment.
264 RCL_Unit depth
; ///< Corrected depth.
265 RCL_Unit wallHeight
;///< Only for wall pixels, says its height.
266 RCL_Unit height
; ///< World height (mostly for floor).
267 RCL_HitResult hit
; ///< Corresponding ray hit.
268 RCL_Vector2D texCoords
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
269 texture coordinates. */
272 void RCL_PIXEL_FUNCTION (RCL_PixelInfo
*pixel
);
278 } RCL_RayConstraints
;
281 Function used to retrieve some information about cells of the rendered scene.
282 It should return a characteristic of given square as an integer (e.g. square
283 height, texture index, ...) - between squares that return different numbers
284 there is considered to be a collision.
286 This function should be as fast as possible as it will typically be called
289 typedef RCL_Unit (*RCL_ArrayFunction
)(int16_t x
, int16_t y
);
291 TODO: maybe array functions should be replaced by defines of funtion names
292 like with pixelFunc? Could be more efficient than function pointers.
296 Function that renders a single pixel at the display. It is handed an info
297 about the pixel it should draw.
299 This function should be as fast as possible as it will typically be called
302 typedef void (*RCL_PixelFunction
)(RCL_PixelInfo
*info
);
305 (*RCL_ColumnFunction
)(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
309 Simple-interface function to cast a single ray.
311 @return The first collision result.
313 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
);
316 Casts a 3D ray in 3D environment with floor and optional ceiling
317 (ceilingHeightFunc can be 0). This can be useful for hitscan shooting,
318 visibility checking etc.
320 @return normalized ditance (0 to RCL_UNITS_PER_SQUARE) along the ray at which
321 the environment was hit, RCL_UNITS_PER_SQUARE means nothing was hit
323 RCL_Unit
RCL_castRay3D(
324 RCL_Vector2D pos1
, RCL_Unit height1
, RCL_Vector2D pos2
, RCL_Unit height2
,
325 RCL_ArrayFunction floorHeightFunc
, RCL_ArrayFunction ceilingHeightFunc
,
326 RCL_RayConstraints constraints
);
329 Maps a single point in the world to the screen (2D position + depth).
331 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
335 Casts a single ray and returns a list of collisions.
337 @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
338 distance is divided by the ray direction vector length (to correct
340 @param arrayFunc function that will be used to determine collisions (hits)
341 with the ray (squares for which this function returns different values
342 are considered to have a collision between them), this will typically
343 be a function returning floor height
344 @param typeFunc optional (can be 0) function - if provided, it will be used
345 to mark the hit result with the number returned by this function
346 (it can be e.g. a texture index)
347 @param hitResults array in which the hit results will be stored (has to be
348 preallocated with at space for at least as many hit results as
349 maxHits specified with the constraints parameter)
350 @param hitResultsLen in this variable the number of hit results will be
352 @param constraints specifies constraints for the ray cast
354 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
355 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
356 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
);
358 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
);
363 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
364 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
365 RCL_UNITS_PER_SQUARE)
367 RCL_Unit
RCL_cos(RCL_Unit input
);
369 RCL_Unit
RCL_sin(RCL_Unit input
);
371 RCL_Unit
RCL_tan(RCL_Unit input
);
373 RCL_Unit
RCL_ctg(RCL_Unit input
);
375 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
376 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
);
378 /// Computes a cos of an angle between two vectors.
379 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
);
381 uint16_t RCL_sqrt(RCL_Unit value
);
382 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
);
383 RCL_Unit
RCL_len(RCL_Vector2D v
);
386 Converts an angle in whole degrees to an angle in RCL_Units that this library
389 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
);
391 ///< Computes the change in size of an object due to perspective (vertical FOV).
392 RCL_Unit
RCL_perspectiveScaleVertical(RCL_Unit originalSize
, RCL_Unit distance
);
394 RCL_Unit
RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize
,
395 RCL_Unit scaledSize
);
398 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize
, RCL_Unit distance
);
400 RCL_Unit
RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize
,
401 RCL_Unit scaledSize
);
404 Casts rays for given camera view and for each hit calls a user provided
407 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
408 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
409 RCL_RayConstraints constraints
);
412 Using provided functions, renders a complete complex (multilevel) camera
415 This function should render each screen pixel exactly once.
417 function rendering summary:
418 - performance: slower
421 - different wall heights: yes
422 - floor/ceiling textures: no
423 - floor geometry: yes, multilevel
424 - ceiling geometry: yes (optional), multilevel
426 - camera shearing: yes
427 - rendering order: left-to-right, not specifically ordered vertically
429 @param cam camera whose view to render
430 @param floorHeightFunc function that returns floor height (in RCL_Units)
431 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
432 0 (no ceiling will be rendered)
433 @param typeFunction function that says a type of square (e.g. its texture
434 index), can be 0 (no type in hit result)
435 @param pixelFunc callback function to draw a single pixel on screen
436 @param constraints constraints for each cast ray
438 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
439 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
440 RCL_RayConstraints constraints
);
443 Renders given camera view, with help of provided functions. This function is
444 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
447 function rendering summary:
448 - performance: faster
451 - different wall heights: yes
452 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
453 - floor geometry: no (just flat floor, with depth information)
454 - ceiling geometry: no (just flat ceiling, with depth information)
456 - camera shearing: no
457 - rendering order: left-to-right, top-to-bottom
459 Additionally this function supports rendering rolling doors.
461 This function should render each screen pixel exactly once.
463 @param rollFunc function that for given square says its door roll in
464 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
465 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
466 rendering should also be faster as fewer intersections will be tested)
468 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
469 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
470 RCL_RayConstraints constraints
);
473 Function that moves given camera and makes it collide with walls and
474 potentially also floor and ceilings. It's meant to help implement player
477 @param camera camera to move
478 @param planeOffset offset to move the camera in
479 @param heightOffset height offset to move the camera in
480 @param floorHeightFunc function used to retrieve the floor height
481 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
482 (camera won't collide with ceiling)
483 @param computeHeight whether to compute height - if false (0), floor and
484 ceiling functions won't be used and the camera will
485 only collide horizontally with walls (good for simpler
487 @param force if true, forces to recompute collision even if position doesn't
490 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
491 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
492 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
);
494 void RCL_initCamera(RCL_Camera
*camera
);
495 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
);
497 //=============================================================================
500 #define _RCL_UNUSED(what) (void)(what);
502 // global helper variables, for precomputing stuff etc.
503 RCL_Camera _RCL_camera
;
504 RCL_Unit _RCL_horizontalDepthStep
= 0;
505 RCL_Unit _RCL_startFloorHeight
= 0;
506 RCL_Unit _RCL_startCeil_Height
= 0;
507 RCL_Unit _RCL_camResYLimit
= 0;
508 RCL_Unit _RCL_middleRow
= 0;
509 RCL_ArrayFunction _RCL_floorFunction
= 0;
510 RCL_ArrayFunction _RCL_ceilFunction
= 0;
511 RCL_Unit _RCL_fHorizontalDepthStart
= 0;
512 RCL_Unit _RCL_cHorizontalDepthStart
= 0;
513 int16_t _RCL_cameraHeightScreen
= 0;
514 RCL_ArrayFunction _RCL_rollFunction
= 0; // says door rolling
515 RCL_Unit
*_RCL_floorPixelDistances
= 0;
517 RCL_Unit
RCL_clamp(RCL_Unit value
, RCL_Unit valueMin
, RCL_Unit valueMax
)
519 if (value
>= valueMin
)
521 if (value
<= valueMax
)
530 static inline RCL_Unit
RCL_abs(RCL_Unit value
)
532 return value
* (((value
>= 0) << 1) - 1);
535 /// Like mod, but behaves differently for negative values.
536 static inline RCL_Unit
RCL_wrap(RCL_Unit value
, RCL_Unit mod
)
538 RCL_Unit cmp
= value
< 0;
539 return cmp
* mod
+ (value
% mod
) - cmp
;
542 /// Performs division, rounding down, NOT towards zero.
543 static inline RCL_Unit
RCL_divRoundDown(RCL_Unit value
, RCL_Unit divisor
)
545 return value
/ divisor
- ((value
>= 0) ? 0 : 1);
548 // Bhaskara's cosine approximation formula
549 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
550 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
551 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
553 #if RCL_USE_COS_LUT == 1
555 #ifdef RCL_RAYCAST_TINY
556 const RCL_Unit cosLUT
[64] =
558 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
561 const RCL_Unit cosLUT
[64] =
563 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
564 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
565 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
566 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
570 #elif RCL_USE_COS_LUT == 2
571 const RCL_Unit cosLUT
[128] =
573 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
574 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
575 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
576 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
577 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
578 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
579 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
580 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
584 RCL_Unit
RCL_cos(RCL_Unit input
)
586 input
= RCL_wrap(input
,RCL_UNITS_PER_SQUARE
);
588 #if RCL_USE_COS_LUT == 1
590 #ifdef RCL_RAYCAST_TINY
591 return cosLUT
[input
];
593 return cosLUT
[input
/ 16];
596 #elif RCL_USE_COS_LUT == 2
597 return cosLUT
[input
/ 8];
599 if (input
< RCL_UNITS_PER_SQUARE
/ 4)
600 return trigHelper(input
);
601 else if (input
< RCL_UNITS_PER_SQUARE
/ 2)
602 return -1 * trigHelper(RCL_UNITS_PER_SQUARE
/ 2 - input
);
603 else if (input
< 3 * RCL_UNITS_PER_SQUARE
/ 4)
604 return -1 * trigHelper(input
- RCL_UNITS_PER_SQUARE
/ 2);
606 return trigHelper(RCL_UNITS_PER_SQUARE
- input
);
612 RCL_Unit
RCL_sin(RCL_Unit input
)
614 return RCL_cos(input
- RCL_UNITS_PER_SQUARE
/ 4);
617 RCL_Unit
RCL_tan(RCL_Unit input
)
619 return (RCL_sin(input
) * RCL_UNITS_PER_SQUARE
) / RCL_cos(input
);
622 RCL_Unit
RCL_ctg(RCL_Unit input
)
624 return (RCL_cos(input
) * RCL_UNITS_PER_SQUARE
) / RCL_sin(input
);
627 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
)
631 result
.x
= RCL_cos(angle
);
632 result
.y
= -1 * RCL_sin(angle
);
637 uint16_t RCL_sqrt(RCL_Unit value
)
639 #ifdef RCL_RAYCAST_TINY
642 uint16_t b
= 1u << 14;
646 uint32_t b
= 1u << 30;
657 result
= result
+ 2 * b
;
667 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
)
669 RCL_Unit dx
= p2
.x
- p1
.x
;
670 RCL_Unit dy
= p2
.y
- p1
.y
;
672 #if RCL_USE_DIST_APPROX == 2
673 // octagonal approximation
678 return dy
> dx
? dx
/ 2 + dy
: dy
/ 2 + dx
;
679 #elif RCL_USE_DIST_APPROX == 1
680 // more accurate approximation
682 RCL_Unit a
, b
, result
;
684 dx
= ((dx
< 0) * 2 - 1) * dx
;
685 dy
= ((dy
< 0) * 2 - 1) * dy
;
698 result
= a
+ (44 * b
) / 102;
701 result
-= (5 * a
) / 128;
708 return RCL_sqrt((RCL_Unit
) (dx
+ dy
));
712 RCL_Unit
RCL_len(RCL_Vector2D v
)
718 return RCL_dist(zero
,v
);
721 static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point
, RCL_Ray ray
)
723 RCL_Unit dX
= point
.x
- ray
.start
.x
;
724 RCL_Unit dY
= point
.y
- ray
.start
.y
;
725 return (ray
.direction
.x
* dY
- ray
.direction
.y
* dX
) > 0;
726 // ^ Z component of cross-product
729 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
730 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
731 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
)
733 RCL_Vector2D currentPos
= ray
.start
;
734 RCL_Vector2D currentSquare
;
736 currentSquare
.x
= RCL_divRoundDown(ray
.start
.x
,RCL_UNITS_PER_SQUARE
);
737 currentSquare
.y
= RCL_divRoundDown(ray
.start
.y
,RCL_UNITS_PER_SQUARE
);
741 RCL_Unit squareType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
744 RCL_Vector2D nextSideDist
; // dist. from start to the next side in given axis
746 RCL_Vector2D step
; // -1 or 1 for each axis
747 int8_t stepHorizontal
= 0; // whether the last step was hor. or vert.
752 RCL_Unit dirVecLengthNorm
= RCL_len(ray
.direction
) * RCL_UNITS_PER_SQUARE
;
754 delta
.x
= RCL_abs(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.x
));
755 delta
.y
= RCL_abs(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.y
));
759 if (ray
.direction
.x
< 0)
762 nextSideDist
.x
= (RCL_wrap(ray
.start
.x
,RCL_UNITS_PER_SQUARE
) * delta
.x
) /
763 RCL_UNITS_PER_SQUARE
;
769 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.x
,RCL_UNITS_PER_SQUARE
)) *
770 delta
.x
) / RCL_UNITS_PER_SQUARE
;
773 if (ray
.direction
.y
< 0)
776 nextSideDist
.y
= (RCL_wrap(ray
.start
.y
,RCL_UNITS_PER_SQUARE
) * delta
.y
) /
777 RCL_UNITS_PER_SQUARE
;
783 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.y
,RCL_UNITS_PER_SQUARE
)) *
784 delta
.y
) / RCL_UNITS_PER_SQUARE
;
789 #define RECIP_SCALE 65536
791 RCL_Unit rayDirXRecip
= RECIP_SCALE
/ RCL_nonZero(ray
.direction
.x
);
792 RCL_Unit rayDirYRecip
= RECIP_SCALE
/ RCL_nonZero(ray
.direction
.y
);
793 // ^ we precompute reciprocals to avoid divisions in the loop
795 for (uint16_t i
= 0; i
< constraints
.maxSteps
; ++i
)
797 RCL_Unit currentType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
799 if (RCL_unlikely(currentType
!= squareType
))
805 h
.arrayValue
= currentType
;
807 h
.position
= currentPos
;
808 h
.square
= currentSquare
;
812 h
.position
.x
= currentSquare
.x
* RCL_UNITS_PER_SQUARE
;
818 h
.position
.x
+= RCL_UNITS_PER_SQUARE
;
821 RCL_Unit diff
= h
.position
.x
- ray
.start
.x
;
823 h
.position
.y
= // avoid division by multiplying with reciprocal
824 ray
.start
.y
+ (ray
.direction
.y
* diff
* rayDirXRecip
) / RECIP_SCALE
;
827 /* Here we compute the fish eye corrected distance (perpendicular to
828 the projection plane) as the Euclidean distance divided by the length
829 of the ray direction vector. This can be computed without actually
830 computing Euclidean distances as a hypothenuse A (distance) divided
831 by hypothenuse B (length) is equal to leg A (distance along one axis)
832 divided by leg B (length along the same axis). */
835 (((h
.position
.x
- ray
.start
.x
) / 4) *
836 RCL_UNITS_PER_SQUARE
* rayDirXRecip
)
839 // ^ / 4 is here to prevent overflow
844 h
.position
.y
= currentSquare
.y
* RCL_UNITS_PER_SQUARE
;
850 h
.position
.y
+= RCL_UNITS_PER_SQUARE
;
853 RCL_Unit diff
= h
.position
.y
- ray
.start
.y
;
856 ray
.start
.x
+ (ray
.direction
.x
* diff
* rayDirYRecip
) / RECIP_SCALE
;
860 (((h
.position
.y
- ray
.start
.y
) / 4) *
861 RCL_UNITS_PER_SQUARE
* rayDirYRecip
)
864 // ^ / 4 is here to prevent overflow
869 h
.distance
= RCL_dist(h
.position
,ray
.start
);
872 h
.type
= typeFunc(currentSquare
.x
,currentSquare
.y
);
874 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
877 case 0: h
.textureCoord
=
878 RCL_wrap(-1 * h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
880 case 1: h
.textureCoord
=
881 RCL_wrap(h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
883 case 2: h
.textureCoord
=
884 RCL_wrap(h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
886 case 3: h
.textureCoord
=
887 RCL_wrap(-1 * h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
889 default: h
.textureCoord
= 0; break;
892 if (_RCL_rollFunction
!= 0)
894 h
.doorRoll
= _RCL_rollFunction(currentSquare
.x
,currentSquare
.y
);
896 if (h
.direction
== 0 || h
.direction
== 1)
904 hitResults
[*hitResultsLen
] = h
;
908 squareType
= currentType
;
910 if (*hitResultsLen
>= constraints
.maxHits
)
916 if (nextSideDist
.x
< nextSideDist
.y
)
918 nextSideDist
.x
+= delta
.x
;
919 currentSquare
.x
+= step
.x
;
924 nextSideDist
.y
+= delta
.y
;
925 currentSquare
.y
+= step
.y
;
931 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
)
933 RCL_HitResult result
;
935 RCL_RayConstraints c
;
940 RCL_castRayMultiHit(ray
,arrayFunc
,0,&result
,&RCL_len
,c
);
943 result
.distance
= -1;
948 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
949 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
950 RCL_RayConstraints constraints
)
953 RCL_angleToDirection(cam
.direction
- RCL_HORIZONTAL_FOV_HALF
);
956 RCL_angleToDirection(cam
.direction
+ RCL_HORIZONTAL_FOV_HALF
);
958 /* We scale the side distances so that the middle one is
959 RCL_UNITS_PER_SQUARE, which has to be this way. */
961 RCL_Unit cos
= RCL_nonZero(RCL_cos(RCL_HORIZONTAL_FOV_HALF
));
963 dir1
.x
= (dir1
.x
* RCL_UNITS_PER_SQUARE
) / cos
;
964 dir1
.y
= (dir1
.y
* RCL_UNITS_PER_SQUARE
) / cos
;
966 dir2
.x
= (dir2
.x
* RCL_UNITS_PER_SQUARE
) / cos
;
967 dir2
.y
= (dir2
.y
* RCL_UNITS_PER_SQUARE
) / cos
;
969 RCL_Unit dX
= dir2
.x
- dir1
.x
;
970 RCL_Unit dY
= dir2
.y
- dir1
.y
;
972 RCL_HitResult hits
[constraints
.maxHits
];
976 r
.start
= cam
.position
;
978 RCL_Unit currentDX
= 0;
979 RCL_Unit currentDY
= 0;
981 for (int16_t i
= 0; i
< cam
.resolution
.x
; ++i
)
983 /* Here by linearly interpolating the direction vector its length changes,
984 which in result achieves correcting the fish eye effect (computing
985 perpendicular distance). */
987 r
.direction
.x
= dir1
.x
+ currentDX
/ cam
.resolution
.x
;
988 r
.direction
.y
= dir1
.y
+ currentDY
/ cam
.resolution
.x
;
990 RCL_castRayMultiHit(r
,arrayFunc
,typeFunction
,hits
,&hitCount
,constraints
);
992 columnFunc(hits
,hitCount
,i
,r
);
1000 Helper function that determines intersection with both ceiling and floor.
1002 RCL_Unit
_RCL_floorCeilFunction(int16_t x
, int16_t y
)
1004 RCL_Unit f
= _RCL_floorFunction(x
,y
);
1006 if (_RCL_ceilFunction
== 0)
1009 RCL_Unit c
= _RCL_ceilFunction(x
,y
);
1011 #ifndef RCL_RAYCAST_TINY
1012 return ((f
& 0x0000ffff) << 16) | (c
& 0x0000ffff);
1014 return ((f
& 0x00ff) << 8) | (c
& 0x00ff);
1018 RCL_Unit
_floorHeightNotZeroFunction(int16_t x
, int16_t y
)
1020 return _RCL_floorFunction(x
,y
) == 0 ? 0 :
1021 RCL_nonZero((x
& 0x00FF) | ((y
& 0x00FF) << 8));
1022 // ^ this makes collisions between all squares - needed for rolling doors
1025 RCL_Unit
RCL_adjustDistance(RCL_Unit distance
, RCL_Camera
*camera
,
1028 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1029 possibly be computed more efficiently by not computing Euclidean
1030 distance at all, but rather compute the distance of the collision
1031 point from the projection plane (line). */
1035 RCL_vectorsAngleCos(RCL_angleToDirection(camera
->direction
),
1036 ray
->direction
)) / RCL_UNITS_PER_SQUARE
;
1038 return RCL_nonZero(result
);
1039 // ^ prevent division by zero
1042 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1043 static inline int16_t _RCL_drawHorizontalColumn(
1046 RCL_Unit limit1
, // TODO: int16_t?
1048 RCL_Unit verticalOffset
,
1050 int8_t computeDepth
,
1051 int8_t computeCoords
,
1052 int16_t depthIncrementMultiplier
,
1054 RCL_PixelInfo
*pixelInfo
1059 RCL_Unit depthIncrement
;
1063 pixelInfo
->isWall
= 0;
1065 int16_t limit
= RCL_clamp(yTo
,limit1
,limit2
);
1067 RCL_Unit depth
= 0; /* TODO: this is for clamping depth to 0 so that we don't
1068 have negative depths, but we should do it more
1069 elegantly and efficiently */
1073 /* for performance reasons have different version of the critical loop
1074 to be able to branch early */
1075 #define loop(doDepth,doCoords)\
1077 if (doDepth) /*constant condition - compiler should optimize it out*/\
1079 depth = pixelInfo->depth + RCL_abs(verticalOffset) *\
1080 RCL_VERTICAL_DEPTH_MULTIPLY;\
1081 depthIncrement = depthIncrementMultiplier *\
1082 _RCL_horizontalDepthStep;\
1084 if (doCoords) /*constant condition - compiler should optimize it out*/\
1086 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1087 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1089 for (int16_t i = yCurrent + increment;\
1090 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1093 pixelInfo->position.y = i;\
1094 if (doDepth) /*constant condition - compiler should optimize it out*/\
1096 depth += depthIncrement;\
1097 pixelInfo->depth = RCL_zeroClamp(depth); \
1098 /* ^ int comparison is fast, it is not braching! (= test instr.) */\
1100 if (doCoords) /*constant condition - compiler should optimize it out*/\
1102 RCL_Unit d = _RCL_floorPixelDistances[i];\
1103 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1104 pixelInfo->texCoords.x =\
1105 _RCL_camera.position.x + ((d * dx) / d2);\
1106 pixelInfo->texCoords.y =\
1107 _RCL_camera.position.y + ((d * dy) / d2);\
1109 RCL_PIXEL_FUNCTION(pixelInfo);\
1113 if (computeDepth
) // branch early
1133 /// Helper for drawing walls. Returns the last drawn pixel position.
1134 static inline int16_t _RCL_drawWall(
1138 RCL_Unit limit1
, // TODO: int16_t?
1142 RCL_PixelInfo
*pixelInfo
1147 height
= RCL_abs(height
);
1149 pixelInfo
->isWall
= 1;
1151 RCL_Unit limit
= RCL_clamp(yTo
,limit1
,limit2
);
1153 RCL_Unit wallLength
= RCL_nonZero(RCL_abs(yTo
- yFrom
- 1));
1155 RCL_Unit wallPosition
= RCL_abs(yFrom
- yCurrent
) - increment
;
1157 RCL_Unit heightScaled
= height
* RCL_TEXTURE_INTERPOLATION_SCALE
;
1158 _RCL_UNUSED(heightScaled
);
1160 RCL_Unit coordStepScaled
= RCL_COMPUTE_WALL_TEXCOORDS
?
1161 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1162 ((RCL_UNITS_PER_SQUARE
* RCL_TEXTURE_INTERPOLATION_SCALE
) / wallLength
)
1164 (heightScaled
/ wallLength
)
1168 pixelInfo
->texCoords
.y
= RCL_COMPUTE_WALL_TEXCOORDS
?
1169 (wallPosition
* coordStepScaled
) : 0;
1173 coordStepScaled
*= -1;
1174 pixelInfo
->texCoords
.y
=
1175 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1176 (RCL_UNITS_PER_SQUARE
* RCL_TEXTURE_INTERPOLATION_SCALE
)
1177 - pixelInfo
->texCoords
.y
;
1179 heightScaled
- pixelInfo
->texCoords
.y
;
1184 // with floor wall, don't start under 0
1185 pixelInfo
->texCoords
.y
= RCL_zeroClamp(pixelInfo
->texCoords
.y
);
1188 RCL_Unit textureCoordScaled
= pixelInfo
->texCoords
.y
;
1190 for (RCL_Unit i
= yCurrent
+ increment
;
1191 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1194 pixelInfo
->position
.y
= i
;
1196 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1197 pixelInfo
->texCoords
.y
=
1198 textureCoordScaled
/ RCL_TEXTURE_INTERPOLATION_SCALE
;
1200 textureCoordScaled
+= coordStepScaled
;
1203 RCL_PIXEL_FUNCTION(pixelInfo
);
1209 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1210 static inline void _RCL_makeInfiniteHit(RCL_HitResult
*hit
, RCL_Ray
*ray
)
1212 hit
->distance
= RCL_UNITS_PER_SQUARE
* RCL_UNITS_PER_SQUARE
;
1213 /* ^ horizon is at infinity, but we can't use too big infinity
1214 (RCL_INFINITY) because it would overflow in the following mult. */
1215 hit
->position
.x
= (ray
->direction
.x
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1216 hit
->position
.y
= (ray
->direction
.y
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1219 hit
->textureCoord
= 0;
1220 hit
->arrayValue
= 0;
1225 void _RCL_columnFunctionComplex(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
1228 // last written Y position, can never go backwards
1229 RCL_Unit fPosY
= _RCL_camera
.resolution
.y
;
1230 RCL_Unit cPosY
= -1;
1232 // world coordinates (relative to camera height though)
1233 RCL_Unit fZ1World
= _RCL_startFloorHeight
;
1234 RCL_Unit cZ1World
= _RCL_startCeil_Height
;
1243 // we'll be simulatenously drawing the floor and the ceiling now
1244 for (RCL_Unit j
= 0; j
<= hitCount
; ++j
)
1245 { // ^ = add extra iteration for horizon plane
1246 int8_t drawingHorizon
= j
== hitCount
;
1249 RCL_Unit distance
= 1;
1251 RCL_Unit fWallHeight
= 0, cWallHeight
= 0;
1252 RCL_Unit fZ2World
= 0, cZ2World
= 0;
1253 RCL_Unit fZ1Screen
= 0, cZ1Screen
= 0;
1254 RCL_Unit fZ2Screen
= 0, cZ2Screen
= 0;
1256 if (!drawingHorizon
)
1259 distance
= RCL_nonZero(hit
.distance
);
1262 fWallHeight
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1263 fZ2World
= fWallHeight
- _RCL_camera
.height
;
1264 fZ1Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1265 (fZ1World
* _RCL_camera
.resolution
.y
) /
1266 RCL_UNITS_PER_SQUARE
,distance
);
1267 fZ2Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1268 (fZ2World
* _RCL_camera
.resolution
.y
) /
1269 RCL_UNITS_PER_SQUARE
,distance
);
1271 if (_RCL_ceilFunction
!= 0)
1273 cWallHeight
= _RCL_ceilFunction(hit
.square
.x
,hit
.square
.y
);
1274 cZ2World
= cWallHeight
- _RCL_camera
.height
;
1275 cZ1Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1276 (cZ1World
* _RCL_camera
.resolution
.y
) /
1277 RCL_UNITS_PER_SQUARE
,distance
);
1278 cZ2Screen
= _RCL_middleRow
- RCL_perspectiveScaleVertical(
1279 (cZ2World
* _RCL_camera
.resolution
.y
) /
1280 RCL_UNITS_PER_SQUARE
,distance
);
1285 fZ1Screen
= _RCL_middleRow
;
1286 cZ1Screen
= _RCL_middleRow
+ 1;
1287 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1293 p
.isHorizon
= drawingHorizon
;
1295 // draw floor until wall
1297 p
.height
= fZ1World
+ _RCL_camera
.height
;
1300 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1301 p
.depth
= (_RCL_fHorizontalDepthStart
- fPosY
) * _RCL_horizontalDepthStep
;
1306 limit
= _RCL_drawHorizontalColumn(fPosY
,fZ1Screen
,cPosY
+ 1,
1307 _RCL_camera
.resolution
.y
,fZ1World
,-1,RCL_COMPUTE_FLOOR_DEPTH
,
1308 // ^ purposfully allow outside screen bounds
1309 RCL_COMPUTE_FLOOR_TEXCOORDS
&& p
.height
== RCL_FLOOR_TEXCOORDS_HEIGHT
,
1315 if (_RCL_ceilFunction
!= 0 || drawingHorizon
)
1317 // draw ceiling until wall
1319 p
.height
= cZ1World
+ _RCL_camera
.height
;
1321 #if RCL_COMPUTE_CEILING_DEPTH == 1
1322 p
.depth
= (cPosY
- _RCL_cHorizontalDepthStart
) *
1323 _RCL_horizontalDepthStep
;
1326 limit
= _RCL_drawHorizontalColumn(cPosY
,cZ1Screen
,
1327 -1,fPosY
- 1,cZ1World
,1,RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1328 // ^ purposfully allow outside screen bounds here
1334 if (!drawingHorizon
) // don't draw walls for horizon plane
1339 p
.texCoords
.x
= hit
.textureCoord
;
1340 p
.height
= fZ1World
+ _RCL_camera
.height
;
1341 p
.wallHeight
= fWallHeight
;
1345 if (fPosY
> 0) // still pixels left?
1349 limit
= _RCL_drawWall(fPosY
,fZ1Screen
,fZ2Screen
,cPosY
+ 1,
1350 _RCL_camera
.resolution
.y
,
1351 // ^ purposfully allow outside screen bounds here
1352 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1353 RCL_UNITS_PER_SQUARE
1363 fZ1World
= fZ2World
; // for the next iteration
1364 } // ^ purposfully allow outside screen bounds here
1366 // draw ceiling wall
1368 if (_RCL_ceilFunction
!= 0 && cPosY
< _RCL_camResYLimit
) // pixels left?
1371 p
.height
= cZ1World
+ _RCL_camera
.height
;
1372 p
.wallHeight
= cWallHeight
;
1374 limit
= _RCL_drawWall(cPosY
,cZ1Screen
,cZ2Screen
,
1376 // ^ puposfully allow outside screen bounds here
1377 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1378 RCL_UNITS_PER_SQUARE
1387 cZ1World
= cZ2World
; // for the next iteration
1388 } // ^ puposfully allow outside screen bounds here
1393 void _RCL_columnFunctionSimple(RCL_HitResult
*hits
, uint16_t hitCount
,
1394 uint16_t x
, RCL_Ray ray
)
1397 RCL_Unit wallHeightScreen
= 0;
1398 RCL_Unit wallStart
= _RCL_middleRow
;
1404 p
.wallHeight
= RCL_UNITS_PER_SQUARE
;
1408 RCL_HitResult hit
= hits
[0];
1412 if (_RCL_rollFunction
!= 0 && RCL_COMPUTE_WALL_TEXCOORDS
== 1)
1414 if (hit
.arrayValue
== 0)
1416 // standing inside door square, looking out => move to the next hit
1425 // normal hit, check the door roll
1427 RCL_Unit texCoordMod
= hit
.textureCoord
% RCL_UNITS_PER_SQUARE
;
1429 int8_t unrolled
= hit
.doorRoll
>= 0 ?
1430 (hit
.doorRoll
> texCoordMod
) :
1431 (texCoordMod
> RCL_UNITS_PER_SQUARE
+ hit
.doorRoll
);
1437 if (hitCount
> 1) /* should probably always be true (hit on square
1440 if (hit
.direction
% 2 != hits
[1].direction
% 2)
1442 // hit on the inner side
1446 else if (hitCount
> 2)
1448 // hit on the opposite side
1461 dist
= hit
.distance
;
1463 RCL_Unit wallHeightWorld
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1465 if (wallHeightWorld
< 0)
1467 /* We can't just do wallHeightWorld = max(0,wallHeightWorld) because
1468 we would be processing an actual hit with height 0, which shouldn't
1469 ever happen, so we assign some arbitrary height. */
1471 wallHeightWorld
= RCL_UNITS_PER_SQUARE
;
1474 RCL_Unit worldPointTop
= wallHeightWorld
- _RCL_camera
.height
;
1475 RCL_Unit worldPointBottom
= -1 * _RCL_camera
.height
;
1477 wallStart
= _RCL_middleRow
-
1478 (RCL_perspectiveScaleVertical(worldPointTop
,dist
)
1479 * _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1481 int16_t wallEnd
= _RCL_middleRow
-
1482 (RCL_perspectiveScaleVertical(worldPointBottom
,dist
)
1483 * _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1485 wallHeightScreen
= wallEnd
- wallStart
;
1487 if (wallHeightScreen
<= 0) // can happen because of rounding errors
1488 wallHeightScreen
= 1;
1493 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1502 p
.height
= RCL_UNITS_PER_SQUARE
;
1504 y
= _RCL_drawHorizontalColumn(-1,wallStart
,-1,_RCL_middleRow
,_RCL_camera
.height
,1,
1505 RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1514 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1515 p
.hit
.textureCoord
-= p
.hit
.doorRoll
;
1518 p
.texCoords
.x
= p
.hit
.textureCoord
;
1521 RCL_Unit limit
= _RCL_drawWall(y
,wallStart
,wallStart
+ wallHeightScreen
- 1,
1522 -1,_RCL_camResYLimit
,p
.hit
.arrayValue
,1,&p
);
1524 y
= RCL_max(y
,limit
); // take max, in case no wall was drawn
1525 y
= RCL_max(y
,wallStart
);
1531 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1532 p
.depth
= (_RCL_camera
.resolution
.y
- y
) * _RCL_horizontalDepthStep
+ 1;
1535 _RCL_drawHorizontalColumn(y
,_RCL_camResYLimit
,-1,_RCL_camResYLimit
,
1536 _RCL_camera
.height
,1,RCL_COMPUTE_FLOOR_DEPTH
,RCL_COMPUTE_FLOOR_TEXCOORDS
,
1541 Precomputes a distance from camera to the floor at each screen row into an
1542 array (must be preallocated with sufficient (camera.resolution.y) length).
1544 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera
,
1545 RCL_Unit
*dest
, uint16_t startIndex
)
1547 RCL_Unit camHeightScreenSize
=
1548 (camera
.height
* camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1550 for (uint16_t i
= startIndex
; i
< camera
.resolution
.y
; ++i
)
1551 dest
[i
] = RCL_perspectiveScaleVerticalInverse(camHeightScreenSize
,
1552 RCL_abs(i
- _RCL_middleRow
));
1555 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1556 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
1557 RCL_RayConstraints constraints
)
1559 _RCL_floorFunction
= floorHeightFunc
;
1560 _RCL_ceilFunction
= ceilingHeightFunc
;
1562 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1564 uint16_t halfResY
= cam
.resolution
.y
/ 2;
1566 _RCL_middleRow
= halfResY
+ cam
.shear
;
1568 _RCL_fHorizontalDepthStart
= _RCL_middleRow
+ halfResY
;
1569 _RCL_cHorizontalDepthStart
= _RCL_middleRow
- halfResY
;
1571 _RCL_startFloorHeight
= floorHeightFunc(
1572 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1573 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
;
1575 _RCL_startCeil_Height
=
1576 ceilingHeightFunc
!= 0 ?
1578 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1579 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
1582 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1584 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1585 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1586 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,0);
1587 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1590 RCL_castRaysMultiHit(cam
,_RCL_floorCeilFunction
,typeFunction
,
1591 _RCL_columnFunctionComplex
,constraints
);
1594 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1595 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
1596 RCL_RayConstraints constraints
)
1598 _RCL_floorFunction
= floorHeightFunc
;
1600 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1601 _RCL_middleRow
= cam
.resolution
.y
/ 2;
1602 _RCL_rollFunction
= rollFunc
;
1604 _RCL_cameraHeightScreen
=
1605 (_RCL_camera
.resolution
.y
* (_RCL_camera
.height
- RCL_UNITS_PER_SQUARE
)) /
1606 RCL_UNITS_PER_SQUARE
;
1608 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1610 constraints
.maxHits
=
1611 _RCL_rollFunction
== 0 ?
1612 1 : // no door => 1 hit is enough
1613 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1615 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1616 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1617 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,_RCL_middleRow
);
1618 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1621 RCL_castRaysMultiHit(cam
,_floorHeightNotZeroFunction
,typeFunc
,
1622 _RCL_columnFunctionSimple
, constraints
);
1624 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1625 _RCL_floorPixelDistances
= 0;
1629 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
)
1631 RCL_Vector2D result
;
1632 RCL_Unit l
= RCL_len(v
);
1635 result
.x
= (v
.x
* RCL_UNITS_PER_SQUARE
) / l
;
1636 result
.y
= (v
.y
* RCL_UNITS_PER_SQUARE
) / l
;
1641 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
)
1643 v1
= RCL_normalize(v1
);
1644 v2
= RCL_normalize(v2
);
1646 return (v1
.x
* v2
.x
+ v1
.y
* v2
.y
) / RCL_UNITS_PER_SQUARE
;
1649 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
1652 RCL_PixelInfo result
;
1654 RCL_Vector2D toPoint
;
1656 toPoint
.x
= worldPosition
.x
- camera
.position
.x
;
1657 toPoint
.y
= worldPosition
.y
- camera
.position
.y
;
1659 RCL_Unit middleColumn
= camera
.resolution
.x
/ 2;
1661 // rotate the point to camera space (y left/right, x forw/backw)
1663 RCL_Unit cos
= RCL_cos(camera
.direction
);
1664 RCL_Unit sin
= RCL_sin(camera
.direction
);
1666 RCL_Unit tmp
= toPoint
.x
;
1668 toPoint
.x
= (toPoint
.x
* cos
- toPoint
.y
* sin
) / RCL_UNITS_PER_SQUARE
;
1669 toPoint
.y
= (tmp
* sin
+ toPoint
.y
* cos
) / RCL_UNITS_PER_SQUARE
;
1671 result
.depth
= toPoint
.x
;
1674 middleColumn
+ (-1 * toPoint
.y
* middleColumn
) / RCL_nonZero(result
.depth
);
1677 camera
.resolution
.y
/ 2 -
1678 (RCL_perspectiveScaleVertical(height
- camera
.height
,result
.depth
)
1679 * camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
1685 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
)
1687 return (degrees
* RCL_UNITS_PER_SQUARE
) / 360;
1690 RCL_Unit
RCL_perspectiveScaleVertical(RCL_Unit originalSize
, RCL_Unit distance
)
1692 return distance
!= 0 ?
1693 (originalSize
* RCL_UNITS_PER_SQUARE
) /
1694 ((RCL_VERTICAL_FOV_TAN
* 2 * distance
) / RCL_UNITS_PER_SQUARE
)
1698 RCL_Unit
RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize
,
1699 RCL_Unit scaledSize
)
1701 return scaledSize
!= 0 ?
1702 (originalSize
* RCL_UNITS_PER_SQUARE
+ RCL_UNITS_PER_SQUARE
/ 2) /
1703 // ^ take the middle
1704 ((RCL_VERTICAL_FOV_TAN
* 2 * scaledSize
) / RCL_UNITS_PER_SQUARE
)
1709 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize
, RCL_Unit distance
)
1711 return distance
!= 0 ?
1712 (originalSize
* RCL_UNITS_PER_SQUARE
) /
1713 ((RCL_HORIZONTAL_FOV_TAN
* 2 * distance
) / RCL_UNITS_PER_SQUARE
)
1717 RCL_Unit
RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize
,
1718 RCL_Unit scaledSize
)
1720 return scaledSize
!= 0 ?
1721 (originalSize
* RCL_UNITS_PER_SQUARE
+ RCL_UNITS_PER_SQUARE
/ 2) /
1722 ((RCL_HORIZONTAL_FOV_TAN
* 2 * scaledSize
) / RCL_UNITS_PER_SQUARE
)
1726 RCL_Unit
RCL_castRay3D(
1727 RCL_Vector2D pos1
, RCL_Unit height1
, RCL_Vector2D pos2
, RCL_Unit height2
,
1728 RCL_ArrayFunction floorHeightFunc
, RCL_ArrayFunction ceilingHeightFunc
,
1729 RCL_RayConstraints constraints
)
1731 RCL_HitResult hits
[constraints
.maxHits
];
1740 ray
.direction
.x
= pos2
.x
- pos1
.x
;
1741 ray
.direction
.y
= pos2
.y
- pos1
.y
;
1743 distance
= RCL_len(ray
.direction
);
1745 ray
.direction
= RCL_normalize(ray
.direction
);
1747 RCL_Unit heightDiff
= height2
- height1
;
1749 RCL_castRayMultiHit(ray
,floorHeightFunc
,0,hits
,&numHits
,constraints
);
1751 RCL_Unit result
= RCL_UNITS_PER_SQUARE
;
1753 int16_t squareX
= RCL_divRoundDown(pos1
.x
,RCL_UNITS_PER_SQUARE
);
1754 int16_t squareY
= RCL_divRoundDown(pos1
.y
,RCL_UNITS_PER_SQUARE
);
1756 RCL_Unit startHeight
= floorHeightFunc(squareX
,squareY
);
1758 #define checkHits(comp,res) \
1760 RCL_Unit currentHeight = startHeight; \
1761 for (uint16_t i = 0; i < numHits; ++i) \
1763 if (hits[i].distance > distance) \
1765 RCL_Unit h = hits[i].arrayValue; \
1766 if ((currentHeight comp h ? currentHeight : h) \
1767 comp (height1 + (hits[i].distance * heightDiff) / distance)) \
1769 res = (hits[i].distance * RCL_UNITS_PER_SQUARE) / distance; \
1772 currentHeight = h; \
1778 if (ceilingHeightFunc
!= 0)
1780 RCL_Unit result2
= RCL_UNITS_PER_SQUARE
;
1782 startHeight
= ceilingHeightFunc(squareX
,squareY
);
1784 RCL_castRayMultiHit(ray
,ceilingHeightFunc
,0,hits
,&numHits
,constraints
);
1786 checkHits(<,result2
)
1788 if (result2
< result
)
1797 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
1798 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
1799 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
)
1801 int8_t movesInPlane
= planeOffset
.x
!= 0 || planeOffset
.y
!= 0;
1802 int16_t xSquareNew
, ySquareNew
;
1804 if (movesInPlane
|| force
)
1806 RCL_Vector2D corner
; // BBox corner in the movement direction
1807 RCL_Vector2D cornerNew
;
1809 int16_t xDir
= planeOffset
.x
> 0 ? 1 : -1;
1810 int16_t yDir
= planeOffset
.y
> 0 ? 1 : -1;
1812 corner
.x
= camera
->position
.x
+ xDir
* RCL_CAMERA_COLL_RADIUS
;
1813 corner
.y
= camera
->position
.y
+ yDir
* RCL_CAMERA_COLL_RADIUS
;
1815 int16_t xSquare
= RCL_divRoundDown(corner
.x
,RCL_UNITS_PER_SQUARE
);
1816 int16_t ySquare
= RCL_divRoundDown(corner
.y
,RCL_UNITS_PER_SQUARE
);
1818 cornerNew
.x
= corner
.x
+ planeOffset
.x
;
1819 cornerNew
.y
= corner
.y
+ planeOffset
.y
;
1821 xSquareNew
= RCL_divRoundDown(cornerNew
.x
,RCL_UNITS_PER_SQUARE
);
1822 ySquareNew
= RCL_divRoundDown(cornerNew
.y
,RCL_UNITS_PER_SQUARE
);
1824 RCL_Unit bottomLimit
= -1 * RCL_INFINITY
;
1825 RCL_Unit topLimit
= RCL_INFINITY
;
1827 RCL_Unit currCeilHeight
= RCL_INFINITY
;
1831 bottomLimit
= camera
->height
- RCL_CAMERA_COLL_HEIGHT_BELOW
+
1832 RCL_CAMERA_COLL_STEP_HEIGHT
;
1834 topLimit
= camera
->height
+ RCL_CAMERA_COLL_HEIGHT_ABOVE
;
1836 if (ceilingHeightFunc
!= 0)
1837 currCeilHeight
= ceilingHeightFunc(xSquare
,ySquare
);
1840 // checks a single square for collision against the camera
1841 #define collCheck(dir,s1,s2)\
1844 RCL_Unit height = floorHeightFunc(s1,s2);\
1845 if (height > bottomLimit || \
1846 currCeilHeight - height < \
1847 RCL_CAMERA_COLL_HEIGHT_BELOW + RCL_CAMERA_COLL_HEIGHT_ABOVE)\
1849 else if (ceilingHeightFunc != 0)\
1851 RCL_Unit height2 = ceilingHeightFunc(s1,s2);\
1852 if ((height2 < topLimit) || ((height2 - height) < \
1853 (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))\
1858 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1860 // check a collision against non-diagonal square
1861 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1862 if (dir##SquareNew != dir##Square)\
1864 collCheck(dir,s1,s2)\
1866 if (!dir##Collides)\
1867 { /* now also check for coll on the neighbouring square */ \
1868 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1869 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1870 if (dir2##Square2 != dir2##Square)\
1873 collCheck(dir,dir##SquareNew,dir2##Square2)\
1875 collCheck(dir,dir2##Square2,dir##SquareNew)\
1879 int8_t xCollides
= 0;
1880 collCheckOrtho(x
,y
,xSquareNew
,ySquare
,1)
1882 int8_t yCollides
= 0;
1883 collCheckOrtho(y
,x
,xSquare
,ySquareNew
,0)
1885 #define collHandle(dir)\
1887 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1888 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1891 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1892 collision can't happen */
1894 if (xSquare
!= xSquareNew
&& ySquare
!= ySquareNew
) // corner?
1896 int8_t xyCollides
= 0;
1897 collCheck(xy
,xSquareNew
,ySquareNew
)
1901 // normally should slide, but let's KISS
1913 camera
->position
.x
= cornerNew
.x
- xDir
* RCL_CAMERA_COLL_RADIUS
;
1914 camera
->position
.y
= cornerNew
.y
- yDir
* RCL_CAMERA_COLL_RADIUS
;
1917 if (computeHeight
&& (movesInPlane
|| heightOffset
!= 0 || force
))
1919 camera
->height
+= heightOffset
;
1921 int16_t xSquare1
= RCL_divRoundDown(camera
->position
.x
-
1922 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1924 int16_t xSquare2
= RCL_divRoundDown(camera
->position
.x
+
1925 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1927 int16_t ySquare1
= RCL_divRoundDown(camera
->position
.y
-
1928 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1930 int16_t ySquare2
= RCL_divRoundDown(camera
->position
.y
+
1931 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1933 RCL_Unit bottomLimit
= floorHeightFunc(xSquare1
,ySquare1
);
1934 RCL_Unit topLimit
= ceilingHeightFunc
!= 0 ?
1935 ceilingHeightFunc(xSquare1
,ySquare1
) : RCL_INFINITY
;
1939 #define checkSquares(s1,s2)\
1941 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1942 bottomLimit = RCL_max(bottomLimit,height);\
1943 height = ceilingHeightFunc != 0 ?\
1944 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
1945 topLimit = RCL_min(topLimit,height);\
1948 if (xSquare2
!= xSquare1
)
1951 if (ySquare2
!= ySquare1
)
1954 if (xSquare2
!= xSquare1
&& ySquare2
!= ySquare1
)
1957 camera
->height
= RCL_clamp(camera
->height
,
1958 bottomLimit
+ RCL_CAMERA_COLL_HEIGHT_BELOW
,
1959 topLimit
- RCL_CAMERA_COLL_HEIGHT_ABOVE
);
1965 void RCL_initCamera(RCL_Camera
*camera
)
1967 camera
->position
.x
= 0;
1968 camera
->position
.y
= 0;
1969 camera
->direction
= 0;
1970 camera
->resolution
.x
= 20;
1971 camera
->resolution
.y
= 15;
1973 camera
->height
= RCL_UNITS_PER_SQUARE
;
1976 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
)
1978 constraints
->maxHits
= 1;
1979 constraints
->maxSteps
= 20;