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_ACCURATE_WALL_TEXTURING
93 #define RCL_ACCURATE_WALL_TEXTURING 0 /**< If turned on, vertical wall texture
94 coordinates will always be calculated
95 with more precise (but slower) method,
96 otherwise RCL_MIN_TEXTURE_STEP will be
97 used to decide the method. */
100 #ifndef RCL_COMPUTE_FLOOR_DEPTH
101 #define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for
102 floor pixels - turns this off if not
106 #ifndef RCL_COMPUTE_CEILING_DEPTH
107 #define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for
111 #ifndef RCL_ROLL_TEXTURE_COORDS
112 #define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
113 roll the texture coordinates along (mostly
114 desired for doors). */
117 #ifndef RCL_VERTICAL_FOV
118 #define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 2)
121 #ifndef RCL_HORIZONTAL_FOV
122 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
125 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
127 #ifndef RCL_CAMERA_COLL_RADIUS
128 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
131 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
132 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
135 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
136 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
139 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
140 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
143 #ifndef RCL_MIN_TEXTURE_STEP
144 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
145 #define RCL_MIN_TEXTURE_STEP 12 /**< Specifies the minimum step in pixels
146 that can be used to compute texture
147 coordinates in a fast way. Smallet step
148 should be faster (but less accurate). */
150 #define RCL_MIN_TEXTURE_STEP 24
154 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
155 horizon has (the floor
157 approximated with the
160 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
161 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
162 difference when approximating floor/ceil
166 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
167 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
168 #define RCL_nonZero(v) ((v) != 0 ? (v) : 1) ///< To prevent zero divisions.
170 #define RCL_logV2D(v)\
171 printf("[%d,%d]\n",v.x,v.y);
173 #define RCL_logRay(r){\
176 RCL_logV2D(r.start);\
178 RCL_logV2D(r.direction);}
180 #define RCL_logHitResult(h){\
182 printf(" square: ");\
183 RCL_logV2D(h.square);\
185 RCL_logV2D(h.position);\
186 printf(" dist: %d\n", h.distance);\
187 printf(" dir: %d\n", h.direction);\
188 printf(" texcoord: %d\n", h.textureCoord);}
190 #define RCL_logPixelInfo(p){\
192 printf(" position: ");\
193 RCL_logV2D(p.position);\
194 printf(" texCoord: ");\
195 RCL_logV2D(p.texCoords);\
196 printf(" depth: %d\n", p.depth);\
197 printf(" height: %d\n", p.height);\
198 printf(" wall: %d\n", p.isWall);\
200 RCL_logHitResult(p.hit);\
203 #define RCL_logCamera(c){\
204 printf("camera:\n");\
205 printf(" position: ");\
206 RCL_logV2D(c.position);\
207 printf(" height: %d\n",c.height);\
208 printf(" direction: %d\n",c.direction);\
209 printf(" shear: %d\n",c.shear);\
210 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
213 /// Position in 2D space.
223 RCL_Vector2D direction
;
228 RCL_Unit distance
; /**< Distance to the hit position, or -1 if no
229 collision happened. If RCL_RECTILINEAR != 0, then
230 the distance is perpendicular to the projection
231 plane (fish eye correction), otherwise it is
232 the straight distance to the ray start
234 uint8_t direction
; /**< Direction of hit. The convention for angle
235 units is explained above. */
236 RCL_Unit textureCoord
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
237 texture coordinate (horizontal). */
238 RCL_Vector2D square
; ///< Collided square coordinates.
239 RCL_Vector2D position
; ///< Exact collision position in RCL_Units.
240 RCL_Unit arrayValue
; /** Value returned by array function (most often
241 this will be the floor height). */
242 RCL_Unit type
; /**< Integer identifying type of square (number
243 returned by type function, e.g. texture
245 RCL_Unit doorRoll
; ///< Holds value of door roll.
250 RCL_Vector2D position
;
252 RCL_Vector2D resolution
;
253 int16_t shear
; /**< Shear offset in pixels (0 => no shear), can simulate
259 Holds an information about a single rendered pixel (for a pixel function
260 that works as a fragment shader).
264 RCL_Vector2D position
; ///< On-screen position.
265 int8_t isWall
; ///< Whether the pixel is a wall or a floor/ceiling.
266 int8_t isFloor
; ///< Whether the pixel is floor or ceiling.
267 int8_t isHorizon
; ///< If the pixel belongs to horizon segment.
268 RCL_Unit depth
; ///< Corrected depth.
269 RCL_Unit height
; ///< World height (mostly for floor).
270 RCL_HitResult hit
; ///< Corresponding ray hit.
271 RCL_Vector2D texCoords
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
272 texture coordinates. */
275 void RCL_PIXEL_FUNCTION (RCL_PixelInfo
*pixel
);
281 } RCL_RayConstraints
;
284 Function used to retrieve some information about cells of the rendered scene.
285 It should return a characteristic of given square as an integer (e.g. square
286 height, texture index, ...) - between squares that return different numbers
287 there is considered to be a collision.
289 This function should be as fast as possible as it will typically be called
292 typedef RCL_Unit (*RCL_ArrayFunction
)(int16_t x
, int16_t y
);
295 Function that renders a single pixel at the display. It is handed an info
296 about the pixel it should draw.
298 This function should be as fast as possible as it will typically be called
301 typedef void (*RCL_PixelFunction
)(RCL_PixelInfo
*info
);
304 (*RCL_ColumnFunction
)(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
308 Simple-interface function to cast a single ray.
309 @return The first collision result.
311 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
);
314 Maps a single point in the world to the screen (2D position + depth).
316 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
320 Casts a single ray and returns a list of collisions.
322 @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
323 distance is divided by the ray direction vector length (to correct
325 @param arrayFunc function that will be used to determine collisions (hits)
326 with the ray (squares for which this function returns different values
327 are considered to have a collision between them), this will typically
328 be a function returning floor height
329 @param typeFunc optional (can be 0) function - if provided, it will be used
330 to mark the hit result with the number returned by this function
331 (it can be e.g. a texture index)
332 @param hitResults array in which the hit results will be stored (has to be
333 preallocated with at space for at least as many hit results as
334 maxHits specified with the constraints parameter)
335 @param hitResultsLen in this variable the number of hit results will be
337 @param constraints specifies constraints for the ray cast
339 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
340 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
341 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
);
343 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
);
348 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
349 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
350 RCL_UNITS_PER_SQUARE)
352 RCL_Unit
RCL_cosInt(RCL_Unit input
);
354 RCL_Unit
RCL_sinInt(RCL_Unit input
);
356 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
357 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
);
359 /// Computes a cos of an angle between two vectors.
360 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
);
362 uint16_t RCL_sqrtInt(RCL_Unit value
);
363 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
);
364 RCL_Unit
RCL_len(RCL_Vector2D v
);
367 Converts an angle in whole degrees to an angle in RCL_Units that this library
370 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
);
372 ///< Computes the change in size of an object due to perspective.
373 RCL_Unit
RCL_perspectiveScale(RCL_Unit originalSize
, RCL_Unit distance
);
375 RCL_Unit
RCL_perspectiveScaleInverse(RCL_Unit originalSize
,
376 RCL_Unit scaledSize
);
379 Casts rays for given camera view and for each hit calls a user provided
382 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
383 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
384 RCL_RayConstraints constraints
);
387 Using provided functions, renders a complete complex (multilevel) camera
390 This function should render each screen pixel exactly once.
392 function rendering summary:
393 - performance: slower
396 - different wall heights: yes
397 - floor/ceiling textures: no
398 - floor geometry: yes, multilevel
399 - ceiling geometry: yes (optional), multilevel
401 - camera shearing: yes
402 - rendering order: left-to-right, not specifically ordered vertically
404 @param cam camera whose view to render
405 @param floorHeightFunc function that returns floor height (in RCL_Units)
406 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
407 0 (no ceiling will be rendered)
408 @param typeFunction function that says a type of square (e.g. its texture
409 index), can be 0 (no type in hit result)
410 @param pixelFunc callback function to draw a single pixel on screen
411 @param constraints constraints for each cast ray
413 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
414 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
415 RCL_RayConstraints constraints
);
418 Renders given camera view, with help of provided functions. This function is
419 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
422 function rendering summary:
423 - performance: faster
426 - different wall heights: yes
427 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
428 - floor geometry: no (just flat floor, with depth information)
429 - ceiling geometry: no (just flat ceiling, with depth information)
431 - camera shearing: no
432 - rendering order: left-to-right, top-to-bottom
434 Additionally this function supports rendering rolling doors.
436 This function should render each screen pixel exactly once.
438 @param rollFunc function that for given square says its door roll in
439 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
440 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
441 rendering should also be faster as fewer intersections will be tested)
443 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
444 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
445 RCL_RayConstraints constraints
);
448 Function that moves given camera and makes it collide with walls and
449 potentially also floor and ceilings. It's meant to help implement player
452 @param camera camera to move
453 @param planeOffset offset to move the camera in
454 @param heightOffset height offset to move the camera in
455 @param floorHeightFunc function used to retrieve the floor height
456 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
457 (camera won't collide with ceiling)
458 @param computeHeight whether to compute height - if false (0), floor and
459 ceiling functions won't be used and the camera will
460 only collide horizontally with walls (good for simpler
462 @param force if true, forces to recompute collision even if position doesn't
465 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
466 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
467 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
);
469 void RCL_initCamera(RCL_Camera
*camera
);
470 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
);
472 //=============================================================================
475 #define _RCL_UNUSED(what) (void)(what);
477 // global helper variables, for precomputing stuff etc.
478 RCL_Camera _RCL_camera
;
479 RCL_Unit _RCL_horizontalDepthStep
= 0;
480 RCL_Unit _RCL_startFloorHeight
= 0;
481 RCL_Unit _RCL_startCeil_Height
= 0;
482 RCL_Unit _RCL_camResYLimit
= 0;
483 RCL_Unit _RCL_middleRow
= 0;
484 RCL_ArrayFunction _RCL_floorFunction
= 0;
485 RCL_ArrayFunction _RCL_ceilFunction
= 0;
486 RCL_Unit _RCL_fHorizontalDepthStart
= 0;
487 RCL_Unit _RCL_cHorizontalDepthStart
= 0;
488 int16_t _RCL_cameraHeightScreen
= 0;
489 RCL_ArrayFunction _RCL_rollFunction
= 0; // says door rolling
490 RCL_Unit
*_RCL_floorPixelDistances
= 0;
493 // function call counters for profiling
494 uint32_t profile_RCL_sqrtInt
= 0;
495 uint32_t profile_RCL_clamp
= 0;
496 uint32_t profile_RCL_cosInt
= 0;
497 uint32_t profile_RCL_angleToDirection
= 0;
498 uint32_t profile_RCL_dist
= 0;
499 uint32_t profile_RCL_len
= 0;
500 uint32_t profile_RCL_pointIfLeftOfRay
= 0;
501 uint32_t profile_RCL_castRayMultiHit
= 0;
502 uint32_t profile_RCL_castRay
= 0;
503 uint32_t profile_RCL_absVal
= 0;
504 uint32_t profile_RCL_normalize
= 0;
505 uint32_t profile_RCL_vectorsAngleCos
= 0;
506 uint32_t profile_RCL_perspectiveScale
= 0;
507 uint32_t profile_RCL_wrap
= 0;
508 uint32_t profile_RCL_divRoundDown
= 0;
509 #define RCL_profileCall(c) profile_##c += 1
511 #define printProfile() {\
512 printf("profile:\n");\
513 printf(" RCL_sqrtInt: %d\n",profile_RCL_sqrtInt);\
514 printf(" RCL_clamp: %d\n",profile_RCL_clamp);\
515 printf(" RCL_cosInt: %d\n",profile_RCL_cosInt);\
516 printf(" RCL_angleToDirection: %d\n",profile_RCL_angleToDirection);\
517 printf(" RCL_dist: %d\n",profile_RCL_dist);\
518 printf(" RCL_len: %d\n",profile_RCL_len);\
519 printf(" RCL_pointIfLeftOfRay: %d\n",profile_RCL_pointIfLeftOfRay);\
520 printf(" RCL_castRayMultiHit : %d\n",profile_RCL_castRayMultiHit);\
521 printf(" RCL_castRay: %d\n",profile_RCL_castRay);\
522 printf(" RCL_normalize: %d\n",profile_RCL_normalize);\
523 printf(" RCL_vectorsAngleCos: %d\n",profile_RCL_vectorsAngleCos);\
524 printf(" RCL_absVal: %d\n",profile_RCL_absVal);\
525 printf(" RCL_perspectiveScale: %d\n",profile_RCL_perspectiveScale);\
526 printf(" RCL_wrap: %d\n",profile_RCL_wrap);\
527 printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); }
529 #define RCL_profileCall(c)
532 RCL_Unit
RCL_clamp(RCL_Unit value
, RCL_Unit valueMin
, RCL_Unit valueMax
)
534 RCL_profileCall(RCL_clamp
);
536 if (value
>= valueMin
)
538 if (value
<= valueMax
)
547 static inline RCL_Unit
RCL_absVal(RCL_Unit value
)
549 RCL_profileCall(RCL_absVal
);
551 return value
>= 0 ? value
: -1 * value
;
554 /// Like mod, but behaves differently for negative values.
555 static inline RCL_Unit
RCL_wrap(RCL_Unit value
, RCL_Unit mod
)
557 RCL_profileCall(RCL_wrap
);
559 return value
>= 0 ? (value
% mod
) : (mod
+ (value
% mod
) - 1);
562 /// Performs division, rounding down, NOT towards zero.
563 static inline RCL_Unit
RCL_divRoundDown(RCL_Unit value
, RCL_Unit divisor
)
565 RCL_profileCall(RCL_divRoundDown
);
567 return value
/ divisor
- ((value
>= 0) ? 0 : 1);
570 // Bhaskara's cosine approximation formula
571 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
572 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
573 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
575 #if RCL_USE_COS_LUT == 1
577 #ifdef RCL_RAYCAST_TINY
578 const RCL_Unit cosLUT
[64] =
580 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
583 const RCL_Unit cosLUT
[64] =
585 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
586 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
587 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
588 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
592 #elif RCL_USE_COS_LUT == 2
593 const RCL_Unit cosLUT
[128] =
595 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
596 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
597 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
598 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
599 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
600 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
601 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
602 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
606 RCL_Unit
RCL_cosInt(RCL_Unit input
)
608 RCL_profileCall(RCL_cosInt
);
610 input
= RCL_wrap(input
,RCL_UNITS_PER_SQUARE
);
612 #if RCL_USE_COS_LUT == 1
614 #ifdef RCL_RAYCAST_TINY
615 return cosLUT
[input
];
617 return cosLUT
[input
/ 16];
620 #elif RCL_USE_COS_LUT == 2
621 return cosLUT
[input
/ 8];
623 if (input
< RCL_UNITS_PER_SQUARE
/ 4)
624 return trigHelper(input
);
625 else if (input
< RCL_UNITS_PER_SQUARE
/ 2)
626 return -1 * trigHelper(RCL_UNITS_PER_SQUARE
/ 2 - input
);
627 else if (input
< 3 * RCL_UNITS_PER_SQUARE
/ 4)
628 return -1 * trigHelper(input
- RCL_UNITS_PER_SQUARE
/ 2);
630 return trigHelper(RCL_UNITS_PER_SQUARE
- input
);
636 RCL_Unit
RCL_sinInt(RCL_Unit input
)
638 return RCL_cosInt(input
- RCL_UNITS_PER_SQUARE
/ 4);
641 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
)
643 RCL_profileCall(RCL_angleToDirection
);
647 result
.x
= RCL_cosInt(angle
);
648 result
.y
= -1 * RCL_sinInt(angle
);
653 uint16_t RCL_sqrtInt(RCL_Unit value
)
655 RCL_profileCall(RCL_sqrtInt
);
657 #ifdef RCL_RAYCAST_TINY
660 uint16_t b
= 1u << 14;
664 uint32_t b
= 1u << 30;
675 result
= result
+ 2 * b
;
685 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
)
687 RCL_profileCall(RCL_dist
);
689 RCL_Unit dx
= p2
.x
- p1
.x
;
690 RCL_Unit dy
= p2
.y
- p1
.y
;
692 #if RCL_USE_DIST_APPROX == 2
693 // octagonal approximation
698 return dy
> dx
? dx
/ 2 + dy
: dy
/ 2 + dx
;
699 #elif RCL_USE_DIST_APPROX == 1
700 // more accurate approximation
702 RCL_Unit a
, b
, result
;
704 dx
= dx
< 0 ? -1 * dx
: dx
;
705 dy
= dy
< 0 ? -1 * dy
: dy
;
718 result
= a
+ (44 * b
) / 102;
721 result
-= (5 * a
) / 128;
728 return RCL_sqrtInt((RCL_Unit
) (dx
+ dy
));
732 RCL_Unit
RCL_len(RCL_Vector2D v
)
734 RCL_profileCall(RCL_len
);
740 return RCL_dist(zero
,v
);
743 static inline int8_t RCL_pointIfLeftOfRay(RCL_Vector2D point
, RCL_Ray ray
)
745 RCL_profileCall(RCL_pointIfLeftOfRay
);
747 RCL_Unit dX
= point
.x
- ray
.start
.x
;
748 RCL_Unit dY
= point
.y
- ray
.start
.y
;
749 return (ray
.direction
.x
* dY
- ray
.direction
.y
* dX
) > 0;
750 // ^ Z component of cross-product
753 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
754 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
755 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
)
757 RCL_profileCall(RCL_castRayMultiHit
);
759 RCL_Vector2D currentPos
= ray
.start
;
760 RCL_Vector2D currentSquare
;
762 currentSquare
.x
= RCL_divRoundDown(ray
.start
.x
,RCL_UNITS_PER_SQUARE
);
763 currentSquare
.y
= RCL_divRoundDown(ray
.start
.y
,RCL_UNITS_PER_SQUARE
);
767 RCL_Unit squareType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
770 RCL_Vector2D nextSideDist
; // dist. from start to the next side in given axis
772 RCL_Vector2D step
; // -1 or 1 for each axis
773 int8_t stepHorizontal
= 0; // whether the last step was hor. or vert.
778 RCL_Unit dirVecLengthNorm
= RCL_len(ray
.direction
) * RCL_UNITS_PER_SQUARE
;
780 delta
.x
= RCL_absVal(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.x
));
781 delta
.y
= RCL_absVal(dirVecLengthNorm
/ RCL_nonZero(ray
.direction
.y
));
785 if (ray
.direction
.x
< 0)
788 nextSideDist
.x
= (RCL_wrap(ray
.start
.x
,RCL_UNITS_PER_SQUARE
) * delta
.x
) /
789 RCL_UNITS_PER_SQUARE
;
795 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.x
,RCL_UNITS_PER_SQUARE
)) *
796 delta
.x
) / RCL_UNITS_PER_SQUARE
;
799 if (ray
.direction
.y
< 0)
802 nextSideDist
.y
= (RCL_wrap(ray
.start
.y
,RCL_UNITS_PER_SQUARE
) * delta
.y
) /
803 RCL_UNITS_PER_SQUARE
;
809 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.y
,RCL_UNITS_PER_SQUARE
)) *
810 delta
.y
) / RCL_UNITS_PER_SQUARE
;
815 for (uint16_t i
= 0; i
< constraints
.maxSteps
; ++i
)
817 RCL_Unit currentType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
819 if (currentType
!= squareType
)
825 h
.arrayValue
= currentType
;
827 h
.position
= currentPos
;
828 h
.square
= currentSquare
;
832 h
.position
.x
= currentSquare
.x
* RCL_UNITS_PER_SQUARE
;
838 h
.position
.x
+= RCL_UNITS_PER_SQUARE
;
841 RCL_Unit diff
= h
.position
.x
- ray
.start
.x
;
842 h
.position
.y
= ray
.start
.y
+ ((ray
.direction
.y
* diff
) /
843 RCL_nonZero(ray
.direction
.x
));
846 /* Here we compute the fish eye corrected distance (perpendicular to
847 the projection plane) as the Euclidean distance divided by the length
848 of the ray direction vector. This can be computed without actually
849 computing Euclidean distances as a hypothenuse A (distance) divided
850 by hypothenuse B (length) is equal to leg A (distance along one axis)
851 divided by leg B (length along the same axis). */
854 ((h
.position
.x
- ray
.start
.x
) * RCL_UNITS_PER_SQUARE
) /
855 RCL_nonZero(ray
.direction
.x
);
860 h
.position
.y
= currentSquare
.y
* RCL_UNITS_PER_SQUARE
;
866 h
.position
.y
+= RCL_UNITS_PER_SQUARE
;
869 RCL_Unit diff
= h
.position
.y
- ray
.start
.y
;
870 h
.position
.x
= ray
.start
.x
+ ((ray
.direction
.x
* diff
) /
871 RCL_nonZero(ray
.direction
.y
));
875 ((h
.position
.y
- ray
.start
.y
) * RCL_UNITS_PER_SQUARE
) /
876 RCL_nonZero(ray
.direction
.y
);
881 h
.distance
= RCL_dist(h
.position
,ray
.start
);
885 h
.type
= typeFunc(currentSquare
.x
,currentSquare
.y
);
887 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
890 case 0: h
.textureCoord
=
891 RCL_wrap(-1 * h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
893 case 1: h
.textureCoord
=
894 RCL_wrap(h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
896 case 2: h
.textureCoord
=
897 RCL_wrap(h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
899 case 3: h
.textureCoord
=
900 RCL_wrap(-1 * h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
902 default: h
.textureCoord
= 0; break;
905 if (_RCL_rollFunction
!= 0)
907 h
.doorRoll
= _RCL_rollFunction(currentSquare
.x
,currentSquare
.y
);
909 if (h
.direction
== 0 || h
.direction
== 1)
917 hitResults
[*hitResultsLen
] = h
;
921 squareType
= currentType
;
923 if (*hitResultsLen
>= constraints
.maxHits
)
929 if (nextSideDist
.x
< nextSideDist
.y
)
931 nextSideDist
.x
+= delta
.x
;
932 currentSquare
.x
+= step
.x
;
937 nextSideDist
.y
+= delta
.y
;
938 currentSquare
.y
+= step
.y
;
944 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
)
946 RCL_profileCall(RCL_castRay
);
948 RCL_HitResult result
;
950 RCL_RayConstraints c
;
955 RCL_castRayMultiHit(ray
,arrayFunc
,0,&result
,&RCL_len
,c
);
958 result
.distance
= -1;
963 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
964 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
965 RCL_RayConstraints constraints
)
968 RCL_angleToDirection(cam
.direction
- RCL_HORIZONTAL_FOV_HALF
);
971 RCL_angleToDirection(cam
.direction
+ RCL_HORIZONTAL_FOV_HALF
);
973 RCL_Unit dX
= dir2
.x
- dir1
.x
;
974 RCL_Unit dY
= dir2
.y
- dir1
.y
;
976 RCL_HitResult hits
[constraints
.maxHits
];
980 r
.start
= cam
.position
;
982 RCL_Unit currentDX
= 0;
983 RCL_Unit currentDY
= 0;
985 for (int16_t i
= 0; i
< cam
.resolution
.x
; ++i
)
987 /* Here by linearly interpolating the direction vector its length changes,
988 which in result achieves correcting the fish eye effect (computing
989 perpendicular distance). */
991 r
.direction
.x
= dir1
.x
+ currentDX
/ cam
.resolution
.x
;
992 r
.direction
.y
= dir1
.y
+ currentDY
/ cam
.resolution
.x
;
994 RCL_castRayMultiHit(r
,arrayFunc
,typeFunction
,hits
,&hitCount
,constraints
);
996 columnFunc(hits
,hitCount
,i
,r
);
1004 Helper function that determines intersection with both ceiling and floor.
1006 RCL_Unit
_RCL_floorCeilFunction(int16_t x
, int16_t y
)
1008 RCL_Unit f
= _RCL_floorFunction(x
,y
);
1010 if (_RCL_ceilFunction
== 0)
1013 RCL_Unit c
= _RCL_ceilFunction(x
,y
);
1015 #ifndef RCL_RAYCAST_TINY
1016 return ((f
& 0x0000ffff) << 16) | (c
& 0x0000ffff);
1018 return ((f
& 0x00ff) << 8) | (c
& 0x00ff);
1022 RCL_Unit
_floorHeightNotZeroFunction(int16_t x
, int16_t y
)
1024 return _RCL_floorFunction(x
,y
) == 0 ? 0 :
1025 RCL_nonZero((x
& 0x00FF) | ((y
& 0x00FF) << 8));
1026 // ^ this makes collisions between all squares - needed for rolling doors
1029 RCL_Unit
RCL_adjustDistance(RCL_Unit distance
, RCL_Camera
*camera
,
1032 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1033 possibly be computed more efficiently by not computing Euclidean
1034 distance at all, but rather compute the distance of the collision
1035 point from the projection plane (line). */
1039 RCL_vectorsAngleCos(RCL_angleToDirection(camera
->direction
),
1040 ray
->direction
)) / RCL_UNITS_PER_SQUARE
;
1042 return RCL_nonZero(result
);
1043 // ^ prevent division by zero
1046 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1047 static inline int16_t _RCL_drawVertical(
1050 RCL_Unit limit1
, // TODO: int16_t?
1052 RCL_Unit verticalOffset
,
1054 int8_t computeDepth
,
1055 int8_t computeCoords
,
1056 int16_t depthIncrementMultiplier
,
1058 RCL_PixelInfo
*pixelInfo
1063 RCL_Unit depthIncrement
;
1067 pixelInfo
->isWall
= 0;
1069 int16_t limit
= RCL_clamp(yTo
,limit1
,limit2
);
1071 /* for performance reasons have different version of the critical loop
1072 to be able to branch early */
1073 #define loop(doDepth,doCoords)\
1075 if (doDepth) /*constant condition - compiler should optimize it out*/\
1077 pixelInfo->depth += RCL_absVal(verticalOffset) *\
1078 RCL_VERTICAL_DEPTH_MULTIPLY;\
1079 depthIncrement = depthIncrementMultiplier *\
1080 _RCL_horizontalDepthStep;\
1082 if (doCoords) /*constant condition - compiler should optimize it out*/\
1084 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1085 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1087 for (int16_t i = yCurrent + increment;\
1088 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1091 pixelInfo->position.y = i;\
1092 if (doDepth) /*constant condition - compiler should optimize it out*/\
1093 pixelInfo->depth += depthIncrement;\
1094 if (doCoords) /*constant condition - compiler should optimize it out*/\
1096 RCL_Unit d = _RCL_floorPixelDistances[i];\
1097 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1098 pixelInfo->texCoords.x =\
1099 _RCL_camera.position.x + ((d * dx) / d2);\
1100 pixelInfo->texCoords.y =\
1101 _RCL_camera.position.y + ((d * dy) / d2);\
1103 RCL_PIXEL_FUNCTION(pixelInfo);\
1107 if (computeDepth
) // branch early
1127 /// Helper for drawing walls. Returns the last drawn pixel position.
1128 static inline int16_t _RCL_drawWall(
1132 RCL_Unit limit1
, // TODO: int16_t?
1136 RCL_PixelInfo
*pixelInfo
1141 pixelInfo
->isWall
= 1;
1143 RCL_Unit limit
= RCL_clamp(yTo
,limit1
,limit2
);
1145 RCL_Unit wallLength
= yTo
- yFrom
- 1;
1146 wallLength
= RCL_nonZero(wallLength
);
1148 RCL_Unit wallPosition
= RCL_absVal(yFrom
- yCurrent
) - increment
;
1150 RCL_Unit coordStep
= RCL_COMPUTE_WALL_TEXCOORDS
?
1151 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1152 RCL_UNITS_PER_SQUARE
/ wallLength
1158 pixelInfo
->texCoords
.y
= RCL_COMPUTE_WALL_TEXCOORDS
?
1159 wallPosition
* coordStep
: 0;
1161 #if RCL_ACCURATE_WALL_TEXTURING == 1
1164 if (RCL_absVal(coordStep
) < RCL_MIN_TEXTURE_STEP
)
1165 /* for the sake of performance there are two-versions of the loop - it's
1166 better to branch early than inside the loop */
1169 for (RCL_Unit i
= yCurrent
+ increment
;
1170 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1173 // more expensive texture coord computing
1174 pixelInfo
->position
.y
= i
;
1176 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1177 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1178 pixelInfo
->texCoords
.y
= (wallPosition
* RCL_UNITS_PER_SQUARE
) / wallLength
;
1180 pixelInfo
->texCoords
.y
= (wallPosition
* height
) / wallLength
;
1185 RCL_PIXEL_FUNCTION(pixelInfo
);
1190 for (RCL_Unit i
= yCurrent
+ increment
;
1191 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1194 // cheaper texture coord computing
1196 pixelInfo
->position
.y
= i
;
1198 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1199 pixelInfo
->texCoords
.y
+= coordStep
;
1202 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
;
1242 // we'll be simulatenously drawing the floor and the ceiling now
1243 for (RCL_Unit j
= 0; j
<= hitCount
; ++j
)
1244 { // ^ = add extra iteration for horizon plane
1245 int8_t drawingHorizon
= j
== hitCount
;
1248 RCL_Unit distance
= 1;
1250 RCL_Unit fWallHeight
= 0, cWallHeight
= 0;
1251 RCL_Unit fZ2World
= 0, cZ2World
= 0;
1252 RCL_Unit fZ1Screen
= 0, cZ1Screen
= 0;
1253 RCL_Unit fZ2Screen
= 0, cZ2Screen
= 0;
1255 if (!drawingHorizon
)
1258 distance
= RCL_nonZero(hit
.distance
);
1261 fWallHeight
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1262 fZ2World
= fWallHeight
- _RCL_camera
.height
;
1263 fZ1Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1264 (fZ1World
* _RCL_camera
.resolution
.y
) /
1265 RCL_UNITS_PER_SQUARE
,distance
);
1266 fZ2Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1267 (fZ2World
* _RCL_camera
.resolution
.y
) /
1268 RCL_UNITS_PER_SQUARE
,distance
);
1270 if (_RCL_ceilFunction
!= 0)
1272 cWallHeight
= _RCL_ceilFunction(hit
.square
.x
,hit
.square
.y
);
1273 cZ2World
= cWallHeight
- _RCL_camera
.height
;
1274 cZ1Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1275 (cZ1World
* _RCL_camera
.resolution
.y
) /
1276 RCL_UNITS_PER_SQUARE
,distance
);
1277 cZ2Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1278 (cZ2World
* _RCL_camera
.resolution
.y
) /
1279 RCL_UNITS_PER_SQUARE
,distance
);
1284 fZ1Screen
= _RCL_middleRow
;
1285 cZ1Screen
= _RCL_middleRow
+ 1;
1286 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1292 p
.isHorizon
= drawingHorizon
;
1294 // draw floor until wall
1296 p
.height
= fZ1World
+ _RCL_camera
.height
;
1298 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1299 p
.depth
= (_RCL_fHorizontalDepthStart
- fPosY
) * _RCL_horizontalDepthStep
;
1304 limit
= _RCL_drawVertical(fPosY
,fZ1Screen
,cPosY
+ 1,
1305 _RCL_camera
.resolution
.y
,fZ1World
,-1,RCL_COMPUTE_FLOOR_DEPTH
,
1306 // ^ purposfully allow outside screen bounds
1307 RCL_COMPUTE_FLOOR_TEXCOORDS
&& p
.height
== RCL_FLOOR_TEXCOORDS_HEIGHT
,
1313 if (_RCL_ceilFunction
!= 0 || drawingHorizon
)
1315 // draw ceiling until wall
1317 p
.height
= cZ1World
+ _RCL_camera
.height
;
1319 #if RCL_COMPUTE_CEILING_DEPTH == 1
1320 p
.depth
= (cPosY
- _RCL_cHorizontalDepthStart
) *
1321 _RCL_horizontalDepthStep
;
1324 limit
= _RCL_drawVertical(cPosY
,cZ1Screen
,
1325 -1,fPosY
- 1,cZ1World
,1,RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1326 // ^ purposfully allow outside screen bounds here
1332 if (!drawingHorizon
) // don't draw walls for horizon plane
1337 p
.texCoords
.x
= hit
.textureCoord
;
1338 p
.height
= 0; // don't compute this, no use
1342 if (fPosY
> 0) // still pixels left?
1346 limit
= _RCL_drawWall(fPosY
,fZ1Screen
,fZ2Screen
,cPosY
+ 1,
1347 _RCL_camera
.resolution
.y
,
1348 // ^ purposfully allow outside screen bounds here
1349 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1350 RCL_UNITS_PER_SQUARE
1360 fZ1World
= fZ2World
; // for the next iteration
1361 } // ^ purposfully allow outside screen bounds here
1363 // draw ceiling wall
1365 if (_RCL_ceilFunction
!= 0 && cPosY
< _RCL_camResYLimit
) // pixels left?
1369 limit
= _RCL_drawWall(cPosY
,cZ1Screen
,cZ2Screen
,
1371 // ^ puposfully allow outside screen bounds here
1372 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1373 RCL_UNITS_PER_SQUARE
1382 cZ1World
= cZ2World
; // for the next iteration
1383 } // ^ puposfully allow outside screen bounds here
1388 void _RCL_columnFunctionSimple(RCL_HitResult
*hits
, uint16_t hitCount
,
1389 uint16_t x
, RCL_Ray ray
)
1392 RCL_Unit wallHeightScreen
= 0;
1393 RCL_Unit wallStart
= _RCL_middleRow
;
1394 RCL_Unit heightOffset
= 0;
1403 RCL_HitResult hit
= hits
[0];
1407 if (_RCL_rollFunction
!= 0 && RCL_COMPUTE_WALL_TEXCOORDS
== 1)
1409 if (hit
.arrayValue
== 0)
1411 // standing inside door square, looking out => move to the next hit
1420 // normal hit, check the door roll
1422 RCL_Unit texCoordMod
= hit
.textureCoord
% RCL_UNITS_PER_SQUARE
;
1424 int8_t unrolled
= hit
.doorRoll
>= 0 ?
1425 (hit
.doorRoll
> texCoordMod
) :
1426 (texCoordMod
> RCL_UNITS_PER_SQUARE
+ hit
.doorRoll
);
1432 if (hitCount
> 1) /* should probably always be true (hit on square
1435 if (hit
.direction
% 2 != hits
[1].direction
% 2)
1437 // hit on the inner side
1441 else if (hitCount
> 2)
1443 // hit on the opposite side
1456 dist
= hit
.distance
;
1458 int16_t wallHeightWorld
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1460 wallHeightScreen
= RCL_perspectiveScale((wallHeightWorld
*
1461 _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
,dist
);
1463 int16_t RCL_normalizedWallHeight
= wallHeightWorld
!= 0 ?
1464 ((RCL_UNITS_PER_SQUARE
* wallHeightScreen
) / wallHeightWorld
) : 0;
1466 heightOffset
= RCL_perspectiveScale(_RCL_cameraHeightScreen
,dist
);
1468 wallStart
= _RCL_middleRow
- wallHeightScreen
+ heightOffset
+
1469 RCL_normalizedWallHeight
;
1474 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1483 p
.height
= RCL_UNITS_PER_SQUARE
;
1485 y
= _RCL_drawVertical(-1,wallStart
,-1,_RCL_middleRow
,_RCL_camera
.height
,1,
1486 RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1495 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1496 p
.hit
.textureCoord
-= p
.hit
.doorRoll
;
1499 p
.texCoords
.x
= p
.hit
.textureCoord
;
1502 RCL_Unit limit
= _RCL_drawWall(y
,wallStart
,wallStart
+ wallHeightScreen
- 1,
1503 -1,_RCL_camResYLimit
,p
.hit
.arrayValue
,1,&p
);
1505 y
= RCL_max(y
,limit
); // take max, in case no wall was drawn
1506 y
= RCL_max(y
,wallStart
);
1512 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1513 p
.depth
= (_RCL_camera
.resolution
.y
- y
) * _RCL_horizontalDepthStep
+ 1;
1516 _RCL_drawVertical(y
,_RCL_camResYLimit
,-1,_RCL_camResYLimit
,
1517 _RCL_camera
.height
,1,RCL_COMPUTE_FLOOR_DEPTH
,RCL_COMPUTE_FLOOR_TEXCOORDS
,
1522 Precomputes a distance from camera to the floor at each screen row into an
1523 array (must be preallocated with sufficient (camera.resolution.y) length).
1525 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera
,
1526 RCL_Unit
*dest
, uint16_t startIndex
)
1528 RCL_Unit camHeightScreenSize
=
1529 (camera
.height
* camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1531 for (uint16_t i
= startIndex
1532 ; i
< camera
.resolution
.y
; ++i
)
1533 dest
[i
] = RCL_perspectiveScaleInverse(camHeightScreenSize
,
1534 RCL_absVal(i
- _RCL_middleRow
));
1537 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1538 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
1539 RCL_RayConstraints constraints
)
1541 _RCL_floorFunction
= floorHeightFunc
;
1542 _RCL_ceilFunction
= ceilingHeightFunc
;
1544 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1546 uint16_t halfResY
= cam
.resolution
.y
/ 2;
1548 _RCL_middleRow
= halfResY
+ cam
.shear
;
1550 _RCL_fHorizontalDepthStart
= _RCL_middleRow
+ halfResY
;
1551 _RCL_cHorizontalDepthStart
= _RCL_middleRow
- halfResY
;
1553 _RCL_startFloorHeight
= floorHeightFunc(
1554 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1555 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
;
1557 _RCL_startCeil_Height
=
1558 ceilingHeightFunc
!= 0 ?
1560 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1561 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
1564 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1566 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1567 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1568 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,0);
1569 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1572 RCL_castRaysMultiHit(cam
,_RCL_floorCeilFunction
,typeFunction
,
1573 _RCL_columnFunctionComplex
,constraints
);
1576 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1577 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
1578 RCL_RayConstraints constraints
)
1580 _RCL_floorFunction
= floorHeightFunc
;
1582 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1583 _RCL_middleRow
= cam
.resolution
.y
/ 2;
1584 _RCL_rollFunction
= rollFunc
;
1586 _RCL_cameraHeightScreen
=
1587 (_RCL_camera
.resolution
.y
* (_RCL_camera
.height
- RCL_UNITS_PER_SQUARE
)) /
1588 RCL_UNITS_PER_SQUARE
;
1590 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1592 constraints
.maxHits
=
1593 _RCL_rollFunction
== 0 ?
1594 1 : // no door => 1 hit is enough
1595 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1597 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1598 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1599 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,_RCL_middleRow
);
1600 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1603 RCL_castRaysMultiHit(cam
,_floorHeightNotZeroFunction
,typeFunc
,
1604 _RCL_columnFunctionSimple
, constraints
);
1606 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1607 _RCL_floorPixelDistances
= 0;
1611 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
)
1613 RCL_profileCall(RCL_normalize
);
1615 RCL_Vector2D result
;
1616 RCL_Unit l
= RCL_len(v
);
1619 result
.x
= (v
.x
* RCL_UNITS_PER_SQUARE
) / l
;
1620 result
.y
= (v
.y
* RCL_UNITS_PER_SQUARE
) / l
;
1625 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
)
1627 RCL_profileCall(RCL_vectorsAngleCos
);
1629 v1
= RCL_normalize(v1
);
1630 v2
= RCL_normalize(v2
);
1632 return (v1
.x
* v2
.x
+ v1
.y
* v2
.y
) / RCL_UNITS_PER_SQUARE
;
1635 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
1638 RCL_PixelInfo result
;
1640 RCL_Unit d
= RCL_dist(worldPosition
,camera
.position
);
1642 RCL_Vector2D toPoint
;
1644 toPoint
.x
= worldPosition
.x
- camera
.position
.x
;
1645 toPoint
.y
= worldPosition
.y
- camera
.position
.y
;
1647 RCL_Vector2D cameraDir
= RCL_angleToDirection(camera
.direction
);
1649 result
.depth
= // adjusted distance
1650 (d
* RCL_vectorsAngleCos(cameraDir
,toPoint
)) / RCL_UNITS_PER_SQUARE
;
1652 result
.position
.y
= camera
.resolution
.y
/ 2 -
1653 (camera
.resolution
.y
*
1654 RCL_perspectiveScale(height
- camera
.height
,result
.depth
)) / RCL_UNITS_PER_SQUARE
1657 RCL_Unit middleColumn
= camera
.resolution
.x
/ 2;
1659 RCL_Unit a
= RCL_sqrtInt(d
* d
- result
.depth
* result
.depth
);
1662 r
.start
= camera
.position
;
1663 r
.direction
= cameraDir
;
1665 if (!RCL_pointIfLeftOfRay(worldPosition
,r
))
1668 RCL_Unit cos
= RCL_cosInt(RCL_HORIZONTAL_FOV_HALF
);
1670 RCL_Unit b
= (result
.depth
* RCL_sinInt(RCL_HORIZONTAL_FOV_HALF
)) /
1674 result
.position
.x
= (a
* middleColumn
) / RCL_nonZero(b
);
1675 result
.position
.x
= middleColumn
- result
.position
.x
;
1680 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
)
1682 return (degrees
* RCL_UNITS_PER_SQUARE
) / 360;
1685 RCL_Unit
RCL_perspectiveScale(RCL_Unit originalSize
, RCL_Unit distance
)
1687 RCL_profileCall(RCL_perspectiveScale
);
1689 return distance
!= 0 ?
1690 (originalSize
* RCL_UNITS_PER_SQUARE
) /
1691 ((RCL_VERTICAL_FOV
* 2 * distance
) / RCL_UNITS_PER_SQUARE
)
1695 RCL_Unit
RCL_perspectiveScaleInverse(RCL_Unit originalSize
,
1696 RCL_Unit scaledSize
)
1698 return scaledSize
!= 0 ?
1699 (originalSize
* RCL_UNITS_PER_SQUARE
+ RCL_UNITS_PER_SQUARE
/ 2) /
1700 // ^ take the middle
1701 ((RCL_VERTICAL_FOV
* 2 * scaledSize
) / RCL_UNITS_PER_SQUARE
)
1705 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
1706 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
1707 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
)
1709 int8_t movesInPlane
= planeOffset
.x
!= 0 || planeOffset
.y
!= 0;
1710 int16_t xSquareNew
, ySquareNew
;
1712 if (movesInPlane
|| force
)
1714 RCL_Vector2D corner
; // BBox corner in the movement direction
1715 RCL_Vector2D cornerNew
;
1717 int16_t xDir
= planeOffset
.x
> 0 ? 1 : -1;
1718 int16_t yDir
= planeOffset
.y
> 0 ? 1 : -1;
1720 corner
.x
= camera
->position
.x
+ xDir
* RCL_CAMERA_COLL_RADIUS
;
1721 corner
.y
= camera
->position
.y
+ yDir
* RCL_CAMERA_COLL_RADIUS
;
1723 int16_t xSquare
= RCL_divRoundDown(corner
.x
,RCL_UNITS_PER_SQUARE
);
1724 int16_t ySquare
= RCL_divRoundDown(corner
.y
,RCL_UNITS_PER_SQUARE
);
1726 cornerNew
.x
= corner
.x
+ planeOffset
.x
;
1727 cornerNew
.y
= corner
.y
+ planeOffset
.y
;
1729 xSquareNew
= RCL_divRoundDown(cornerNew
.x
,RCL_UNITS_PER_SQUARE
);
1730 ySquareNew
= RCL_divRoundDown(cornerNew
.y
,RCL_UNITS_PER_SQUARE
);
1732 RCL_Unit bottomLimit
= -1 * RCL_INFINITY
;
1733 RCL_Unit topLimit
= RCL_INFINITY
;
1737 bottomLimit
= camera
->height
- RCL_CAMERA_COLL_HEIGHT_BELOW
+
1738 RCL_CAMERA_COLL_STEP_HEIGHT
;
1740 topLimit
= camera
->height
+ RCL_CAMERA_COLL_HEIGHT_ABOVE
;
1743 // checks a single square for collision against the camera
1744 #define collCheck(dir,s1,s2)\
1747 RCL_Unit height = floorHeightFunc(s1,s2);\
1748 if (height > bottomLimit)\
1750 else if (ceilingHeightFunc != 0)\
1752 height = ceilingHeightFunc(s1,s2);\
1753 if (height < topLimit)\
1758 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1760 // check a collision against non-diagonal square
1761 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1762 if (dir##SquareNew != dir##Square)\
1764 collCheck(dir,s1,s2)\
1766 if (!dir##Collides)\
1767 { /* now also check for coll on the neighbouring square */ \
1768 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1769 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1770 if (dir2##Square2 != dir2##Square)\
1773 collCheck(dir,dir##SquareNew,dir2##Square2)\
1775 collCheck(dir,dir2##Square2,dir##SquareNew)\
1779 int8_t xCollides
= 0;
1780 collCheckOrtho(x
,y
,xSquareNew
,ySquare
,1)
1782 int8_t yCollides
= 0;
1783 collCheckOrtho(y
,x
,xSquare
,ySquareNew
,0)
1785 #define collHandle(dir)\
1787 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1788 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1791 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1792 collision can't happen */
1794 if (xSquare
!= xSquareNew
&& ySquare
!= ySquareNew
) // corner?
1796 int8_t xyCollides
= 0;
1797 collCheck(xy
,xSquareNew
,ySquareNew
)
1801 // normally should slide, but let's KISS
1813 camera
->position
.x
= cornerNew
.x
- xDir
* RCL_CAMERA_COLL_RADIUS
;
1814 camera
->position
.y
= cornerNew
.y
- yDir
* RCL_CAMERA_COLL_RADIUS
;
1817 if (computeHeight
&& (movesInPlane
|| heightOffset
!= 0 || force
))
1819 camera
->height
+= heightOffset
;
1821 int16_t xSquare1
= RCL_divRoundDown(camera
->position
.x
-
1822 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1824 int16_t xSquare2
= RCL_divRoundDown(camera
->position
.x
+
1825 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1827 int16_t ySquare1
= RCL_divRoundDown(camera
->position
.y
-
1828 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1830 int16_t ySquare2
= RCL_divRoundDown(camera
->position
.y
+
1831 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1833 RCL_Unit bottomLimit
= floorHeightFunc(xSquare1
,ySquare1
);
1834 RCL_Unit topLimit
= ceilingHeightFunc
!= 0 ?
1835 ceilingHeightFunc(xSquare1
,ySquare1
) : RCL_INFINITY
;
1839 #define checkSquares(s1,s2)\
1841 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1842 bottomLimit = RCL_max(bottomLimit,height);\
1843 height = ceilingHeightFunc != 0 ?\
1844 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
1845 topLimit = RCL_min(topLimit,height);\
1848 if (xSquare2
!= xSquare1
)
1851 if (ySquare2
!= ySquare1
)
1854 if (xSquare2
!= xSquare1
&& ySquare2
!= ySquare1
)
1857 camera
->height
= RCL_clamp(camera
->height
,
1858 bottomLimit
+ RCL_CAMERA_COLL_HEIGHT_BELOW
,
1859 topLimit
- RCL_CAMERA_COLL_HEIGHT_ABOVE
);
1865 void RCL_initCamera(RCL_Camera
*camera
)
1867 camera
->position
.x
= 0;
1868 camera
->position
.y
= 0;
1869 camera
->direction
= 0;
1870 camera
->resolution
.x
= 20;
1871 camera
->resolution
.y
= 15;
1873 camera
->height
= RCL_UNITS_PER_SQUARE
;
1876 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
)
1878 constraints
->maxHits
= 1;
1879 constraints
->maxSteps
= 20;