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 / 2)
113 #ifndef RCL_HORIZONTAL_FOV
114 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
117 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
119 #ifndef RCL_CAMERA_COLL_RADIUS
120 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
123 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
124 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
127 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
128 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
131 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
132 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
135 #ifndef RCL_TEXTURE_INTERPOLATION_SCALE
136 #define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed
137 poit vertical texture coord
138 computation. This should be power
139 of two! Higher number can look more
140 accurate but may cause overflow. */
143 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
144 horizon has (the floor
146 approximated with the
149 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
150 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
151 difference when approximating floor/ceil
155 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
156 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
157 #define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions.
158 #define RCL_zeroClamp(x) ((x) * ((x) >= 0))
159 #define RCL_likely(cond) __builtin_expect(!!(cond),1)
160 #define RCL_unlikely(cond) __builtin_expect(!!(cond),0)
162 #define RCL_logV2D(v)\
163 printf("[%d,%d]\n",v.x,v.y);
165 #define RCL_logRay(r){\
168 RCL_logV2D(r.start);\
170 RCL_logV2D(r.direction);}
172 #define RCL_logHitResult(h){\
174 printf(" square: ");\
175 RCL_logV2D(h.square);\
177 RCL_logV2D(h.position);\
178 printf(" dist: %d\n", h.distance);\
179 printf(" dir: %d\n", h.direction);\
180 printf(" texcoord: %d\n", h.textureCoord);}
182 #define RCL_logPixelInfo(p){\
184 printf(" position: ");\
185 RCL_logV2D(p.position);\
186 printf(" texCoord: ");\
187 RCL_logV2D(p.texCoords);\
188 printf(" depth: %d\n", p.depth);\
189 printf(" height: %d\n", p.height);\
190 printf(" wall: %d\n", p.isWall);\
192 RCL_logHitResult(p.hit);\
195 #define RCL_logCamera(c){\
196 printf("camera:\n");\
197 printf(" position: ");\
198 RCL_logV2D(c.position);\
199 printf(" height: %d\n",c.height);\
200 printf(" direction: %d\n",c.direction);\
201 printf(" shear: %d\n",c.shear);\
202 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
205 /// Position in 2D space.
215 RCL_Vector2D direction
;
220 RCL_Unit distance
; /**< Distance to the hit position, or -1 if no
221 collision happened. If RCL_RECTILINEAR != 0, then
222 the distance is perpendicular to the projection
223 plane (fish eye correction), otherwise it is
224 the straight distance to the ray start
226 uint8_t direction
; /**< Direction of hit. The convention for angle
227 units is explained above. */
228 RCL_Unit textureCoord
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
229 texture coordinate (horizontal). */
230 RCL_Vector2D square
; ///< Collided square coordinates.
231 RCL_Vector2D position
; ///< Exact collision position in RCL_Units.
232 RCL_Unit arrayValue
; /** Value returned by array function (most often
233 this will be the floor height). */
234 RCL_Unit type
; /**< Integer identifying type of square (number
235 returned by type function, e.g. texture
237 RCL_Unit doorRoll
; ///< Holds value of door roll.
242 RCL_Vector2D position
;
244 RCL_Vector2D resolution
;
245 int16_t shear
; /**< Shear offset in pixels (0 => no shear), can simulate
251 Holds an information about a single rendered pixel (for a pixel function
252 that works as a fragment shader).
256 RCL_Vector2D position
; ///< On-screen position.
257 int8_t isWall
; ///< Whether the pixel is a wall or a floor/ceiling.
258 int8_t isFloor
; ///< Whether the pixel is floor or ceiling.
259 int8_t isHorizon
; ///< If the pixel belongs to horizon segment.
260 RCL_Unit depth
; ///< Corrected depth.
261 RCL_Unit wallHeight
;///< Only for wall pixels, says its height.
262 RCL_Unit height
; ///< World height (mostly for floor).
263 RCL_HitResult hit
; ///< Corresponding ray hit.
264 RCL_Vector2D texCoords
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
265 texture coordinates. */
268 void RCL_PIXEL_FUNCTION (RCL_PixelInfo
*pixel
);
274 } RCL_RayConstraints
;
277 Function used to retrieve some information about cells of the rendered scene.
278 It should return a characteristic of given square as an integer (e.g. square
279 height, texture index, ...) - between squares that return different numbers
280 there is considered to be a collision.
282 This function should be as fast as possible as it will typically be called
285 typedef RCL_Unit (*RCL_ArrayFunction
)(int16_t x
, int16_t y
);
287 TODO: maybe array functions should be replaced by defines of funtion names
288 like with pixelFunc? Could be more efficient than function pointers.
292 Function that renders a single pixel at the display. It is handed an info
293 about the pixel it should draw.
295 This function should be as fast as possible as it will typically be called
298 typedef void (*RCL_PixelFunction
)(RCL_PixelInfo
*info
);
301 (*RCL_ColumnFunction
)(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
305 Simple-interface function to cast a single ray.
306 @return The first collision result.
308 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
);
311 Maps a single point in the world to the screen (2D position + depth).
313 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
317 Casts a single ray and returns a list of collisions.
319 @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
320 distance is divided by the ray direction vector length (to correct
322 @param arrayFunc function that will be used to determine collisions (hits)
323 with the ray (squares for which this function returns different values
324 are considered to have a collision between them), this will typically
325 be a function returning floor height
326 @param typeFunc optional (can be 0) function - if provided, it will be used
327 to mark the hit result with the number returned by this function
328 (it can be e.g. a texture index)
329 @param hitResults array in which the hit results will be stored (has to be
330 preallocated with at space for at least as many hit results as
331 maxHits specified with the constraints parameter)
332 @param hitResultsLen in this variable the number of hit results will be
334 @param constraints specifies constraints for the ray cast
336 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
337 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
338 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
);
340 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
);
345 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
346 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
347 RCL_UNITS_PER_SQUARE)
349 RCL_Unit
RCL_cosInt(RCL_Unit input
);
351 RCL_Unit
RCL_sinInt(RCL_Unit input
);
353 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
354 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
);
356 /// Computes a cos of an angle between two vectors.
357 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
);
359 uint16_t RCL_sqrtInt(RCL_Unit value
);
360 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
);
361 RCL_Unit
RCL_len(RCL_Vector2D v
);
364 Converts an angle in whole degrees to an angle in RCL_Units that this library
367 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
);
369 ///< Computes the change in size of an object due to perspective.
370 RCL_Unit
RCL_perspectiveScale(RCL_Unit originalSize
, RCL_Unit distance
);
372 RCL_Unit
RCL_perspectiveScaleInverse(RCL_Unit originalSize
,
373 RCL_Unit scaledSize
);
376 Casts rays for given camera view and for each hit calls a user provided
379 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
380 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
381 RCL_RayConstraints constraints
);
384 Using provided functions, renders a complete complex (multilevel) camera
387 This function should render each screen pixel exactly once.
389 function rendering summary:
390 - performance: slower
393 - different wall heights: yes
394 - floor/ceiling textures: no
395 - floor geometry: yes, multilevel
396 - ceiling geometry: yes (optional), multilevel
398 - camera shearing: yes
399 - rendering order: left-to-right, not specifically ordered vertically
401 @param cam camera whose view to render
402 @param floorHeightFunc function that returns floor height (in RCL_Units)
403 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
404 0 (no ceiling will be rendered)
405 @param typeFunction function that says a type of square (e.g. its texture
406 index), can be 0 (no type in hit result)
407 @param pixelFunc callback function to draw a single pixel on screen
408 @param constraints constraints for each cast ray
410 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
411 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
412 RCL_RayConstraints constraints
);
415 Renders given camera view, with help of provided functions. This function is
416 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
419 function rendering summary:
420 - performance: faster
423 - different wall heights: yes
424 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
425 - floor geometry: no (just flat floor, with depth information)
426 - ceiling geometry: no (just flat ceiling, with depth information)
428 - camera shearing: no
429 - rendering order: left-to-right, top-to-bottom
431 Additionally this function supports rendering rolling doors.
433 This function should render each screen pixel exactly once.
435 @param rollFunc function that for given square says its door roll in
436 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
437 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
438 rendering should also be faster as fewer intersections will be tested)
440 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
441 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
442 RCL_RayConstraints constraints
);
445 Function that moves given camera and makes it collide with walls and
446 potentially also floor and ceilings. It's meant to help implement player
449 @param camera camera to move
450 @param planeOffset offset to move the camera in
451 @param heightOffset height offset to move the camera in
452 @param floorHeightFunc function used to retrieve the floor height
453 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
454 (camera won't collide with ceiling)
455 @param computeHeight whether to compute height - if false (0), floor and
456 ceiling functions won't be used and the camera will
457 only collide horizontally with walls (good for simpler
459 @param force if true, forces to recompute collision even if position doesn't
462 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
463 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
464 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
);
466 void RCL_initCamera(RCL_Camera
*camera
);
467 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
);
469 //=============================================================================
472 #define _RCL_UNUSED(what) (void)(what);
474 // global helper variables, for precomputing stuff etc.
475 RCL_Camera _RCL_camera
;
476 RCL_Unit _RCL_horizontalDepthStep
= 0;
477 RCL_Unit _RCL_startFloorHeight
= 0;
478 RCL_Unit _RCL_startCeil_Height
= 0;
479 RCL_Unit _RCL_camResYLimit
= 0;
480 RCL_Unit _RCL_middleRow
= 0;
481 RCL_ArrayFunction _RCL_floorFunction
= 0;
482 RCL_ArrayFunction _RCL_ceilFunction
= 0;
483 RCL_Unit _RCL_fHorizontalDepthStart
= 0;
484 RCL_Unit _RCL_cHorizontalDepthStart
= 0;
485 int16_t _RCL_cameraHeightScreen
= 0;
486 RCL_ArrayFunction _RCL_rollFunction
= 0; // says door rolling
487 RCL_Unit
*_RCL_floorPixelDistances
= 0;
490 // function call counters for profiling
491 uint32_t profile_RCL_sqrtInt
= 0;
492 uint32_t profile_RCL_clamp
= 0;
493 uint32_t profile_RCL_cosInt
= 0;
494 uint32_t profile_RCL_angleToDirection
= 0;
495 uint32_t profile_RCL_dist
= 0;
496 uint32_t profile_RCL_len
= 0;
497 uint32_t profile_RCL_pointIsLeftOfRay
= 0;
498 uint32_t profile_RCL_castRayMultiHit
= 0;
499 uint32_t profile_RCL_castRay
= 0;
500 uint32_t profile_RCL_absVal
= 0;
501 uint32_t profile_RCL_normalize
= 0;
502 uint32_t profile_RCL_vectorsAngleCos
= 0;
503 uint32_t profile_RCL_perspectiveScale
= 0;
504 uint32_t profile_RCL_wrap
= 0;
505 uint32_t profile_RCL_divRoundDown
= 0;
506 #define RCL_profileCall(c) profile_##c += 1
508 #define printProfile() {\
509 printf("profile:\n");\
510 printf(" RCL_sqrtInt: %d\n",profile_RCL_sqrtInt);\
511 printf(" RCL_clamp: %d\n",profile_RCL_clamp);\
512 printf(" RCL_cosInt: %d\n",profile_RCL_cosInt);\
513 printf(" RCL_angleToDirection: %d\n",profile_RCL_angleToDirection);\
514 printf(" RCL_dist: %d\n",profile_RCL_dist);\
515 printf(" RCL_len: %d\n",profile_RCL_len);\
516 printf(" RCL_pointIsLeftOfRay: %d\n",profile_RCL_pointIsLeftOfRay);\
517 printf(" RCL_castRayMultiHit : %d\n",profile_RCL_castRayMultiHit);\
518 printf(" RCL_castRay: %d\n",profile_RCL_castRay);\
519 printf(" RCL_normalize: %d\n",profile_RCL_normalize);\
520 printf(" RCL_vectorsAngleCos: %d\n",profile_RCL_vectorsAngleCos);\
521 printf(" RCL_absVal: %d\n",profile_RCL_absVal);\
522 printf(" RCL_perspectiveScale: %d\n",profile_RCL_perspectiveScale);\
523 printf(" RCL_wrap: %d\n",profile_RCL_wrap);\
524 printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); }
526 #define RCL_profileCall(c)
529 RCL_Unit
RCL_clamp(RCL_Unit value
, RCL_Unit valueMin
, RCL_Unit valueMax
)
531 RCL_profileCall(RCL_clamp
);
533 if (value
>= valueMin
)
535 if (value
<= valueMax
)
544 static inline RCL_Unit
RCL_absVal(RCL_Unit value
)
546 RCL_profileCall(RCL_absVal
);
548 return value
* (((value
>= 0) << 1) - 1);
551 /// Like mod, but behaves differently for negative values.
552 static inline RCL_Unit
RCL_wrap(RCL_Unit value
, RCL_Unit mod
)
554 RCL_profileCall(RCL_wrap
);
555 RCL_Unit cmp
= value
< 0;
556 return cmp
* mod
+ (value
% mod
) - cmp
;
559 /// Performs division, rounding down, NOT towards zero.
560 static inline RCL_Unit
RCL_divRoundDown(RCL_Unit value
, RCL_Unit divisor
)
562 RCL_profileCall(RCL_divRoundDown
);
564 return value
/ divisor
- ((value
>= 0) ? 0 : 1);
567 // Bhaskara's cosine approximation formula
568 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
569 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
570 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
572 #if RCL_USE_COS_LUT == 1
574 #ifdef RCL_RAYCAST_TINY
575 const RCL_Unit cosLUT
[64] =
577 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
580 const RCL_Unit cosLUT
[64] =
582 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
583 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
584 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
585 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
589 #elif RCL_USE_COS_LUT == 2
590 const RCL_Unit cosLUT
[128] =
592 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
593 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
594 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
595 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
596 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
597 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
598 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
599 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
603 RCL_Unit
RCL_cosInt(RCL_Unit input
)
605 RCL_profileCall(RCL_cosInt
);
607 input
= RCL_wrap(input
,RCL_UNITS_PER_SQUARE
);
609 #if RCL_USE_COS_LUT == 1
611 #ifdef RCL_RAYCAST_TINY
612 return cosLUT
[input
];
614 return cosLUT
[input
/ 16];
617 #elif RCL_USE_COS_LUT == 2
618 return cosLUT
[input
/ 8];
620 if (input
< RCL_UNITS_PER_SQUARE
/ 4)
621 return trigHelper(input
);
622 else if (input
< RCL_UNITS_PER_SQUARE
/ 2)
623 return -1 * trigHelper(RCL_UNITS_PER_SQUARE
/ 2 - input
);
624 else if (input
< 3 * RCL_UNITS_PER_SQUARE
/ 4)
625 return -1 * trigHelper(input
- RCL_UNITS_PER_SQUARE
/ 2);
627 return trigHelper(RCL_UNITS_PER_SQUARE
- input
);
633 RCL_Unit
RCL_sinInt(RCL_Unit input
)
635 return RCL_cosInt(input
- RCL_UNITS_PER_SQUARE
/ 4);
638 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
)
640 RCL_profileCall(RCL_angleToDirection
);
644 result
.x
= RCL_cosInt(angle
);
645 result
.y
= -1 * RCL_sinInt(angle
);
650 uint16_t RCL_sqrtInt(RCL_Unit value
)
652 RCL_profileCall(RCL_sqrtInt
);
654 #ifdef RCL_RAYCAST_TINY
657 uint16_t b
= 1u << 14;
661 uint32_t b
= 1u << 30;
672 result
= result
+ 2 * b
;
682 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
)
684 RCL_profileCall(RCL_dist
);
686 RCL_Unit dx
= p2
.x
- p1
.x
;
687 RCL_Unit dy
= p2
.y
- p1
.y
;
689 #if RCL_USE_DIST_APPROX == 2
690 // octagonal approximation
695 return dy
> dx
? dx
/ 2 + dy
: dy
/ 2 + dx
;
696 #elif RCL_USE_DIST_APPROX == 1
697 // more accurate approximation
699 RCL_Unit a
, b
, result
;
701 dx
= ((dx
< 0) * 2 - 1) * dx
;
702 dy
= ((dy
< 0) * 2 - 1) * dy
;
715 result
= a
+ (44 * b
) / 102;
718 result
-= (5 * a
) / 128;
725 return RCL_sqrtInt((RCL_Unit
) (dx
+ dy
));
729 RCL_Unit
RCL_len(RCL_Vector2D v
)
731 RCL_profileCall(RCL_len
);
737 return RCL_dist(zero
,v
);
740 static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point
, RCL_Ray ray
)
742 RCL_profileCall(RCL_pointIsLeftOfRay
);
744 RCL_Unit dX
= point
.x
- ray
.start
.x
;
745 RCL_Unit dY
= point
.y
- ray
.start
.y
;
746 return (ray
.direction
.x
* dY
- ray
.direction
.y
* dX
) > 0;
747 // ^ Z component of cross-product
750 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
751 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
752 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
)
754 RCL_profileCall(RCL_castRayMultiHit
);
756 RCL_Vector2D currentPos
= ray
.start
;
757 RCL_Vector2D currentSquare
;
759 currentSquare
.x
= RCL_divRoundDown(ray
.start
.x
,RCL_UNITS_PER_SQUARE
);
760 currentSquare
.y
= RCL_divRoundDown(ray
.start
.y
,RCL_UNITS_PER_SQUARE
);
764 RCL_Unit squareType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
767 RCL_Vector2D nextSideDist
; // dist. from start to the next side in given axis
769 RCL_Vector2D step
; // -1 or 1 for each axis
770 int8_t stepHorizontal
= 0; // whether the last step was hor. or vert.
775 RCL_Unit dirVecLengthNorm
= RCL_len(ray
.direction
) * RCL_UNITS_PER_SQUARE
;
777 delta
.x
= RCL_absVal(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.x
));
778 delta
.y
= RCL_absVal(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.y
));
782 if (ray
.direction
.x
< 0)
785 nextSideDist
.x
= (RCL_wrap(ray
.start
.x
,RCL_UNITS_PER_SQUARE
) * delta
.x
) /
786 RCL_UNITS_PER_SQUARE
;
792 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.x
,RCL_UNITS_PER_SQUARE
)) *
793 delta
.x
) / RCL_UNITS_PER_SQUARE
;
796 if (ray
.direction
.y
< 0)
799 nextSideDist
.y
= (RCL_wrap(ray
.start
.y
,RCL_UNITS_PER_SQUARE
) * delta
.y
) /
800 RCL_UNITS_PER_SQUARE
;
806 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.y
,RCL_UNITS_PER_SQUARE
)) *
807 delta
.y
) / RCL_UNITS_PER_SQUARE
;
812 #define RECIP_SCALE 65536
814 RCL_Unit rayDirXRecip
= RECIP_SCALE
/ RCL_nonZero(ray
.direction
.x
);
815 RCL_Unit rayDirYRecip
= RECIP_SCALE
/ RCL_nonZero(ray
.direction
.y
);
816 // ^ we precompute reciprocals to avoid divisions in the loop
818 for (uint16_t i
= 0; i
< constraints
.maxSteps
; ++i
)
820 RCL_Unit currentType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
822 if (RCL_unlikely(currentType
!= squareType
))
828 h
.arrayValue
= currentType
;
830 h
.position
= currentPos
;
831 h
.square
= currentSquare
;
835 h
.position
.x
= currentSquare
.x
* RCL_UNITS_PER_SQUARE
;
841 h
.position
.x
+= RCL_UNITS_PER_SQUARE
;
844 RCL_Unit diff
= h
.position
.x
- ray
.start
.x
;
846 h
.position
.y
= // avoid division by multiplying with reciprocal
847 ray
.start
.y
+ ((ray
.direction
.y
* diff
) * rayDirXRecip
) / RECIP_SCALE
;
850 /* Here we compute the fish eye corrected distance (perpendicular to
851 the projection plane) as the Euclidean distance divided by the length
852 of the ray direction vector. This can be computed without actually
853 computing Euclidean distances as a hypothenuse A (distance) divided
854 by hypothenuse B (length) is equal to leg A (distance along one axis)
855 divided by leg B (length along the same axis). */
858 ((h
.position
.x
- ray
.start
.x
) * RCL_UNITS_PER_SQUARE
* rayDirXRecip
)
864 h
.position
.y
= currentSquare
.y
* RCL_UNITS_PER_SQUARE
;
870 h
.position
.y
+= RCL_UNITS_PER_SQUARE
;
873 RCL_Unit diff
= h
.position
.y
- ray
.start
.y
;
876 ray
.start
.x
+ ((ray
.direction
.x
* diff
) * rayDirYRecip
) / RECIP_SCALE
;
880 ((h
.position
.y
- ray
.start
.y
) * RCL_UNITS_PER_SQUARE
* rayDirYRecip
)
886 h
.distance
= RCL_dist(h
.position
,ray
.start
);
890 h
.type
= typeFunc(currentSquare
.x
,currentSquare
.y
);
892 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
895 case 0: h
.textureCoord
=
896 RCL_wrap(-1 * h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
898 case 1: h
.textureCoord
=
899 RCL_wrap(h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
901 case 2: h
.textureCoord
=
902 RCL_wrap(h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
904 case 3: h
.textureCoord
=
905 RCL_wrap(-1 * h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
907 default: h
.textureCoord
= 0; break;
910 if (_RCL_rollFunction
!= 0)
912 h
.doorRoll
= _RCL_rollFunction(currentSquare
.x
,currentSquare
.y
);
914 if (h
.direction
== 0 || h
.direction
== 1)
922 hitResults
[*hitResultsLen
] = h
;
926 squareType
= currentType
;
928 if (*hitResultsLen
>= constraints
.maxHits
)
934 if (nextSideDist
.x
< nextSideDist
.y
)
936 nextSideDist
.x
+= delta
.x
;
937 currentSquare
.x
+= step
.x
;
942 nextSideDist
.y
+= delta
.y
;
943 currentSquare
.y
+= step
.y
;
949 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
)
951 RCL_profileCall(RCL_castRay
);
953 RCL_HitResult result
;
955 RCL_RayConstraints c
;
960 RCL_castRayMultiHit(ray
,arrayFunc
,0,&result
,&RCL_len
,c
);
963 result
.distance
= -1;
968 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
969 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
970 RCL_RayConstraints constraints
)
973 RCL_angleToDirection(cam
.direction
- RCL_HORIZONTAL_FOV_HALF
);
976 RCL_angleToDirection(cam
.direction
+ RCL_HORIZONTAL_FOV_HALF
);
978 RCL_Unit dX
= dir2
.x
- dir1
.x
;
979 RCL_Unit dY
= dir2
.y
- dir1
.y
;
981 RCL_HitResult hits
[constraints
.maxHits
];
985 r
.start
= cam
.position
;
987 RCL_Unit currentDX
= 0;
988 RCL_Unit currentDY
= 0;
990 for (int16_t i
= 0; i
< cam
.resolution
.x
; ++i
)
992 /* Here by linearly interpolating the direction vector its length changes,
993 which in result achieves correcting the fish eye effect (computing
994 perpendicular distance). */
996 r
.direction
.x
= dir1
.x
+ currentDX
/ cam
.resolution
.x
;
997 r
.direction
.y
= dir1
.y
+ currentDY
/ cam
.resolution
.x
;
999 RCL_castRayMultiHit(r
,arrayFunc
,typeFunction
,hits
,&hitCount
,constraints
);
1001 columnFunc(hits
,hitCount
,i
,r
);
1009 Helper function that determines intersection with both ceiling and floor.
1011 RCL_Unit
_RCL_floorCeilFunction(int16_t x
, int16_t y
)
1013 RCL_Unit f
= _RCL_floorFunction(x
,y
);
1015 if (_RCL_ceilFunction
== 0)
1018 RCL_Unit c
= _RCL_ceilFunction(x
,y
);
1020 #ifndef RCL_RAYCAST_TINY
1021 return ((f
& 0x0000ffff) << 16) | (c
& 0x0000ffff);
1023 return ((f
& 0x00ff) << 8) | (c
& 0x00ff);
1027 RCL_Unit
_floorHeightNotZeroFunction(int16_t x
, int16_t y
)
1029 return _RCL_floorFunction(x
,y
) == 0 ? 0 :
1030 RCL_nonZero((x
& 0x00FF) | ((y
& 0x00FF) << 8));
1031 // ^ this makes collisions between all squares - needed for rolling doors
1034 RCL_Unit
RCL_adjustDistance(RCL_Unit distance
, RCL_Camera
*camera
,
1037 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1038 possibly be computed more efficiently by not computing Euclidean
1039 distance at all, but rather compute the distance of the collision
1040 point from the projection plane (line). */
1044 RCL_vectorsAngleCos(RCL_angleToDirection(camera
->direction
),
1045 ray
->direction
)) / RCL_UNITS_PER_SQUARE
;
1047 return RCL_nonZero(result
);
1048 // ^ prevent division by zero
1051 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1052 static inline int16_t _RCL_drawHorizontalColumn(
1055 RCL_Unit limit1
, // TODO: int16_t?
1057 RCL_Unit verticalOffset
,
1059 int8_t computeDepth
,
1060 int8_t computeCoords
,
1061 int16_t depthIncrementMultiplier
,
1063 RCL_PixelInfo
*pixelInfo
1068 RCL_Unit depthIncrement
;
1072 pixelInfo
->isWall
= 0;
1074 int16_t limit
= RCL_clamp(yTo
,limit1
,limit2
);
1076 RCL_Unit depth
= 0; /* TODO: this is for clamping depth to 0 so that we don't
1077 have negative depths, but we should do it more
1078 elegantly and efficiently */
1082 /* for performance reasons have different version of the critical loop
1083 to be able to branch early */
1084 #define loop(doDepth,doCoords)\
1086 if (doDepth) /*constant condition - compiler should optimize it out*/\
1088 depth = pixelInfo->depth + RCL_absVal(verticalOffset) *\
1089 RCL_VERTICAL_DEPTH_MULTIPLY;\
1090 depthIncrement = depthIncrementMultiplier *\
1091 _RCL_horizontalDepthStep;\
1093 if (doCoords) /*constant condition - compiler should optimize it out*/\
1095 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1096 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1098 for (int16_t i = yCurrent + increment;\
1099 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1102 pixelInfo->position.y = i;\
1103 if (doDepth) /*constant condition - compiler should optimize it out*/\
1105 depth += depthIncrement;\
1106 pixelInfo->depth = RCL_zeroClamp(depth); \
1107 /* ^ int comparison is fast, it is not braching! (= test instr.) */\
1109 if (doCoords) /*constant condition - compiler should optimize it out*/\
1111 RCL_Unit d = _RCL_floorPixelDistances[i];\
1112 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1113 pixelInfo->texCoords.x =\
1114 _RCL_camera.position.x + ((d * dx) / d2);\
1115 pixelInfo->texCoords.y =\
1116 _RCL_camera.position.y + ((d * dy) / d2);\
1118 RCL_PIXEL_FUNCTION(pixelInfo);\
1122 if (computeDepth
) // branch early
1142 /// Helper for drawing walls. Returns the last drawn pixel position.
1143 static inline int16_t _RCL_drawWall(
1147 RCL_Unit limit1
, // TODO: int16_t?
1151 RCL_PixelInfo
*pixelInfo
1156 height
= RCL_absVal(height
);
1158 pixelInfo
->isWall
= 1;
1160 RCL_Unit limit
= RCL_clamp(yTo
,limit1
,limit2
);
1162 RCL_Unit wallLength
= RCL_nonZero(RCL_absVal(yTo
- yFrom
- 1));
1164 RCL_Unit wallPosition
= RCL_absVal(yFrom
- yCurrent
) - increment
;
1166 RCL_Unit heightScaled
= height
* RCL_TEXTURE_INTERPOLATION_SCALE
;
1167 _RCL_UNUSED(heightScaled
);
1169 RCL_Unit coordStepScaled
= RCL_COMPUTE_WALL_TEXCOORDS
?
1170 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1171 ((RCL_UNITS_PER_SQUARE
* RCL_TEXTURE_INTERPOLATION_SCALE
) / wallLength
)
1173 (heightScaled
/ wallLength
)
1177 pixelInfo
->texCoords
.y
= RCL_COMPUTE_WALL_TEXCOORDS
?
1178 (wallPosition
* coordStepScaled
) : 0;
1182 coordStepScaled
*= -1;
1183 pixelInfo
->texCoords
.y
=
1184 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1185 (RCL_UNITS_PER_SQUARE
* RCL_TEXTURE_INTERPOLATION_SCALE
)
1186 - pixelInfo
->texCoords
.y
;
1188 heightScaled
- pixelInfo
->texCoords
.y
;
1193 // with floor wall, don't start under 0
1194 pixelInfo
->texCoords
.y
= RCL_zeroClamp(pixelInfo
->texCoords
.y
);
1197 RCL_Unit textureCoordScaled
= pixelInfo
->texCoords
.y
;
1199 for (RCL_Unit i
= yCurrent
+ increment
;
1200 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1203 pixelInfo
->position
.y
= i
;
1205 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1206 pixelInfo
->texCoords
.y
=
1207 textureCoordScaled
/ RCL_TEXTURE_INTERPOLATION_SCALE
;
1209 textureCoordScaled
+= coordStepScaled
;
1212 RCL_PIXEL_FUNCTION(pixelInfo
);
1218 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1219 static inline void _RCL_makeInfiniteHit(RCL_HitResult
*hit
, RCL_Ray
*ray
)
1221 hit
->distance
= RCL_UNITS_PER_SQUARE
* RCL_UNITS_PER_SQUARE
;
1222 /* ^ horizon is at infinity, but we can't use too big infinity
1223 (RCL_INFINITY) because it would overflow in the following mult. */
1224 hit
->position
.x
= (ray
->direction
.x
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1225 hit
->position
.y
= (ray
->direction
.y
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1228 hit
->textureCoord
= 0;
1229 hit
->arrayValue
= 0;
1234 void _RCL_columnFunctionComplex(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
1237 // last written Y position, can never go backwards
1238 RCL_Unit fPosY
= _RCL_camera
.resolution
.y
;
1239 RCL_Unit cPosY
= -1;
1241 // world coordinates (relative to camera height though)
1242 RCL_Unit fZ1World
= _RCL_startFloorHeight
;
1243 RCL_Unit cZ1World
= _RCL_startCeil_Height
;
1252 // we'll be simulatenously drawing the floor and the ceiling now
1253 for (RCL_Unit j
= 0; j
<= hitCount
; ++j
)
1254 { // ^ = add extra iteration for horizon plane
1255 int8_t drawingHorizon
= j
== hitCount
;
1258 RCL_Unit distance
= 1;
1260 RCL_Unit fWallHeight
= 0, cWallHeight
= 0;
1261 RCL_Unit fZ2World
= 0, cZ2World
= 0;
1262 RCL_Unit fZ1Screen
= 0, cZ1Screen
= 0;
1263 RCL_Unit fZ2Screen
= 0, cZ2Screen
= 0;
1265 if (!drawingHorizon
)
1268 distance
= RCL_nonZero(hit
.distance
);
1271 fWallHeight
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1272 fZ2World
= fWallHeight
- _RCL_camera
.height
;
1273 fZ1Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1274 (fZ1World
* _RCL_camera
.resolution
.y
) /
1275 RCL_UNITS_PER_SQUARE
,distance
);
1276 fZ2Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1277 (fZ2World
* _RCL_camera
.resolution
.y
) /
1278 RCL_UNITS_PER_SQUARE
,distance
);
1280 if (_RCL_ceilFunction
!= 0)
1282 cWallHeight
= _RCL_ceilFunction(hit
.square
.x
,hit
.square
.y
);
1283 cZ2World
= cWallHeight
- _RCL_camera
.height
;
1284 cZ1Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1285 (cZ1World
* _RCL_camera
.resolution
.y
) /
1286 RCL_UNITS_PER_SQUARE
,distance
);
1287 cZ2Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1288 (cZ2World
* _RCL_camera
.resolution
.y
) /
1289 RCL_UNITS_PER_SQUARE
,distance
);
1294 fZ1Screen
= _RCL_middleRow
;
1295 cZ1Screen
= _RCL_middleRow
+ 1;
1296 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1302 p
.isHorizon
= drawingHorizon
;
1304 // draw floor until wall
1306 p
.height
= fZ1World
+ _RCL_camera
.height
;
1309 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1310 p
.depth
= (_RCL_fHorizontalDepthStart
- fPosY
) * _RCL_horizontalDepthStep
;
1315 limit
= _RCL_drawHorizontalColumn(fPosY
,fZ1Screen
,cPosY
+ 1,
1316 _RCL_camera
.resolution
.y
,fZ1World
,-1,RCL_COMPUTE_FLOOR_DEPTH
,
1317 // ^ purposfully allow outside screen bounds
1318 RCL_COMPUTE_FLOOR_TEXCOORDS
&& p
.height
== RCL_FLOOR_TEXCOORDS_HEIGHT
,
1324 if (_RCL_ceilFunction
!= 0 || drawingHorizon
)
1326 // draw ceiling until wall
1328 p
.height
= cZ1World
+ _RCL_camera
.height
;
1330 #if RCL_COMPUTE_CEILING_DEPTH == 1
1331 p
.depth
= (cPosY
- _RCL_cHorizontalDepthStart
) *
1332 _RCL_horizontalDepthStep
;
1335 limit
= _RCL_drawHorizontalColumn(cPosY
,cZ1Screen
,
1336 -1,fPosY
- 1,cZ1World
,1,RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1337 // ^ purposfully allow outside screen bounds here
1343 if (!drawingHorizon
) // don't draw walls for horizon plane
1348 p
.texCoords
.x
= hit
.textureCoord
;
1349 p
.height
= fZ1World
+ _RCL_camera
.height
;
1350 p
.wallHeight
= fWallHeight
;
1354 if (fPosY
> 0) // still pixels left?
1358 limit
= _RCL_drawWall(fPosY
,fZ1Screen
,fZ2Screen
,cPosY
+ 1,
1359 _RCL_camera
.resolution
.y
,
1360 // ^ purposfully allow outside screen bounds here
1361 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1362 RCL_UNITS_PER_SQUARE
1372 fZ1World
= fZ2World
; // for the next iteration
1373 } // ^ purposfully allow outside screen bounds here
1375 // draw ceiling wall
1377 if (_RCL_ceilFunction
!= 0 && cPosY
< _RCL_camResYLimit
) // pixels left?
1380 p
.height
= cZ1World
+ _RCL_camera
.height
;
1381 p
.wallHeight
= cWallHeight
;
1383 limit
= _RCL_drawWall(cPosY
,cZ1Screen
,cZ2Screen
,
1385 // ^ puposfully allow outside screen bounds here
1386 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1387 RCL_UNITS_PER_SQUARE
1396 cZ1World
= cZ2World
; // for the next iteration
1397 } // ^ puposfully allow outside screen bounds here
1402 void _RCL_columnFunctionSimple(RCL_HitResult
*hits
, uint16_t hitCount
,
1403 uint16_t x
, RCL_Ray ray
)
1406 RCL_Unit wallHeightScreen
= 0;
1407 RCL_Unit wallStart
= _RCL_middleRow
;
1408 RCL_Unit heightOffset
= 0;
1414 p
.wallHeight
= RCL_UNITS_PER_SQUARE
;
1418 RCL_HitResult hit
= hits
[0];
1422 if (_RCL_rollFunction
!= 0 && RCL_COMPUTE_WALL_TEXCOORDS
== 1)
1424 if (hit
.arrayValue
== 0)
1426 // standing inside door square, looking out => move to the next hit
1435 // normal hit, check the door roll
1437 RCL_Unit texCoordMod
= hit
.textureCoord
% RCL_UNITS_PER_SQUARE
;
1439 int8_t unrolled
= hit
.doorRoll
>= 0 ?
1440 (hit
.doorRoll
> texCoordMod
) :
1441 (texCoordMod
> RCL_UNITS_PER_SQUARE
+ hit
.doorRoll
);
1447 if (hitCount
> 1) /* should probably always be true (hit on square
1450 if (hit
.direction
% 2 != hits
[1].direction
% 2)
1452 // hit on the inner side
1456 else if (hitCount
> 2)
1458 // hit on the opposite side
1471 dist
= hit
.distance
;
1473 int16_t wallHeightWorld
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1475 wallHeightScreen
= RCL_perspectiveScale((wallHeightWorld
*
1476 _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
,dist
);
1478 int16_t RCL_normalizedWallHeight
= wallHeightWorld
!= 0 ?
1479 ((RCL_UNITS_PER_SQUARE
* wallHeightScreen
) / wallHeightWorld
) : 0;
1481 heightOffset
= RCL_perspectiveScale(_RCL_cameraHeightScreen
,dist
);
1483 wallStart
= _RCL_middleRow
- wallHeightScreen
+ heightOffset
+
1484 RCL_normalizedWallHeight
;
1489 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1498 p
.height
= RCL_UNITS_PER_SQUARE
;
1500 y
= _RCL_drawHorizontalColumn(-1,wallStart
,-1,_RCL_middleRow
,_RCL_camera
.height
,1,
1501 RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1510 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1511 p
.hit
.textureCoord
-= p
.hit
.doorRoll
;
1514 p
.texCoords
.x
= p
.hit
.textureCoord
;
1517 RCL_Unit limit
= _RCL_drawWall(y
,wallStart
,wallStart
+ wallHeightScreen
- 1,
1518 -1,_RCL_camResYLimit
,p
.hit
.arrayValue
,1,&p
);
1520 y
= RCL_max(y
,limit
); // take max, in case no wall was drawn
1521 y
= RCL_max(y
,wallStart
);
1527 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1528 p
.depth
= (_RCL_camera
.resolution
.y
- y
) * _RCL_horizontalDepthStep
+ 1;
1531 _RCL_drawHorizontalColumn(y
,_RCL_camResYLimit
,-1,_RCL_camResYLimit
,
1532 _RCL_camera
.height
,1,RCL_COMPUTE_FLOOR_DEPTH
,RCL_COMPUTE_FLOOR_TEXCOORDS
,
1537 Precomputes a distance from camera to the floor at each screen row into an
1538 array (must be preallocated with sufficient (camera.resolution.y) length).
1540 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera
,
1541 RCL_Unit
*dest
, uint16_t startIndex
)
1543 RCL_Unit camHeightScreenSize
=
1544 (camera
.height
* camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1546 for (uint16_t i
= startIndex
1547 ; i
< camera
.resolution
.y
; ++i
)
1548 dest
[i
] = RCL_perspectiveScaleInverse(camHeightScreenSize
,
1549 RCL_absVal(i
- _RCL_middleRow
));
1552 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1553 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
1554 RCL_RayConstraints constraints
)
1556 _RCL_floorFunction
= floorHeightFunc
;
1557 _RCL_ceilFunction
= ceilingHeightFunc
;
1559 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1561 uint16_t halfResY
= cam
.resolution
.y
/ 2;
1563 _RCL_middleRow
= halfResY
+ cam
.shear
;
1565 _RCL_fHorizontalDepthStart
= _RCL_middleRow
+ halfResY
;
1566 _RCL_cHorizontalDepthStart
= _RCL_middleRow
- halfResY
;
1568 _RCL_startFloorHeight
= floorHeightFunc(
1569 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1570 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
;
1572 _RCL_startCeil_Height
=
1573 ceilingHeightFunc
!= 0 ?
1575 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1576 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
1579 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1581 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1582 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1583 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,0);
1584 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1587 RCL_castRaysMultiHit(cam
,_RCL_floorCeilFunction
,typeFunction
,
1588 _RCL_columnFunctionComplex
,constraints
);
1591 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1592 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
1593 RCL_RayConstraints constraints
)
1595 _RCL_floorFunction
= floorHeightFunc
;
1597 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1598 _RCL_middleRow
= cam
.resolution
.y
/ 2;
1599 _RCL_rollFunction
= rollFunc
;
1601 _RCL_cameraHeightScreen
=
1602 (_RCL_camera
.resolution
.y
* (_RCL_camera
.height
- RCL_UNITS_PER_SQUARE
)) /
1603 RCL_UNITS_PER_SQUARE
;
1605 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1607 constraints
.maxHits
=
1608 _RCL_rollFunction
== 0 ?
1609 1 : // no door => 1 hit is enough
1610 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1612 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1613 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1614 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,_RCL_middleRow
);
1615 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1618 RCL_castRaysMultiHit(cam
,_floorHeightNotZeroFunction
,typeFunc
,
1619 _RCL_columnFunctionSimple
, constraints
);
1621 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1622 _RCL_floorPixelDistances
= 0;
1626 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
)
1628 RCL_profileCall(RCL_normalize
);
1630 RCL_Vector2D result
;
1631 RCL_Unit l
= RCL_len(v
);
1634 result
.x
= (v
.x
* RCL_UNITS_PER_SQUARE
) / l
;
1635 result
.y
= (v
.y
* RCL_UNITS_PER_SQUARE
) / l
;
1640 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
)
1642 RCL_profileCall(RCL_vectorsAngleCos
);
1644 v1
= RCL_normalize(v1
);
1645 v2
= RCL_normalize(v2
);
1647 return (v1
.x
* v2
.x
+ v1
.y
* v2
.y
) / RCL_UNITS_PER_SQUARE
;
1650 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
1653 RCL_PixelInfo result
;
1655 RCL_Vector2D toPoint
;
1657 toPoint
.x
= worldPosition
.x
- camera
.position
.x
;
1658 toPoint
.y
= worldPosition
.y
- camera
.position
.y
;
1660 RCL_Unit middleColumn
= camera
.resolution
.x
/ 2;
1664 RCL_Unit cos
= RCL_cosInt(camera
.direction
);
1665 RCL_Unit sin
= RCL_sinInt(camera
.direction
);
1667 RCL_Unit tmp
= toPoint
.x
;
1669 toPoint
.x
= (toPoint
.x
* cos
- toPoint
.y
* sin
) / RCL_UNITS_PER_SQUARE
;
1670 toPoint
.y
= (tmp
* sin
+ toPoint
.y
* cos
) / RCL_UNITS_PER_SQUARE
;
1672 result
.depth
= toPoint
.x
;
1675 middleColumn
+ (-1 * toPoint
.y
* middleColumn
) / RCL_nonZero(result
.depth
);
1677 result
.position
.y
= camera
.resolution
.y
/ 2 -
1678 (camera
.resolution
.y
*
1679 RCL_perspectiveScale(height
- camera
.height
,result
.depth
))
1680 / RCL_UNITS_PER_SQUARE
+ camera
.shear
;
1685 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
)
1687 return (degrees
* RCL_UNITS_PER_SQUARE
) / 360;
1690 RCL_Unit
RCL_perspectiveScale(RCL_Unit originalSize
, RCL_Unit distance
)
1692 RCL_profileCall(RCL_perspectiveScale
);
1694 return distance
!= 0 ?
1695 (originalSize
* RCL_UNITS_PER_SQUARE
) /
1696 ((RCL_VERTICAL_FOV
* 2 * distance
) / RCL_UNITS_PER_SQUARE
)
1700 RCL_Unit
RCL_perspectiveScaleInverse(RCL_Unit originalSize
,
1701 RCL_Unit scaledSize
)
1703 return scaledSize
!= 0 ?
1704 (originalSize
* RCL_UNITS_PER_SQUARE
+ RCL_UNITS_PER_SQUARE
/ 2) /
1705 // ^ take the middle
1706 ((RCL_VERTICAL_FOV
* 2 * scaledSize
) / RCL_UNITS_PER_SQUARE
)
1710 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
1711 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
1712 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
)
1714 int8_t movesInPlane
= planeOffset
.x
!= 0 || planeOffset
.y
!= 0;
1715 int16_t xSquareNew
, ySquareNew
;
1717 if (movesInPlane
|| force
)
1719 RCL_Vector2D corner
; // BBox corner in the movement direction
1720 RCL_Vector2D cornerNew
;
1722 int16_t xDir
= planeOffset
.x
> 0 ? 1 : -1;
1723 int16_t yDir
= planeOffset
.y
> 0 ? 1 : -1;
1725 corner
.x
= camera
->position
.x
+ xDir
* RCL_CAMERA_COLL_RADIUS
;
1726 corner
.y
= camera
->position
.y
+ yDir
* RCL_CAMERA_COLL_RADIUS
;
1728 int16_t xSquare
= RCL_divRoundDown(corner
.x
,RCL_UNITS_PER_SQUARE
);
1729 int16_t ySquare
= RCL_divRoundDown(corner
.y
,RCL_UNITS_PER_SQUARE
);
1731 cornerNew
.x
= corner
.x
+ planeOffset
.x
;
1732 cornerNew
.y
= corner
.y
+ planeOffset
.y
;
1734 xSquareNew
= RCL_divRoundDown(cornerNew
.x
,RCL_UNITS_PER_SQUARE
);
1735 ySquareNew
= RCL_divRoundDown(cornerNew
.y
,RCL_UNITS_PER_SQUARE
);
1737 RCL_Unit bottomLimit
= -1 * RCL_INFINITY
;
1738 RCL_Unit topLimit
= RCL_INFINITY
;
1742 bottomLimit
= camera
->height
- RCL_CAMERA_COLL_HEIGHT_BELOW
+
1743 RCL_CAMERA_COLL_STEP_HEIGHT
;
1745 topLimit
= camera
->height
+ RCL_CAMERA_COLL_HEIGHT_ABOVE
;
1748 // checks a single square for collision against the camera
1749 #define collCheck(dir,s1,s2)\
1752 RCL_Unit height = floorHeightFunc(s1,s2);\
1753 if (height > bottomLimit)\
1755 else if (ceilingHeightFunc != 0)\
1757 height = ceilingHeightFunc(s1,s2);\
1758 if (height < topLimit)\
1763 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1765 // check a collision against non-diagonal square
1766 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1767 if (dir##SquareNew != dir##Square)\
1769 collCheck(dir,s1,s2)\
1771 if (!dir##Collides)\
1772 { /* now also check for coll on the neighbouring square */ \
1773 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1774 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1775 if (dir2##Square2 != dir2##Square)\
1778 collCheck(dir,dir##SquareNew,dir2##Square2)\
1780 collCheck(dir,dir2##Square2,dir##SquareNew)\
1784 int8_t xCollides
= 0;
1785 collCheckOrtho(x
,y
,xSquareNew
,ySquare
,1)
1787 int8_t yCollides
= 0;
1788 collCheckOrtho(y
,x
,xSquare
,ySquareNew
,0)
1790 #define collHandle(dir)\
1792 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1793 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1796 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1797 collision can't happen */
1799 if (xSquare
!= xSquareNew
&& ySquare
!= ySquareNew
) // corner?
1801 int8_t xyCollides
= 0;
1802 collCheck(xy
,xSquareNew
,ySquareNew
)
1806 // normally should slide, but let's KISS
1818 camera
->position
.x
= cornerNew
.x
- xDir
* RCL_CAMERA_COLL_RADIUS
;
1819 camera
->position
.y
= cornerNew
.y
- yDir
* RCL_CAMERA_COLL_RADIUS
;
1822 if (computeHeight
&& (movesInPlane
|| heightOffset
!= 0 || force
))
1824 camera
->height
+= heightOffset
;
1826 int16_t xSquare1
= RCL_divRoundDown(camera
->position
.x
-
1827 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1829 int16_t xSquare2
= RCL_divRoundDown(camera
->position
.x
+
1830 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1832 int16_t ySquare1
= RCL_divRoundDown(camera
->position
.y
-
1833 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1835 int16_t ySquare2
= RCL_divRoundDown(camera
->position
.y
+
1836 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1838 RCL_Unit bottomLimit
= floorHeightFunc(xSquare1
,ySquare1
);
1839 RCL_Unit topLimit
= ceilingHeightFunc
!= 0 ?
1840 ceilingHeightFunc(xSquare1
,ySquare1
) : RCL_INFINITY
;
1844 #define checkSquares(s1,s2)\
1846 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1847 bottomLimit = RCL_max(bottomLimit,height);\
1848 height = ceilingHeightFunc != 0 ?\
1849 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
1850 topLimit = RCL_min(topLimit,height);\
1853 if (xSquare2
!= xSquare1
)
1856 if (ySquare2
!= ySquare1
)
1859 if (xSquare2
!= xSquare1
&& ySquare2
!= ySquare1
)
1862 camera
->height
= RCL_clamp(camera
->height
,
1863 bottomLimit
+ RCL_CAMERA_COLL_HEIGHT_BELOW
,
1864 topLimit
- RCL_CAMERA_COLL_HEIGHT_ABOVE
);
1870 void RCL_initCamera(RCL_Camera
*camera
)
1872 camera
->position
.x
= 0;
1873 camera
->position
.y
= 0;
1874 camera
->direction
= 0;
1875 camera
->resolution
.x
= 20;
1876 camera
->resolution
.y
= 15;
1878 camera
->height
= RCL_UNITS_PER_SQUARE
;
1881 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
)
1883 constraints
->maxHits
= 1;
1884 constraints
->maxSteps
= 20;