7 raycastlib (RCL) - Small C header-only raycasting library for embedded and
8 low performance computers, such as Arduino. Only uses integer math and stdint
11 Check the defines below to fine-tune accuracy vs performance! Don't forget
12 to compile with optimizations.
14 Before including the library define RCL_PIXEL_FUNCTION to the name of the
15 function (with RCL_PixelFunction signature) that will render your pixels!
17 - All public (and most private) library identifiers start with RCL_.
18 - Game field's bottom left corner is at [0,0].
19 - X axis goes right in the ground plane.
20 - Y axis goes up in the ground plane.
21 - Height means the Z (vertical) coordinate.
22 - Each game square is RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE points.
23 - Angles are in RCL_Units, 0 means pointing right (x+) and positively rotates
24 clockwise. A full angle has RCL_UNITS_PER_SQUARE RCL_Units.
25 - Most things are normalized with RCL_UNITS_PER_SQUARE (sin, cos, vector
26 unit length, texture coordinates etc.).
27 - Screen coordinates are normal: [0,0] = top left, x goes right, y goes down.
29 author: Miloslav "drummyfish" Ciz
30 license: CC0 1.0 + all IP waiver
35 This work's goal is to never be encumbered by any exclusive intellectual
38 The work is therefore provided under CC0 1.0 + additional WAIVER OF ALL
39 INTELLECTUAL PROPERTY RIGHTS that waives the rest of intellectual property
40 rights not already waived by CC0 1.0. The WAIVER OF ALL INTELLECTUAL PROPERTY
43 Each contributor to this work agrees that they waive any exclusive rights,
44 including but not limited to copyright, patents, trademark, trade dress,
45 industrial design, plant varieties and trade secrets, to any and all ideas,
46 concepts, processes, discoveries, improvements and inventions conceived,
47 discovered, made, designed, researched or developed by the contributor either
48 solely or jointly with others, which relate to this work or result from this
49 work. Should any waiver of such right be judged legally invalid or ineffective
50 under applicable law, the contributor hereby grants to each affected person a
51 royalty-free, non transferable, non sublicensable, non exclusive, irrevocable
52 and unconditional license to this right.
57 #ifndef RCL_RAYCAST_TINY
58 #define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a
60 typedef int32_t RCL_Unit
; /**< Smallest spatial unit, there is
61 RCL_UNITS_PER_SQUARE units in a square's
62 length. This effectively serves the purpose of
63 a fixed-point arithmetic. */
64 #define RCL_INFINITY 2000000000
66 #define RCL_UNITS_PER_SQUARE 32
67 typedef int16_t RCL_Unit
;
68 #define RCL_INFINITY 30000
69 #define RCL_USE_DIST_APPROX 2
72 #ifndef RCL_COMPUTE_WALL_TEXCOORDS
73 #define RCL_COMPUTE_WALL_TEXCOORDS 1
76 #ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
77 #define RCL_COMPUTE_FLOOR_TEXCOORDS 0
80 #ifndef RCL_FLOOR_TEXCOORDS_HEIGHT
81 #define RCL_FLOOR_TEXCOORDS_HEIGHT 0 /** If RCL_COMPUTE_FLOOR_TEXCOORDS == 1,
82 this says for what height level the
83 texture coords will be computed for
84 (for simplicity/performance only one
88 #ifndef RCL_USE_COS_LUT
89 #define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
95 #ifndef RCL_USE_DIST_APPROX
96 #define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use:
97 0: none (compute full Euclidean distance)
98 1: accurate approximation
99 2: octagonal approximation (LQ) */
102 #ifndef RCL_TEXTURE_VERTICAL_STRETCH
103 #define RCL_TEXTURE_VERTICAL_STRETCH 1 /**< Whether textures should be
104 stretched to wall height (possibly
105 slightly slower if on). */
108 #ifndef RCL_ACCURATE_WALL_TEXTURING
109 #define RCL_ACCURATE_WALL_TEXTURING 0 /**< If turned on, vertical wall texture
110 coordinates will always be calculated
111 with more precise (but slower) method,
112 otherwise RCL_MIN_TEXTURE_STEP will be
113 used to decide the method. */
116 #ifndef RCL_COMPUTE_FLOOR_DEPTH
117 #define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for
118 floor pixels - turns this off if not
122 #ifndef RCL_COMPUTE_CEILING_DEPTH
123 #define RCL_COMPUTE_CEILING_DEPTH 1 /**< AS RCL_COMPUTE_FLOOR_DEPTH but for
127 #ifndef RCL_ROLL_TEXTURE_COORDS
128 #define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
129 roll the texture coordinates along (mostly
130 desired for doors). */
133 #ifndef RCL_VERTICAL_FOV
134 #define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 2)
137 #ifndef RCL_HORIZONTAL_FOV
138 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
141 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
143 #ifndef RCL_CAMERA_COLL_RADIUS
144 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
147 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
148 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
151 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
152 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
155 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
156 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
159 #ifndef RCL_MIN_TEXTURE_STEP
160 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
161 #define RCL_MIN_TEXTURE_STEP 12 /**< Specifies the minimum step in pixels
162 that can be used to compute texture
163 coordinates in a fast way. Smallet step
164 should be faster (but less accurate). */
166 #define RCL_MIN_TEXTURE_STEP 24
170 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
171 horizon has (the floor
173 approximated with the
176 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
177 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
178 difference when approximating floor/ceil
182 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
183 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
184 #define RCL_nonZero(v) ((v) != 0 ? (v) : 1) ///< To prevent zero divisions.
186 #define RCL_logV2D(v)\
187 printf("[%d,%d]\n",v.x,v.y);
189 #define RCL_logRay(r){\
192 RCL_logV2D(r.start);\
194 RCL_logV2D(r.direction);}
196 #define RCL_logHitResult(h){\
198 printf(" square: ");\
199 RCL_logV2D(h.square);\
201 RCL_logV2D(h.position);\
202 printf(" dist: %d\n", h.distance);\
203 printf(" dir: %d\n", h.direction);\
204 printf(" texcoord: %d\n", h.textureCoord);}
206 #define RCL_logPixelInfo(p){\
208 printf(" position: ");\
209 RCL_logV2D(p.position);\
210 printf(" texCoord: ");\
211 RCL_logV2D(p.texCoords);\
212 printf(" depth: %d\n", p.depth);\
213 printf(" height: %d\n", p.height);\
214 printf(" wall: %d\n", p.isWall);\
216 RCL_logHitResult(p.hit);\
219 #define RCL_logCamera(c){\
220 printf("camera:\n");\
221 printf(" position: ");\
222 RCL_logV2D(c.position);\
223 printf(" height: %d\n",c.height);\
224 printf(" direction: %d\n",c.direction);\
225 printf(" shear: %d\n",c.shear);\
226 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
229 /// Position in 2D space.
239 RCL_Vector2D direction
;
244 RCL_Unit distance
; /**< Distance (perpend.) to the hit position, or -1
245 if no collision happened. */
246 uint8_t direction
; ///< Direction of hit.
247 RCL_Unit textureCoord
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
248 texture coordinate (horizontal). */
249 RCL_Vector2D square
; ///< Collided square coordinates.
250 RCL_Vector2D position
; ///< Exact collision position in RCL_Units.
251 RCL_Unit arrayValue
; /** Value returned by array function (most often
252 this will be the floor height). */
253 RCL_Unit type
; /**< Integer identifying type of square (number
254 returned by type function, e.g. texture
256 RCL_Unit doorRoll
; ///< Holds value of door roll.
261 RCL_Vector2D position
;
263 RCL_Vector2D resolution
;
264 int16_t shear
; /**< Shear offset in pixels (0 => no shear), can simulate
270 Holds an information about a single rendered pixel (for a pixel function
271 that works as a fragment shader).
275 RCL_Vector2D position
; ///< On-screen position.
276 int8_t isWall
; ///< Whether the pixel is a wall or a floor/ceiling.
277 int8_t isFloor
; ///< Whether the pixel is floor or ceiling.
278 int8_t isHorizon
; ///< If the pixel belongs to horizon segment.
279 RCL_Unit depth
; ///< Corrected depth.
280 RCL_Unit height
; ///< World height (mostly for floor).
281 RCL_HitResult hit
; ///< Corresponding ray hit.
282 RCL_Vector2D texCoords
; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
283 texture coordinates. */
286 void RCL_PIXEL_FUNCTION (RCL_PixelInfo
*pixel
);
292 } RCL_RayConstraints
;
295 Function used to retrieve some information about cells of the rendered scene.
296 It should return a characteristic of given square as an integer (e.g. square
297 height, texture index, ...) - between squares that return different numbers
298 there is considered to be a collision.
300 This function should be as fast as possible as it will typically be called
303 typedef RCL_Unit (*RCL_ArrayFunction
)(int16_t x
, int16_t y
);
306 Function that renders a single pixel at the display. It is handed an info
307 about the pixel it should draw.
309 This function should be as fast as possible as it will typically be called
312 typedef void (*RCL_PixelFunction
)(RCL_PixelInfo
*info
);
315 (*RCL_ColumnFunction
)(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
319 Simple-interface function to cast a single ray.
320 @return The first collision result.
322 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
);
325 Maps a single point in the world to the screen (2D position + depth).
327 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
331 Casts a single ray and returns a list of collisions.
333 @param ray ray to be cast
334 @param arrayFunc function that will be used to determine collisions (hits)
335 with the ray (squares for which this function returns different values
336 are considered to have a collision between them), this will typically
337 be a function returning floor height
338 @param typeFunc optional (can be 0) function - if provided, it will be used
339 to mark the hit result with the number returned by this function
340 (it can be e.g. a texture index)
341 @param hitResults array in which the hit results will be stored (has to be
342 preallocated with at space for at least as many hit results as
343 maxHits specified with the constraints parameter)
344 @param hitResultsLen in this variable the number of hit results will be
346 @param constraints specifies constraints for the ray cast
348 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
349 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
350 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
);
352 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
);
357 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
358 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
359 RCL_UNITS_PER_SQUARE)
361 RCL_Unit
RCL_cosInt(RCL_Unit input
);
363 RCL_Unit
RCL_sinInt(RCL_Unit input
);
365 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
366 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
);
368 /// Computes a cos of an angle between two vectors.
369 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
);
371 uint16_t RCL_sqrtInt(RCL_Unit value
);
372 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
);
373 RCL_Unit
RCL_len(RCL_Vector2D v
);
376 Converts an angle in whole degrees to an angle in RCL_Units that this library
379 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
);
381 /** Computes the change in size of an object due to perspective. */
382 RCL_Unit
RCL_perspectiveScale(RCL_Unit originalSize
, RCL_Unit distance
);
384 RCL_Unit
RCL_perspectiveScaleInverse(RCL_Unit originalSize
,
385 RCL_Unit scaledSize
);
388 Casts rays for given camera view and for each hit calls a user provided
391 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
392 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
393 RCL_RayConstraints constraints
);
396 Using provided functions, renders a complete complex (multilevel) camera
399 This function should render each screen pixel exactly once.
401 function rendering summary:
402 - performance: slower
405 - different wall heights: yes
406 - floor/ceiling textures: no
407 - floor geometry: yes, multilevel
408 - ceiling geometry: yes (optional), multilevel
410 - camera shearing: yes
411 - rendering order: left-to-right, not specifically ordered vertically
413 @param cam camera whose view to render
414 @param floorHeightFunc function that returns floor height (in RCL_Units)
415 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
416 0 (no ceiling will be rendered)
417 @param typeFunction function that says a type of square (e.g. its texture
418 index), can be 0 (no type in hit result)
419 @param pixelFunc callback function to draw a single pixel on screen
420 @param constraints constraints for each cast ray
422 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
423 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
424 RCL_RayConstraints constraints
);
427 Renders given camera view, with help of provided functions. This function is
428 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
431 function rendering summary:
432 - performance: faster
435 - different wall heights: yes
436 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
437 - floor geometry: no (just flat floor, with depth information)
438 - ceiling geometry: no (just flat ceiling, with depth information)
440 - camera shearing: no
441 - rendering order: left-to-right, top-to-bottom
443 Additionally this function supports rendering rolling doors.
445 This function should render each screen pixel exactly once.
447 @param rollFunc function that for given square says its door roll in
448 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
449 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
450 rendering should also be faster as fewer intersections will be tested)
452 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
453 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
454 RCL_RayConstraints constraints
);
457 Function that moves given camera and makes it collide with walls and
458 potentially also floor and ceilings. It's meant to help implement player
461 @param camera camera to move
462 @param planeOffset offset to move the camera in
463 @param heightOffset height offset to move the camera in
464 @param floorHeightFunc function used to retrieve the floor height
465 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
466 (camera won't collide with ceiling)
467 @param computeHeight whether to compute height - if false (0), floor and
468 ceiling functions won't be used and the camera will
469 only collide horizontally with walls (good for simpler
471 @param force if true, forces to recompute collision even if position doesn't
474 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
475 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
476 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
);
478 void RCL_initCamera(RCL_Camera
*camera
);
479 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
);
481 //=============================================================================
484 // global helper variables, for precomputing stuff etc.
485 RCL_Camera _RCL_camera
;
486 RCL_Unit _RCL_horizontalDepthStep
= 0;
487 RCL_Unit _RCL_startFloorHeight
= 0;
488 RCL_Unit _RCL_startCeil_Height
= 0;
489 RCL_Unit _RCL_camResYLimit
= 0;
490 RCL_Unit _RCL_middleRow
= 0;
491 RCL_ArrayFunction _RCL_floorFunction
= 0;
492 RCL_ArrayFunction _RCL_ceilFunction
= 0;
493 RCL_Unit _RCL_fHorizontalDepthStart
= 0;
494 RCL_Unit _RCL_cHorizontalDepthStart
= 0;
495 int16_t _RCL_cameraHeightScreen
= 0;
496 RCL_ArrayFunction _RCL_rollFunction
= 0; // says door rolling
497 RCL_Unit
*_RCL_floorPixelDistances
= 0;
500 // function call counters for profiling
501 uint32_t profile_RCL_sqrtInt
= 0;
502 uint32_t profile_RCL_clamp
= 0;
503 uint32_t profile_RCL_cosInt
= 0;
504 uint32_t profile_RCL_angleToDirection
= 0;
505 uint32_t profile_RCL_dist
= 0;
506 uint32_t profile_RCL_len
= 0;
507 uint32_t profile_RCL_pointIfLeftOfRay
= 0;
508 uint32_t profile_RCL_castRayMultiHit
= 0;
509 uint32_t profile_RCL_castRay
= 0;
510 uint32_t profile_RCL_absVal
= 0;
511 uint32_t profile_RCL_normalize
= 0;
512 uint32_t profile_RCL_vectorsAngleCos
= 0;
513 uint32_t profile_RCL_perspectiveScale
= 0;
514 uint32_t profile_RCL_wrap
= 0;
515 uint32_t profile_RCL_divRoundDown
= 0;
516 #define RCL_profileCall(c) profile_##c += 1
518 #define printProfile() {\
519 printf("profile:\n");\
520 printf(" RCL_sqrtInt: %d\n",profile_RCL_sqrtInt);\
521 printf(" RCL_clamp: %d\n",profile_RCL_clamp);\
522 printf(" RCL_cosInt: %d\n",profile_RCL_cosInt);\
523 printf(" RCL_angleToDirection: %d\n",profile_RCL_angleToDirection);\
524 printf(" RCL_dist: %d\n",profile_RCL_dist);\
525 printf(" RCL_len: %d\n",profile_RCL_len);\
526 printf(" RCL_pointIfLeftOfRay: %d\n",profile_RCL_pointIfLeftOfRay);\
527 printf(" RCL_castRayMultiHit : %d\n",profile_RCL_castRayMultiHit);\
528 printf(" RCL_castRay: %d\n",profile_RCL_castRay);\
529 printf(" RCL_normalize: %d\n",profile_RCL_normalize);\
530 printf(" RCL_vectorsAngleCos: %d\n",profile_RCL_vectorsAngleCos);\
531 printf(" RCL_absVal: %d\n",profile_RCL_absVal);\
532 printf(" RCL_perspectiveScale: %d\n",profile_RCL_perspectiveScale);\
533 printf(" RCL_wrap: %d\n",profile_RCL_wrap);\
534 printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); }
536 #define RCL_profileCall(c)
539 RCL_Unit
RCL_clamp(RCL_Unit value
, RCL_Unit valueMin
, RCL_Unit valueMax
)
541 RCL_profileCall(RCL_clamp
);
543 if (value
>= valueMin
)
545 if (value
<= valueMax
)
554 static inline RCL_Unit
RCL_absVal(RCL_Unit value
)
556 RCL_profileCall(RCL_absVal
);
558 return value
>= 0 ? value
: -1 * value
;
561 /// Like mod, but behaves differently for negative values.
562 static inline RCL_Unit
RCL_wrap(RCL_Unit value
, RCL_Unit mod
)
564 RCL_profileCall(RCL_wrap
);
566 return value
>= 0 ? (value
% mod
) : (mod
+ (value
% mod
) - 1);
569 /// Performs division, rounding down, NOT towards zero.
570 static inline RCL_Unit
RCL_divRoundDown(RCL_Unit value
, RCL_Unit divisor
)
572 RCL_profileCall(RCL_divRoundDown
);
574 return value
/ divisor
- ((value
>= 0) ? 0 : 1);
577 // Bhaskara's cosine approximation formula
578 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
579 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
580 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
582 #if RCL_USE_COS_LUT == 1
584 #ifdef RCL_RAYCAST_TINY
585 const RCL_Unit cosLUT
[64] =
587 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
590 const RCL_Unit cosLUT
[64] =
592 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
593 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
594 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
595 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
599 #elif RCL_USE_COS_LUT == 2
600 const RCL_Unit cosLUT
[128] =
602 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
603 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
604 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
605 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
606 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
607 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
608 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
609 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
613 RCL_Unit
RCL_cosInt(RCL_Unit input
)
615 RCL_profileCall(RCL_cosInt
);
617 input
= RCL_wrap(input
,RCL_UNITS_PER_SQUARE
);
619 #if RCL_USE_COS_LUT == 1
621 #ifdef RCL_RAYCAST_TINY
622 return cosLUT
[input
];
624 return cosLUT
[input
/ 16];
627 #elif RCL_USE_COS_LUT == 2
628 return cosLUT
[input
/ 8];
630 if (input
< RCL_UNITS_PER_SQUARE
/ 4)
631 return trigHelper(input
);
632 else if (input
< RCL_UNITS_PER_SQUARE
/ 2)
633 return -1 * trigHelper(RCL_UNITS_PER_SQUARE
/ 2 - input
);
634 else if (input
< 3 * RCL_UNITS_PER_SQUARE
/ 4)
635 return -1 * trigHelper(input
- RCL_UNITS_PER_SQUARE
/ 2);
637 return trigHelper(RCL_UNITS_PER_SQUARE
- input
);
643 RCL_Unit
RCL_sinInt(RCL_Unit input
)
645 return RCL_cosInt(input
- RCL_UNITS_PER_SQUARE
/ 4);
648 RCL_Vector2D
RCL_angleToDirection(RCL_Unit angle
)
650 RCL_profileCall(RCL_angleToDirection
);
654 result
.x
= RCL_cosInt(angle
);
655 result
.y
= -1 * RCL_sinInt(angle
);
660 uint16_t RCL_sqrtInt(RCL_Unit value
)
662 RCL_profileCall(RCL_sqrtInt
);
664 #ifdef RCL_RAYCAST_TINY
667 uint16_t b
= 1u << 14;
671 uint32_t b
= 1u << 30;
682 result
= result
+ 2 * b
;
692 RCL_Unit
RCL_dist(RCL_Vector2D p1
, RCL_Vector2D p2
)
694 RCL_profileCall(RCL_dist
);
696 RCL_Unit dx
= p2
.x
- p1
.x
;
697 RCL_Unit dy
= p2
.y
- p1
.y
;
699 #if RCL_USE_DIST_APPROX == 2
700 // octagonal approximation
705 return dy
> dx
? dx
/ 2 + dy
: dy
/ 2 + dx
;
706 #elif RCL_USE_DIST_APPROX == 1
707 // more accurate approximation
709 RCL_Unit a
, b
, result
;
711 dx
= dx
< 0 ? -1 * dx
: dx
;
712 dy
= dy
< 0 ? -1 * dy
: dy
;
725 result
= a
+ (44 * b
) / 102;
728 result
-= (5 * a
) / 128;
735 return RCL_sqrtInt((RCL_Unit
) (dx
+ dy
));
739 RCL_Unit
RCL_len(RCL_Vector2D v
)
741 RCL_profileCall(RCL_len
);
747 return RCL_dist(zero
,v
);
750 static inline int8_t RCL_pointIfLeftOfRay(RCL_Vector2D point
, RCL_Ray ray
)
752 RCL_profileCall(RCL_pointIfLeftOfRay
);
754 RCL_Unit dX
= point
.x
- ray
.start
.x
;
755 RCL_Unit dY
= point
.y
- ray
.start
.y
;
756 return (ray
.direction
.x
* dY
- ray
.direction
.y
* dX
) > 0;
757 // ^ Z component of cross-product
761 DEPRECATED in favor of DDA.
762 Casts a ray within a single square, to collide with the square borders.
764 void RCL_castRaySquare(RCL_Ray
*localRay
, RCL_Vector2D
*nextCellOff
,
765 RCL_Vector2D
*collOff
)
770 RCL_Ray criticalLine
;
771 criticalLine
.start
= localRay
->start
;
772 criticalLine
.direction
= localRay
->direction
;
774 #define helper(c1,c2,n)\
776 nextCellOff->c1 = n;\
777 collOff->c1 = criticalLine.start.c1 - localRay->start.c1;\
779 (((RCL_Unit) collOff->c1) * localRay->direction.c2) /\
780 ((localRay->direction.c1 == 0) ? 1 : localRay->direction.c1);\
783 #define helper2(n1,n2,c)\
784 if (RCL_pointIfLeftOfRay(localRay->start,criticalLine) == c)\
789 if (localRay
->direction
.x
> 0)
791 criticalLine
.start
.x
= RCL_UNITS_PER_SQUARE
- 1;
793 if (localRay
->direction
.y
> 0)
796 criticalLine
.start
.y
= RCL_UNITS_PER_SQUARE
- 1;
802 criticalLine
.start
.y
= 0;
808 criticalLine
.start
.x
= 0;
810 if (localRay
->direction
.y
> 0)
813 criticalLine
.start
.y
= RCL_UNITS_PER_SQUARE
- 1;
819 criticalLine
.start
.y
= 0;
827 collOff
->x
+= nextCellOff
->x
;
828 collOff
->y
+= nextCellOff
->y
;
831 void RCL_castRayMultiHit(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
,
832 RCL_ArrayFunction typeFunc
, RCL_HitResult
*hitResults
,
833 uint16_t *hitResultsLen
, RCL_RayConstraints constraints
)
835 RCL_profileCall(RCL_castRayMultiHit
);
837 RCL_Vector2D currentPos
= ray
.start
;
838 RCL_Vector2D currentSquare
;
840 currentSquare
.x
= RCL_divRoundDown(ray
.start
.x
,RCL_UNITS_PER_SQUARE
);
841 currentSquare
.y
= RCL_divRoundDown(ray
.start
.y
,RCL_UNITS_PER_SQUARE
);
845 RCL_Unit squareType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
848 RCL_Vector2D nextSideDist
; // dist. from start to the next side in given axis
850 RCL_Unit currentDist
= 0;
851 RCL_Vector2D step
; // -1 or 1 for each axis
852 int8_t stepHorizontal
= 0; // whether the last step was hor. or vert.
857 RCL_Unit dirVecLength
= RCL_len(ray
.direction
);
859 delta
.x
= RCL_absVal((RCL_UNITS_PER_SQUARE
* dirVecLength
) /
860 RCL_nonZero(ray
.direction
.x
));
862 delta
.y
= RCL_absVal((RCL_UNITS_PER_SQUARE
* dirVecLength
) /
863 RCL_nonZero(ray
.direction
.y
));
867 if (ray
.direction
.x
< 0)
870 nextSideDist
.x
= (RCL_wrap(ray
.start
.x
,RCL_UNITS_PER_SQUARE
) * delta
.x
) /
871 RCL_UNITS_PER_SQUARE
;
877 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.x
,RCL_UNITS_PER_SQUARE
)) *
878 delta
.x
) / RCL_UNITS_PER_SQUARE
;
881 if (ray
.direction
.y
< 0)
884 nextSideDist
.y
= (RCL_wrap(ray
.start
.y
,RCL_UNITS_PER_SQUARE
) * delta
.y
) /
885 RCL_UNITS_PER_SQUARE
;
891 ((RCL_wrap(RCL_UNITS_PER_SQUARE
- ray
.start
.y
,RCL_UNITS_PER_SQUARE
)) *
892 delta
.y
) / RCL_UNITS_PER_SQUARE
;
897 for (uint16_t i
= 0; i
< constraints
.maxSteps
; ++i
)
899 RCL_Unit currentType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
901 if (currentType
!= squareType
)
907 h
.arrayValue
= currentType
;
909 h
.position
= currentPos
;
910 h
.square
= currentSquare
;
911 h
.distance
= currentDist
;
915 h
.position
.x
= currentSquare
.x
* RCL_UNITS_PER_SQUARE
;
921 h
.position
.x
+= RCL_UNITS_PER_SQUARE
;
924 RCL_Unit diff
= h
.position
.x
- ray
.start
.x
;
925 h
.position
.y
= ray
.start
.y
+ ((ray
.direction
.y
* diff
) /
926 RCL_nonZero(ray
.direction
.x
));
929 ((h
.position
.x
- ray
.start
.x
) * RCL_UNITS_PER_SQUARE
) /
930 RCL_nonZero(ray
.direction
.x
);
934 h
.position
.y
= currentSquare
.y
* RCL_UNITS_PER_SQUARE
;
940 h
.position
.y
+= RCL_UNITS_PER_SQUARE
;
943 RCL_Unit diff
= h
.position
.y
- ray
.start
.y
;
944 h
.position
.x
= ray
.start
.x
+ ((ray
.direction
.x
* diff
) /
945 RCL_nonZero(ray
.direction
.y
));
948 ((h
.position
.y
- ray
.start
.y
) * RCL_UNITS_PER_SQUARE
) /
949 RCL_nonZero(ray
.direction
.y
);
953 h
.type
= typeFunc(currentSquare
.x
,currentSquare
.y
);
955 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
958 case 0: h
.textureCoord
=
959 RCL_wrap(-1 * h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
961 case 1: h
.textureCoord
=
962 RCL_wrap(h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
964 case 2: h
.textureCoord
=
965 RCL_wrap(h
.position
.x
,RCL_UNITS_PER_SQUARE
); break;
967 case 3: h
.textureCoord
=
968 RCL_wrap(-1 * h
.position
.y
,RCL_UNITS_PER_SQUARE
); break;
970 default: h
.textureCoord
= 0; break;
973 if (_RCL_rollFunction
!= 0)
974 h
.doorRoll
= _RCL_rollFunction(currentSquare
.x
,currentSquare
.y
);
979 hitResults
[*hitResultsLen
] = h
;
983 squareType
= currentType
;
985 if (*hitResultsLen
>= constraints
.maxHits
)
991 if (nextSideDist
.x
< nextSideDist
.y
)
993 currentDist
= nextSideDist
.x
;
994 nextSideDist
.x
+= delta
.x
;
995 currentSquare
.x
+= step
.x
;
1000 currentDist
= nextSideDist
.y
;
1001 nextSideDist
.y
+= delta
.y
;
1002 currentSquare
.y
+= step
.y
;
1008 RCL_HitResult
RCL_castRay(RCL_Ray ray
, RCL_ArrayFunction arrayFunc
)
1010 RCL_profileCall(RCL_castRay
);
1012 RCL_HitResult result
;
1014 RCL_RayConstraints c
;
1019 RCL_castRayMultiHit(ray
,arrayFunc
,0,&result
,&RCL_len
,c
);
1022 result
.distance
= -1;
1027 void RCL_castRaysMultiHit(RCL_Camera cam
, RCL_ArrayFunction arrayFunc
,
1028 RCL_ArrayFunction typeFunction
, RCL_ColumnFunction columnFunc
,
1029 RCL_RayConstraints constraints
)
1032 RCL_angleToDirection(cam
.direction
- RCL_HORIZONTAL_FOV_HALF
);
1035 RCL_angleToDirection(cam
.direction
+ RCL_HORIZONTAL_FOV_HALF
);
1037 RCL_Unit dX
= dir2
.x
- dir1
.x
;
1038 RCL_Unit dY
= dir2
.y
- dir1
.y
;
1040 RCL_HitResult hits
[constraints
.maxHits
];
1044 r
.start
= cam
.position
;
1046 RCL_Unit currentDX
= 0;
1047 RCL_Unit currentDY
= 0;
1049 for (int16_t i
= 0; i
< cam
.resolution
.x
; ++i
)
1051 r
.direction
.x
= dir1
.x
+ currentDX
/ cam
.resolution
.x
;
1052 r
.direction
.y
= dir1
.y
+ currentDY
/ cam
.resolution
.x
;
1054 RCL_castRayMultiHit(r
,arrayFunc
,typeFunction
,hits
,&hitCount
,constraints
);
1056 columnFunc(hits
,hitCount
,i
,r
);
1064 Helper function that determines intersection with both ceiling and floor.
1066 RCL_Unit
_RCL_floorCeilFunction(int16_t x
, int16_t y
)
1068 RCL_Unit f
= _RCL_floorFunction(x
,y
);
1070 if (_RCL_ceilFunction
== 0)
1073 RCL_Unit c
= _RCL_ceilFunction(x
,y
);
1075 #ifndef RCL_RAYCAST_TINY
1076 return ((f
& 0x0000ffff) << 16) | (c
& 0x0000ffff);
1078 return ((f
& 0x00ff) << 8) | (c
& 0x00ff);
1082 RCL_Unit
_floorHeightNotZeroFunction(int16_t x
, int16_t y
)
1084 return _RCL_floorFunction(x
,y
) == 0 ? 0 :
1085 (x
& 0x00FF) | ((y
& 0x00FF) << 8);
1086 // ^ this makes collisions between all squares - needed for rolling doors
1089 RCL_Unit
RCL_adjustDistance(RCL_Unit distance
, RCL_Camera
*camera
,
1092 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1093 possibly be computed more efficiently by not computing Euclidean
1094 distance at all, but rather compute the distance of the collision
1095 point from the projection plane (line). */
1099 RCL_vectorsAngleCos(RCL_angleToDirection(camera
->direction
),
1100 ray
->direction
)) / RCL_UNITS_PER_SQUARE
;
1102 return RCL_nonZero(result
);
1103 // ^ prevent division by zero
1106 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1107 static inline int16_t _RCL_drawHorizontal(
1110 RCL_Unit limit1
, // TODO: int16_t?
1112 RCL_Unit verticalOffset
,
1114 int8_t computeDepth
,
1115 int8_t computeCoords
,
1116 int16_t depthIncrementMultiplier
,
1118 RCL_PixelInfo
*pixelInfo
1121 RCL_Unit depthIncrement
;
1125 pixelInfo
->isWall
= 0;
1127 int16_t limit
= RCL_clamp(yTo
,limit1
,limit2
);
1129 /* for performance reasons have different version of the critical loop
1130 to be able to branch early */
1131 #define loop(doDepth,doCoords)\
1133 if (doDepth) /*constant condition - compiler should optimize it out*/\
1135 pixelInfo->depth += RCL_absVal(verticalOffset) *\
1136 RCL_VERTICAL_DEPTH_MULTIPLY;\
1137 depthIncrement = depthIncrementMultiplier *\
1138 _RCL_horizontalDepthStep;\
1140 if (doCoords) /*constant condition - compiler should optimize it out*/\
1142 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1143 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1145 for (int16_t i = yCurrent + increment;\
1146 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1149 pixelInfo->position.y = i;\
1150 if (doDepth) /*constant condition - compiler should optimize it out*/\
1151 pixelInfo->depth += depthIncrement;\
1152 if (doCoords) /*constant condition - compiler should optimize it out*/\
1154 RCL_Unit d = _RCL_floorPixelDistances[i];\
1155 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1156 pixelInfo->texCoords.x =\
1157 _RCL_camera.position.x + ((d * dx) / d2);\
1158 pixelInfo->texCoords.y =\
1159 _RCL_camera.position.y + ((d * dy) / d2);\
1161 RCL_PIXEL_FUNCTION(pixelInfo);\
1165 if (computeDepth
) // branch early
1185 /// Helper for drawing walls. Returns the last drawn pixel position.
1186 static inline int16_t _RCL_drawWall(
1190 RCL_Unit limit1
, // TODO: int16_t?
1194 RCL_PixelInfo
*pixelInfo
1197 pixelInfo
->isWall
= 1;
1199 RCL_Unit limit
= RCL_clamp(yTo
,limit1
,limit2
);
1201 RCL_Unit wallLength
= yTo
- yFrom
- 1;
1202 wallLength
= RCL_nonZero(wallLength
);
1204 RCL_Unit wallPosition
= RCL_absVal(yFrom
- yCurrent
) - increment
;
1206 RCL_Unit coordStep
= RCL_COMPUTE_WALL_TEXCOORDS
?
1207 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1208 RCL_UNITS_PER_SQUARE
/ wallLength
1214 pixelInfo
->texCoords
.y
= RCL_COMPUTE_WALL_TEXCOORDS
?
1215 wallPosition
* coordStep
: 0;
1217 #if RCL_ACCURATE_WALL_TEXTURING == 1
1220 if (RCL_absVal(coordStep
) < RCL_MIN_TEXTURE_STEP
)
1221 /* for the sake of performance there are two-versions of the loop - it's
1222 better to branch early than inside the loop */
1225 for (RCL_Unit i
= yCurrent
+ increment
;
1226 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1229 // more expensive texture coord computing
1230 pixelInfo
->position
.y
= i
;
1232 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1233 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1234 pixelInfo
->texCoords
.y
= (wallPosition
* RCL_UNITS_PER_SQUARE
) / wallLength
;
1236 pixelInfo
->texCoords
.y
= (wallPosition
* height
) / wallLength
;
1241 RCL_PIXEL_FUNCTION(pixelInfo
);
1246 for (RCL_Unit i
= yCurrent
+ increment
;
1247 increment
== -1 ? i
>= limit
: i
<= limit
; // TODO: is efficient?
1250 // cheaper texture coord computing
1252 pixelInfo
->position
.y
= i
;
1254 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1255 pixelInfo
->texCoords
.y
+= coordStep
;
1258 RCL_PIXEL_FUNCTION(pixelInfo
);
1265 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1266 static inline void _RCL_makeInfiniteHit(RCL_HitResult
*hit
, RCL_Ray
*ray
)
1268 hit
->distance
= RCL_UNITS_PER_SQUARE
* RCL_UNITS_PER_SQUARE
;
1269 /* ^ horizon is at infinity, but we can't use too big infinity
1270 (RCL_INFINITY) because it would overflow in the following mult. */
1271 hit
->position
.x
= (ray
->direction
.x
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1272 hit
->position
.y
= (ray
->direction
.y
* hit
->distance
) / RCL_UNITS_PER_SQUARE
;
1275 hit
->textureCoord
= 0;
1276 hit
->arrayValue
= 0;
1281 void _RCL_columnFunctionComplex(RCL_HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
1284 // last written Y position, can never go backwards
1285 RCL_Unit fPosY
= _RCL_camera
.resolution
.y
;
1286 RCL_Unit cPosY
= -1;
1288 // world coordinates (relative to camera height though)
1289 RCL_Unit fZ1World
= _RCL_startFloorHeight
;
1290 RCL_Unit cZ1World
= _RCL_startCeil_Height
;
1298 // we'll be simulatenously drawing the floor and the ceiling now
1299 for (RCL_Unit j
= 0; j
<= hitCount
; ++j
)
1300 { // ^ = add extra iteration for horizon plane
1301 int8_t drawingHorizon
= j
== hitCount
;
1304 RCL_Unit distance
= 1;
1306 RCL_Unit fWallHeight
= 0, cWallHeight
= 0;
1307 RCL_Unit fZ2World
= 0, cZ2World
= 0;
1308 RCL_Unit fZ1Screen
= 0, cZ1Screen
= 0;
1309 RCL_Unit fZ2Screen
= 0, cZ2Screen
= 0;
1311 if (!drawingHorizon
)
1314 distance
= RCL_nonZero(hit
.distance
);
1317 fWallHeight
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1318 fZ2World
= fWallHeight
- _RCL_camera
.height
;
1319 fZ1Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1320 (fZ1World
* _RCL_camera
.resolution
.y
) /
1321 RCL_UNITS_PER_SQUARE
,distance
);
1322 fZ2Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1323 (fZ2World
* _RCL_camera
.resolution
.y
) /
1324 RCL_UNITS_PER_SQUARE
,distance
);
1326 if (_RCL_ceilFunction
!= 0)
1328 cWallHeight
= _RCL_ceilFunction(hit
.square
.x
,hit
.square
.y
);
1329 cZ2World
= cWallHeight
- _RCL_camera
.height
;
1330 cZ1Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1331 (cZ1World
* _RCL_camera
.resolution
.y
) /
1332 RCL_UNITS_PER_SQUARE
,distance
);
1333 cZ2Screen
= _RCL_middleRow
- RCL_perspectiveScale(
1334 (cZ2World
* _RCL_camera
.resolution
.y
) /
1335 RCL_UNITS_PER_SQUARE
,distance
);
1340 fZ1Screen
= _RCL_middleRow
;
1341 cZ1Screen
= _RCL_middleRow
+ 1;
1342 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1348 p
.isHorizon
= drawingHorizon
;
1350 // draw floor until wall
1352 p
.height
= fZ1World
+ _RCL_camera
.height
;
1354 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1355 p
.depth
= (_RCL_fHorizontalDepthStart
- fPosY
) * _RCL_horizontalDepthStep
;
1360 limit
= _RCL_drawHorizontal(fPosY
,fZ1Screen
,cPosY
+ 1,
1361 _RCL_camera
.resolution
.y
,fZ1World
,-1,RCL_COMPUTE_FLOOR_DEPTH
,
1362 // ^ purposfully allow outside screen bounds
1363 RCL_COMPUTE_FLOOR_TEXCOORDS
&& p
.height
== RCL_FLOOR_TEXCOORDS_HEIGHT
,
1369 if (_RCL_ceilFunction
!= 0 || drawingHorizon
)
1371 // draw ceiling until wall
1373 p
.height
= cZ1World
+ _RCL_camera
.height
;
1375 #if RCL_COMPUTE_CEILING_DEPTH == 1
1376 p
.depth
= (cPosY
- _RCL_cHorizontalDepthStart
) *
1377 _RCL_horizontalDepthStep
;
1380 limit
= _RCL_drawHorizontal(cPosY
,cZ1Screen
,
1381 -1,fPosY
- 1,cZ1World
,1,RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1382 // ^ purposfully allow outside screen bounds here
1388 if (!drawingHorizon
) // don't draw walls for horizon plane
1393 p
.texCoords
.x
= hit
.textureCoord
;
1394 p
.height
= 0; // don't compute this, no use
1398 if (fPosY
> 0) // still pixels left?
1402 limit
= _RCL_drawWall(fPosY
,fZ1Screen
,fZ2Screen
,cPosY
+ 1,
1403 _RCL_camera
.resolution
.y
,
1404 // ^ purposfully allow outside screen bounds here
1405 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1406 RCL_UNITS_PER_SQUARE
1416 fZ1World
= fZ2World
; // for the next iteration
1417 } // ^ purposfully allow outside screen bounds here
1419 // draw ceiling wall
1421 if (_RCL_ceilFunction
!= 0 && cPosY
< _RCL_camResYLimit
) // pixels left?
1425 limit
= _RCL_drawWall(cPosY
,cZ1Screen
,cZ2Screen
,
1427 // ^ puposfully allow outside screen bounds here
1428 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1429 RCL_UNITS_PER_SQUARE
1438 cZ1World
= cZ2World
; // for the next iteration
1439 } // ^ puposfully allow outside screen bounds here
1444 void _RCL_columnFunctionSimple(RCL_HitResult
*hits
, uint16_t hitCount
,
1445 uint16_t x
, RCL_Ray ray
)
1448 RCL_Unit wallHeightScreen
= 0;
1449 RCL_Unit wallStart
= _RCL_middleRow
;
1450 RCL_Unit heightOffset
= 0;
1459 RCL_HitResult hit
= hits
[0];
1463 if (_RCL_rollFunction
!= 0 && RCL_COMPUTE_WALL_TEXCOORDS
== 1)
1465 if (hit
.arrayValue
== 0)
1467 // standing inside door square, looking out => move to the next hit
1476 // normal hit, check the door roll
1478 RCL_Unit texCoordMod
= hit
.textureCoord
% RCL_UNITS_PER_SQUARE
;
1480 int8_t unrolled
= hit
.doorRoll
>= 0 ?
1481 hit
.doorRoll
> texCoordMod
:
1482 texCoordMod
> RCL_UNITS_PER_SQUARE
+ hit
.doorRoll
;
1488 if (hitCount
> 1) /* should probably always be true (hit on square
1491 if (hit
.direction
% 2 != hits
[1].direction
% 2)
1493 // hit on the inner side
1497 else if (hitCount
> 2)
1499 // hit on the opposite side
1512 dist
= hit
.distance
;
1514 int16_t wallHeightWorld
= _RCL_floorFunction(hit
.square
.x
,hit
.square
.y
);
1516 wallHeightScreen
= RCL_perspectiveScale((wallHeightWorld
*
1517 _RCL_camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
,dist
);
1519 int16_t RCL_normalizedWallHeight
= wallHeightWorld
!= 0 ?
1520 ((RCL_UNITS_PER_SQUARE
* wallHeightScreen
) / wallHeightWorld
) : 0;
1522 heightOffset
= RCL_perspectiveScale(_RCL_cameraHeightScreen
,dist
);
1524 wallStart
= _RCL_middleRow
- wallHeightScreen
+ heightOffset
+
1525 RCL_normalizedWallHeight
;
1530 _RCL_makeInfiniteHit(&p
.hit
,&ray
);
1539 p
.height
= RCL_UNITS_PER_SQUARE
;
1541 y
= _RCL_drawHorizontal(-1,wallStart
,-1,_RCL_middleRow
,_RCL_camera
.height
,1,
1542 RCL_COMPUTE_CEILING_DEPTH
,0,1,&ray
,&p
);
1551 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1552 p
.hit
.textureCoord
-= p
.hit
.doorRoll
;
1555 p
.texCoords
.x
= p
.hit
.textureCoord
;
1558 RCL_Unit limit
= _RCL_drawWall(y
,wallStart
,wallStart
+ wallHeightScreen
- 1,
1559 -1,_RCL_camResYLimit
,p
.hit
.arrayValue
,1,&p
);
1561 y
= RCL_max(y
,limit
); // take max, in case no wall was drawn
1562 y
= RCL_max(y
,wallStart
);
1568 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1569 p
.depth
= (_RCL_camera
.resolution
.y
- y
) * _RCL_horizontalDepthStep
+ 1;
1572 _RCL_drawHorizontal(y
,_RCL_camResYLimit
,-1,_RCL_camResYLimit
,
1573 _RCL_camera
.height
,1,RCL_COMPUTE_FLOOR_DEPTH
,RCL_COMPUTE_FLOOR_TEXCOORDS
,
1578 Precomputes a distance from camera to the floor at each screen row into an
1579 array (must be preallocated with sufficient (camera.resolution.y) length).
1581 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera
,
1582 RCL_Unit
*dest
, uint16_t startIndex
)
1584 RCL_Unit camHeightScreenSize
=
1585 (camera
.height
* camera
.resolution
.y
) / RCL_UNITS_PER_SQUARE
;
1587 for (uint16_t i
= startIndex
1588 ; i
< camera
.resolution
.y
; ++i
)
1589 dest
[i
] = RCL_perspectiveScaleInverse(camHeightScreenSize
,
1590 RCL_absVal(i
- _RCL_middleRow
));
1593 void RCL_renderComplex(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1594 RCL_ArrayFunction ceilingHeightFunc
, RCL_ArrayFunction typeFunction
,
1595 RCL_RayConstraints constraints
)
1597 _RCL_floorFunction
= floorHeightFunc
;
1598 _RCL_ceilFunction
= ceilingHeightFunc
;
1600 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1602 uint16_t halfResY
= cam
.resolution
.y
/ 2;
1604 _RCL_middleRow
= halfResY
+ cam
.shear
;
1606 _RCL_fHorizontalDepthStart
= _RCL_middleRow
+ halfResY
;
1607 _RCL_cHorizontalDepthStart
= _RCL_middleRow
- halfResY
;
1609 _RCL_startFloorHeight
= floorHeightFunc(
1610 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1611 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
;
1613 _RCL_startCeil_Height
=
1614 ceilingHeightFunc
!= 0 ?
1616 RCL_divRoundDown(cam
.position
.x
,RCL_UNITS_PER_SQUARE
),
1617 RCL_divRoundDown(cam
.position
.y
,RCL_UNITS_PER_SQUARE
)) -1 * cam
.height
1620 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1622 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1623 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1624 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,0);
1625 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1628 RCL_castRaysMultiHit(cam
,_RCL_floorCeilFunction
,typeFunction
,
1629 _RCL_columnFunctionComplex
,constraints
);
1632 void RCL_renderSimple(RCL_Camera cam
, RCL_ArrayFunction floorHeightFunc
,
1633 RCL_ArrayFunction typeFunc
, RCL_ArrayFunction rollFunc
,
1634 RCL_RayConstraints constraints
)
1636 _RCL_floorFunction
= floorHeightFunc
;
1638 _RCL_camResYLimit
= cam
.resolution
.y
- 1;
1639 _RCL_middleRow
= cam
.resolution
.y
/ 2;
1640 _RCL_rollFunction
= rollFunc
;
1642 _RCL_cameraHeightScreen
=
1643 (_RCL_camera
.resolution
.y
* (_RCL_camera
.height
- RCL_UNITS_PER_SQUARE
)) /
1644 RCL_UNITS_PER_SQUARE
;
1646 _RCL_horizontalDepthStep
= RCL_HORIZON_DEPTH
/ cam
.resolution
.y
;
1648 constraints
.maxHits
=
1649 _RCL_rollFunction
== 0 ?
1650 1 : // no door => 1 hit is enough
1651 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1653 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1654 RCL_Unit floorPixelDistances
[cam
.resolution
.y
];
1655 _RCL_precomputeFloorDistances(cam
,floorPixelDistances
,_RCL_middleRow
);
1656 _RCL_floorPixelDistances
= floorPixelDistances
; // pass to column function
1659 RCL_castRaysMultiHit(cam
,_floorHeightNotZeroFunction
,typeFunc
,
1660 _RCL_columnFunctionSimple
, constraints
);
1662 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1663 _RCL_floorPixelDistances
= 0;
1667 RCL_Vector2D
RCL_normalize(RCL_Vector2D v
)
1669 RCL_profileCall(RCL_normalize
);
1671 RCL_Vector2D result
;
1672 RCL_Unit l
= RCL_len(v
);
1675 result
.x
= (v
.x
* RCL_UNITS_PER_SQUARE
) / l
;
1676 result
.y
= (v
.y
* RCL_UNITS_PER_SQUARE
) / l
;
1681 RCL_Unit
RCL_vectorsAngleCos(RCL_Vector2D v1
, RCL_Vector2D v2
)
1683 RCL_profileCall(RCL_vectorsAngleCos
);
1685 v1
= RCL_normalize(v1
);
1686 v2
= RCL_normalize(v2
);
1688 return (v1
.x
* v2
.x
+ v1
.y
* v2
.y
) / RCL_UNITS_PER_SQUARE
;
1691 RCL_PixelInfo
RCL_mapToScreen(RCL_Vector2D worldPosition
, RCL_Unit height
,
1694 RCL_PixelInfo result
;
1696 RCL_Unit d
= RCL_dist(worldPosition
,camera
.position
);
1698 RCL_Vector2D toPoint
;
1700 toPoint
.x
= worldPosition
.x
- camera
.position
.x
;
1701 toPoint
.y
= worldPosition
.y
- camera
.position
.y
;
1703 RCL_Vector2D cameraDir
= RCL_angleToDirection(camera
.direction
);
1705 result
.depth
= // adjusted distance
1706 (d
* RCL_vectorsAngleCos(cameraDir
,toPoint
)) / RCL_UNITS_PER_SQUARE
;
1708 result
.position
.y
= camera
.resolution
.y
/ 2 -
1709 (camera
.resolution
.y
*
1710 RCL_perspectiveScale(height
- camera
.height
,result
.depth
)) / RCL_UNITS_PER_SQUARE
1713 RCL_Unit middleColumn
= camera
.resolution
.x
/ 2;
1715 RCL_Unit a
= RCL_sqrtInt(d
* d
- result
.depth
* result
.depth
);
1718 r
.start
= camera
.position
;
1719 r
.direction
= cameraDir
;
1721 if (!RCL_pointIfLeftOfRay(worldPosition
,r
))
1724 RCL_Unit cos
= RCL_cosInt(RCL_HORIZONTAL_FOV_HALF
);
1726 RCL_Unit b
= (result
.depth
* RCL_sinInt(RCL_HORIZONTAL_FOV_HALF
)) /
1730 result
.position
.x
= (a
* middleColumn
) / RCL_nonZero(b
);
1731 result
.position
.x
= middleColumn
- result
.position
.x
;
1736 RCL_Unit
RCL_degreesToUnitsAngle(int16_t degrees
)
1738 return (degrees
* RCL_UNITS_PER_SQUARE
) / 360;
1741 RCL_Unit
RCL_perspectiveScale(RCL_Unit originalSize
, RCL_Unit distance
)
1743 RCL_profileCall(RCL_perspectiveScale
);
1745 return distance
!= 0 ?
1746 (originalSize
* RCL_UNITS_PER_SQUARE
) /
1747 ((RCL_VERTICAL_FOV
* 2 * distance
) / RCL_UNITS_PER_SQUARE
)
1751 RCL_Unit
RCL_perspectiveScaleInverse(RCL_Unit originalSize
,
1752 RCL_Unit scaledSize
)
1754 return scaledSize
!= 0 ?
1755 (originalSize
* RCL_UNITS_PER_SQUARE
+ RCL_UNITS_PER_SQUARE
/ 2) /
1756 // ^ take the middle
1757 ((RCL_VERTICAL_FOV
* 2 * scaledSize
) / RCL_UNITS_PER_SQUARE
)
1761 void RCL_moveCameraWithCollision(RCL_Camera
*camera
, RCL_Vector2D planeOffset
,
1762 RCL_Unit heightOffset
, RCL_ArrayFunction floorHeightFunc
,
1763 RCL_ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
)
1765 int8_t movesInPlane
= planeOffset
.x
!= 0 || planeOffset
.y
!= 0;
1766 int16_t xSquareNew
, ySquareNew
;
1768 if (movesInPlane
|| force
)
1770 RCL_Vector2D corner
; // BBox corner in the movement direction
1771 RCL_Vector2D cornerNew
;
1773 int16_t xDir
= planeOffset
.x
> 0 ? 1 : -1;
1774 int16_t yDir
= planeOffset
.y
> 0 ? 1 : -1;
1776 corner
.x
= camera
->position
.x
+ xDir
* RCL_CAMERA_COLL_RADIUS
;
1777 corner
.y
= camera
->position
.y
+ yDir
* RCL_CAMERA_COLL_RADIUS
;
1779 int16_t xSquare
= RCL_divRoundDown(corner
.x
,RCL_UNITS_PER_SQUARE
);
1780 int16_t ySquare
= RCL_divRoundDown(corner
.y
,RCL_UNITS_PER_SQUARE
);
1782 cornerNew
.x
= corner
.x
+ planeOffset
.x
;
1783 cornerNew
.y
= corner
.y
+ planeOffset
.y
;
1785 xSquareNew
= RCL_divRoundDown(cornerNew
.x
,RCL_UNITS_PER_SQUARE
);
1786 ySquareNew
= RCL_divRoundDown(cornerNew
.y
,RCL_UNITS_PER_SQUARE
);
1788 RCL_Unit bottomLimit
= -1 * RCL_INFINITY
;
1789 RCL_Unit topLimit
= RCL_INFINITY
;
1793 bottomLimit
= camera
->height
- RCL_CAMERA_COLL_HEIGHT_BELOW
+
1794 RCL_CAMERA_COLL_STEP_HEIGHT
;
1796 topLimit
= camera
->height
+ RCL_CAMERA_COLL_HEIGHT_ABOVE
;
1799 // checks a single square for collision against the camera
1800 #define collCheck(dir,s1,s2)\
1803 RCL_Unit height = floorHeightFunc(s1,s2);\
1804 if (height > bottomLimit)\
1806 else if (ceilingHeightFunc != 0)\
1808 height = ceilingHeightFunc(s1,s2);\
1809 if (height < topLimit)\
1814 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1816 // check a collision against non-diagonal square
1817 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1818 if (dir##SquareNew != dir##Square)\
1820 collCheck(dir,s1,s2)\
1822 if (!dir##Collides)\
1823 { /* now also check for coll on the neighbouring square */ \
1824 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1825 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1826 if (dir2##Square2 != dir2##Square)\
1829 collCheck(dir,dir##SquareNew,dir2##Square2)\
1831 collCheck(dir,dir2##Square2,dir##SquareNew)\
1835 int8_t xCollides
= 0;
1836 collCheckOrtho(x
,y
,xSquareNew
,ySquare
,1)
1838 int8_t yCollides
= 0;
1839 collCheckOrtho(y
,x
,xSquare
,ySquareNew
,0)
1841 #define collHandle(dir)\
1843 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1844 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1847 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1848 collision can't happen */
1850 if (xSquare
!= xSquareNew
&& ySquare
!= ySquareNew
) // corner?
1852 int8_t xyCollides
= 0;
1853 collCheck(xy
,xSquareNew
,ySquareNew
)
1857 // normally should slide, but let's KISS
1869 camera
->position
.x
= cornerNew
.x
- xDir
* RCL_CAMERA_COLL_RADIUS
;
1870 camera
->position
.y
= cornerNew
.y
- yDir
* RCL_CAMERA_COLL_RADIUS
;
1873 if (computeHeight
&& (movesInPlane
|| heightOffset
!= 0 || force
))
1875 camera
->height
+= heightOffset
;
1877 int16_t xSquare1
= RCL_divRoundDown(camera
->position
.x
-
1878 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1880 int16_t xSquare2
= RCL_divRoundDown(camera
->position
.x
+
1881 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1883 int16_t ySquare1
= RCL_divRoundDown(camera
->position
.y
-
1884 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1886 int16_t ySquare2
= RCL_divRoundDown(camera
->position
.y
+
1887 RCL_CAMERA_COLL_RADIUS
,RCL_UNITS_PER_SQUARE
);
1889 RCL_Unit bottomLimit
= floorHeightFunc(xSquare1
,ySquare1
);
1890 RCL_Unit topLimit
= ceilingHeightFunc
!= 0 ?
1891 ceilingHeightFunc(xSquare1
,ySquare1
) : RCL_INFINITY
;
1895 #define checkSquares(s1,s2)\
1897 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1898 bottomLimit = RCL_max(bottomLimit,height);\
1899 height = ceilingHeightFunc != 0 ?\
1900 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
1901 topLimit = RCL_min(topLimit,height);\
1904 if (xSquare2
!= xSquare1
)
1907 if (ySquare2
!= ySquare1
)
1910 if (xSquare2
!= xSquare1
&& ySquare2
!= ySquare1
)
1913 camera
->height
= RCL_clamp(camera
->height
,
1914 bottomLimit
+ RCL_CAMERA_COLL_HEIGHT_BELOW
,
1915 topLimit
- RCL_CAMERA_COLL_HEIGHT_ABOVE
);
1921 void RCL_initCamera(RCL_Camera
*camera
)
1923 camera
->position
.x
= 0;
1924 camera
->position
.y
= 0;
1925 camera
->direction
= 0;
1926 camera
->resolution
.x
= 20;
1927 camera
->resolution
.y
= 15;
1929 camera
->height
= RCL_UNITS_PER_SQUARE
;
1932 void RCL_initRayConstraints(RCL_RayConstraints
*constraints
)
1934 constraints
->maxHits
= 1;
1935 constraints
->maxSteps
= 20;