Horizontal -> vertical
[raycastlib.git] / raycastlib.h
blobc09d280c8a89f07c8204b9e7cd50e48ed944b4eb
1 #ifndef RAYCASTLIB_H
2 #define RAYCASTLIB_H
4 /**
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
7 standard library.
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
28 license: CC0 1.0
29 version: 0.82
32 #include <stdint.h>
34 #ifndef RCL_RAYCAST_TINY /** Turns on super efficient version of this library.
35 Only use if neccesarry, looks ugly. Also not done
36 yet. */
37 #define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a
38 spatial square. */
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
44 #else
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
49 #endif
51 #ifndef RCL_COMPUTE_WALL_TEXCOORDS
52 #define RCL_COMPUTE_WALL_TEXCOORDS 1
53 #endif
55 #ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
56 #define RCL_COMPUTE_FLOOR_TEXCOORDS 0
57 #endif
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
64 level is allowed). */
65 #endif
67 #ifndef RCL_USE_COS_LUT
68 #define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
69 0: none (compute)
70 1: 64 items
71 2: 128 items */
72 #endif
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) */
79 #endif
81 #ifndef RCL_RECTILINEAR
82 #define RCL_RECTILINEAR 1 /**< Whether to use rectilinear perspective (normally
83 used), or curvilinear perspective (fish eye). */
84 #endif
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). */
90 #endif
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. */
98 #endif
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
103 needed. */
104 #endif
106 #ifndef RCL_COMPUTE_CEILING_DEPTH
107 #define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for
108 ceiling. */
109 #endif
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). */
115 #endif
117 #ifndef RCL_VERTICAL_FOV
118 #define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 2)
119 #endif
121 #ifndef RCL_HORIZONTAL_FOV
122 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
123 #endif
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
129 #endif
131 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
132 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
133 #endif
135 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
136 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
137 #endif
139 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
140 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
141 #endif
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). */
149 #else
150 #define RCL_MIN_TEXTURE_STEP 24
151 #endif
152 #endif
154 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
155 horizon has (the floor
156 depth is only
157 approximated with the
158 help of this
159 constant). */
160 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
161 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
162 difference when approximating floor/ceil
163 depth. */
164 #endif
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){\
174 printf("ray:\n");\
175 printf(" start: ");\
176 RCL_logV2D(r.start);\
177 printf(" dir: ");\
178 RCL_logV2D(r.direction);}
180 #define RCL_logHitResult(h){\
181 printf("hit:\n");\
182 printf(" square: ");\
183 RCL_logV2D(h.square);\
184 printf(" pos: ");\
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){\
191 printf("pixel:\n");\
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);\
199 printf(" hit: ");\
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.
214 typedef struct
216 RCL_Unit x;
217 RCL_Unit y;
218 } RCL_Vector2D;
220 typedef struct
222 RCL_Vector2D start;
223 RCL_Vector2D direction;
224 } RCL_Ray;
226 typedef struct
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
233 position. */
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
244 index).*/
245 RCL_Unit doorRoll; ///< Holds value of door roll.
246 } RCL_HitResult;
248 typedef struct
250 RCL_Vector2D position;
251 RCL_Unit direction;
252 RCL_Vector2D resolution;
253 int16_t shear; /**< Shear offset in pixels (0 => no shear), can simulate
254 looking up/down. */
255 RCL_Unit height;
256 } RCL_Camera;
259 Holds an information about a single rendered pixel (for a pixel function
260 that works as a fragment shader).
262 typedef struct
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. */
273 } RCL_PixelInfo;
275 void RCL_PIXEL_FUNCTION (RCL_PixelInfo *pixel);
277 typedef struct
279 uint16_t maxHits;
280 uint16_t maxSteps;
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
290 very often.
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
299 very often.
301 typedef void (*RCL_PixelFunction)(RCL_PixelInfo *info);
303 typedef void
304 (*RCL_ColumnFunction)(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
305 RCL_Ray ray);
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,
317 RCL_Camera camera);
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
324 the fish eye effect)
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
336 returned
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);
346 Cos function.
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
368 uses.
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
380 function.
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
388 view.
390 This function should render each screen pixel exactly once.
392 function rendering summary:
393 - performance: slower
394 - accuracy: higher
395 - wall textures: yes
396 - different wall heights: yes
397 - floor/ceiling textures: no
398 - floor geometry: yes, multilevel
399 - ceiling geometry: yes (optional), multilevel
400 - rolling door: no
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
420 flat levels.
422 function rendering summary:
423 - performance: faster
424 - accuracy: lower
425 - wall textures: yes
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)
430 - rolling door: yes
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
450 movement.
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
461 game, also faster)
462 @param force if true, forces to recompute collision even if position doesn't
463 change
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 //=============================================================================
473 // privates
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;
492 #ifdef RCL_PROFILE
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); }
528 #else
529 #define RCL_profileCall(c)
530 #endif
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)
539 return value;
540 else
541 return valueMax;
543 else
544 return valueMin;
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
582 #else
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
590 #endif
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
604 #endif
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];
616 #else
617 return cosLUT[input / 16];
618 #endif
620 #elif RCL_USE_COS_LUT == 2
621 return cosLUT[input / 8];
622 #else
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);
629 else
630 return trigHelper(RCL_UNITS_PER_SQUARE - input);
631 #endif
634 #undef trigHelper
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);
645 RCL_Vector2D result;
647 result.x = RCL_cosInt(angle);
648 result.y = -1 * RCL_sinInt(angle);
650 return result;
653 uint16_t RCL_sqrtInt(RCL_Unit value)
655 RCL_profileCall(RCL_sqrtInt);
657 #ifdef RCL_RAYCAST_TINY
658 uint16_t result = 0;
659 uint16_t a = value;
660 uint16_t b = 1u << 14;
661 #else
662 uint32_t result = 0;
663 uint32_t a = value;
664 uint32_t b = 1u << 30;
665 #endif
667 while (b > a)
668 b >>= 2;
670 while (b != 0)
672 if (a >= result + b)
674 a -= result + b;
675 result = result + 2 * b;
678 b >>= 2;
679 result >>= 1;
682 return result;
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
695 dx = RCL_absVal(dx);
696 dy = RCL_absVal(dy);
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;
707 if (dx < dy)
709 a = dy;
710 b = dx;
712 else
714 a = dx;
715 b = dy;
718 result = a + (44 * b) / 102;
720 if (a < (b << 4))
721 result -= (5 * a) / 128;
723 return result;
724 #else
725 dx = dx * dx;
726 dy = dy * dy;
728 return RCL_sqrtInt((RCL_Unit) (dx + dy));
729 #endif
732 RCL_Unit RCL_len(RCL_Vector2D v)
734 RCL_profileCall(RCL_len);
736 RCL_Vector2D zero;
737 zero.x = 0;
738 zero.y = 0;
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);
765 *hitResultsLen = 0;
767 RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
769 // DDA variables
770 RCL_Vector2D nextSideDist; // dist. from start to the next side in given axis
771 RCL_Vector2D delta;
772 RCL_Vector2D step; // -1 or 1 for each axis
773 int8_t stepHorizontal = 0; // whether the last step was hor. or vert.
775 nextSideDist.x = 0;
776 nextSideDist.y = 0;
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));
783 // init DDA
785 if (ray.direction.x < 0)
787 step.x = -1;
788 nextSideDist.x = (RCL_wrap(ray.start.x,RCL_UNITS_PER_SQUARE) * delta.x) /
789 RCL_UNITS_PER_SQUARE;
791 else
793 step.x = 1;
794 nextSideDist.x =
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)
801 step.y = -1;
802 nextSideDist.y = (RCL_wrap(ray.start.y,RCL_UNITS_PER_SQUARE) * delta.y) /
803 RCL_UNITS_PER_SQUARE;
805 else
807 step.y = 1;
808 nextSideDist.y =
809 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.y,RCL_UNITS_PER_SQUARE)) *
810 delta.y) / RCL_UNITS_PER_SQUARE;
813 // DDA loop
815 for (uint16_t i = 0; i < constraints.maxSteps; ++i)
817 RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y);
819 if (currentType != squareType)
821 // collision
823 RCL_HitResult h;
825 h.arrayValue = currentType;
826 h.doorRoll = 0;
827 h.position = currentPos;
828 h.square = currentSquare;
830 if (stepHorizontal)
832 h.position.x = currentSquare.x * RCL_UNITS_PER_SQUARE;
833 h.direction = 3;
835 if (step.x == -1)
837 h.direction = 1;
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));
845 #if RCL_RECTILINEAR
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). */
853 h.distance =
854 ((h.position.x - ray.start.x) * RCL_UNITS_PER_SQUARE) /
855 RCL_nonZero(ray.direction.x);
856 #endif
858 else
860 h.position.y = currentSquare.y * RCL_UNITS_PER_SQUARE;
861 h.direction = 2;
863 if (step.y == -1)
865 h.direction = 0;
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));
873 #if RCL_RECTILINEAR
874 h.distance =
875 ((h.position.y - ray.start.y) * RCL_UNITS_PER_SQUARE) /
876 RCL_nonZero(ray.direction.y);
877 #endif
880 #if !RCL_RECTILINEAR
881 h.distance = RCL_dist(h.position,ray.start);
882 #endif
884 if (typeFunc != 0)
885 h.type = typeFunc(currentSquare.x,currentSquare.y);
887 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
888 switch (h.direction)
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)
910 h.doorRoll *= -1;
913 #else
914 h.textureCoord = 0;
915 #endif
917 hitResults[*hitResultsLen] = h;
919 *hitResultsLen += 1;
921 squareType = currentType;
923 if (*hitResultsLen >= constraints.maxHits)
924 break;
927 // DDA step
929 if (nextSideDist.x < nextSideDist.y)
931 nextSideDist.x += delta.x;
932 currentSquare.x += step.x;
933 stepHorizontal = 1;
935 else
937 nextSideDist.y += delta.y;
938 currentSquare.y += step.y;
939 stepHorizontal = 0;
944 RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc)
946 RCL_profileCall(RCL_castRay);
948 RCL_HitResult result;
949 uint16_t RCL_len;
950 RCL_RayConstraints c;
952 c.maxSteps = 1000;
953 c.maxHits = 1;
955 RCL_castRayMultiHit(ray,arrayFunc,0,&result,&RCL_len,c);
957 if (RCL_len == 0)
958 result.distance = -1;
960 return result;
963 void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
964 RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
965 RCL_RayConstraints constraints)
967 RCL_Vector2D dir1 =
968 RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF);
970 RCL_Vector2D dir2 =
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];
977 uint16_t hitCount;
979 RCL_Ray r;
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);
998 currentDX += dX;
999 currentDY += dY;
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)
1011 return f;
1013 RCL_Unit c = _RCL_ceilFunction(x,y);
1015 #ifndef RCL_RAYCAST_TINY
1016 return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff);
1017 #else
1018 return ((f & 0x00ff) << 8) | (c & 0x00ff);
1019 #endif
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,
1030 RCL_Ray *ray)
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). */
1037 RCL_Unit result =
1038 (distance *
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(
1048 RCL_Unit yCurrent,
1049 RCL_Unit yTo,
1050 RCL_Unit limit1, // TODO: int16_t?
1051 RCL_Unit limit2,
1052 RCL_Unit verticalOffset,
1053 int16_t increment,
1054 int8_t computeDepth,
1055 int8_t computeCoords,
1056 int16_t depthIncrementMultiplier,
1057 RCL_Ray *ray,
1058 RCL_PixelInfo *pixelInfo
1061 _RCL_UNUSED(ray);
1063 RCL_Unit depthIncrement;
1064 RCL_Unit dx;
1065 RCL_Unit dy;
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? */\
1089 i += increment)\
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
1109 if (!computeCoords)
1110 loop(1,0)
1111 else
1112 loop(1,1)
1114 else
1116 if (!computeCoords)
1117 loop(0,0)
1118 else
1119 loop(1,1)
1122 #undef loop
1124 return limit;
1127 /// Helper for drawing walls. Returns the last drawn pixel position.
1128 static inline int16_t _RCL_drawWall(
1129 RCL_Unit yCurrent,
1130 RCL_Unit yFrom,
1131 RCL_Unit yTo,
1132 RCL_Unit limit1, // TODO: int16_t?
1133 RCL_Unit limit2,
1134 RCL_Unit height,
1135 int16_t increment,
1136 RCL_PixelInfo *pixelInfo
1139 _RCL_UNUSED(height)
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
1153 #else
1154 height / wallLength
1155 #endif
1156 : 1;
1158 pixelInfo->texCoords.y = RCL_COMPUTE_WALL_TEXCOORDS ?
1159 wallPosition * coordStep : 0;
1161 #if RCL_ACCURATE_WALL_TEXTURING == 1
1162 if (1)
1163 #else
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 */
1167 #endif
1169 for (RCL_Unit i = yCurrent + increment;
1170 increment == -1 ? i >= limit : i <= limit; // TODO: is efficient?
1171 i += increment)
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;
1179 #else
1180 pixelInfo->texCoords.y = (wallPosition * height) / wallLength;
1181 #endif
1182 #endif
1184 wallPosition++;
1185 RCL_PIXEL_FUNCTION(pixelInfo);
1188 else
1190 for (RCL_Unit i = yCurrent + increment;
1191 increment == -1 ? i >= limit : i <= limit; // TODO: is efficient?
1192 i += increment)
1194 // cheaper texture coord computing
1196 pixelInfo->position.y = i;
1198 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1199 pixelInfo->texCoords.y += coordStep;
1200 #endif
1202 RCL_PIXEL_FUNCTION(pixelInfo);
1206 return limit;
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;
1218 hit->direction = 0;
1219 hit->textureCoord = 0;
1220 hit->arrayValue = 0;
1221 hit->doorRoll = 0;
1222 hit->type = 0;
1225 void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
1226 RCL_Ray ray)
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;
1236 RCL_PixelInfo p;
1237 p.position.x = x;
1238 p.height = 0;
1239 p.texCoords.x = 0;
1240 p.texCoords.y = 0;
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;
1247 RCL_HitResult hit;
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)
1257 hit = hits[j];
1258 distance = RCL_nonZero(hit.distance);
1259 p.hit = hit;
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);
1282 else
1284 fZ1Screen = _RCL_middleRow;
1285 cZ1Screen = _RCL_middleRow + 1;
1286 _RCL_makeInfiniteHit(&p.hit,&ray);
1289 RCL_Unit limit;
1291 p.isWall = 0;
1292 p.isHorizon = drawingHorizon;
1294 // draw floor until wall
1295 p.isFloor = 1;
1296 p.height = fZ1World + _RCL_camera.height;
1298 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1299 p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep;
1300 #else
1301 p.depth = 0;
1302 #endif
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,
1308 1,&ray,&p);
1310 if (fPosY > limit)
1311 fPosY = limit;
1313 if (_RCL_ceilFunction != 0 || drawingHorizon)
1315 // draw ceiling until wall
1316 p.isFloor = 0;
1317 p.height = cZ1World + _RCL_camera.height;
1319 #if RCL_COMPUTE_CEILING_DEPTH == 1
1320 p.depth = (cPosY - _RCL_cHorizontalDepthStart) *
1321 _RCL_horizontalDepthStep;
1322 #endif
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
1328 if (cPosY < limit)
1329 cPosY = limit;
1332 if (!drawingHorizon) // don't draw walls for horizon plane
1334 p.isWall = 1;
1335 p.depth = distance;
1336 p.isFloor = 1;
1337 p.texCoords.x = hit.textureCoord;
1338 p.height = 0; // don't compute this, no use
1340 // draw floor wall
1342 if (fPosY > 0) // still pixels left?
1344 p.isFloor = 1;
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
1351 #else
1352 fZ2World - fZ1World
1353 #endif
1354 ,-1,&p);
1357 if (fPosY > limit)
1358 fPosY = limit;
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?
1367 p.isFloor = 0;
1369 limit = _RCL_drawWall(cPosY,cZ1Screen,cZ2Screen,
1370 -1,fPosY - 1,
1371 // ^ puposfully allow outside screen bounds here
1372 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1373 RCL_UNITS_PER_SQUARE
1374 #else
1375 cZ2World - cZ1World
1376 #endif
1377 ,1,&p);
1379 if (cPosY < limit)
1380 cPosY = limit;
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)
1391 RCL_Unit y = 0;
1392 RCL_Unit wallHeightScreen = 0;
1393 RCL_Unit wallStart = _RCL_middleRow;
1394 RCL_Unit heightOffset = 0;
1396 RCL_Unit dist = 1;
1398 RCL_PixelInfo p;
1399 p.position.x = x;
1401 if (hitCount > 0)
1403 RCL_HitResult hit = hits[0];
1405 uint8_t goOn = 1;
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
1413 if (hitCount > 1)
1414 hit = hits[1];
1415 else
1416 goOn = 0;
1418 else
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);
1428 if (unrolled)
1430 goOn = 0;
1432 if (hitCount > 1) /* should probably always be true (hit on square
1433 exit) */
1435 if (hit.direction % 2 != hits[1].direction % 2)
1437 // hit on the inner side
1438 hit = hits[1];
1439 goOn = 1;
1441 else if (hitCount > 2)
1443 // hit on the opposite side
1444 hit = hits[2];
1445 goOn = 1;
1452 p.hit = hit;
1454 if (goOn)
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;
1472 else
1474 _RCL_makeInfiniteHit(&p.hit,&ray);
1477 // draw ceiling
1479 p.isWall = 0;
1480 p.isFloor = 0;
1481 p.isHorizon = 1;
1482 p.depth = 1;
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);
1488 // draw wall
1490 p.isWall = 1;
1491 p.isFloor = 1;
1492 p.depth = dist;
1493 p.height = 0;
1495 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1496 p.hit.textureCoord -= p.hit.doorRoll;
1497 #endif
1499 p.texCoords.x = p.hit.textureCoord;
1500 p.texCoords.y = 0;
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);
1508 // draw floor
1510 p.isWall = 0;
1512 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1513 p.depth = (_RCL_camera.resolution.y - y) * _RCL_horizontalDepthStep + 1;
1514 #endif
1516 _RCL_drawVertical(y,_RCL_camResYLimit,-1,_RCL_camResYLimit,
1517 _RCL_camera.height,1,RCL_COMPUTE_FLOOR_DEPTH,RCL_COMPUTE_FLOOR_TEXCOORDS,
1518 -1,&ray,&p);
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;
1543 _RCL_camera = cam;
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 ?
1559 ceilingHeightFunc(
1560 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1561 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height
1562 : RCL_INFINITY;
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
1570 #endif
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;
1581 _RCL_camera = cam;
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
1601 #endif
1603 RCL_castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc,
1604 _RCL_columnFunctionSimple, constraints);
1606 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1607 _RCL_floorPixelDistances = 0;
1608 #endif
1611 RCL_Vector2D RCL_normalize(RCL_Vector2D v)
1613 RCL_profileCall(RCL_normalize);
1615 RCL_Vector2D result;
1616 RCL_Unit l = RCL_len(v);
1617 l = RCL_nonZero(l);
1619 result.x = (v.x * RCL_UNITS_PER_SQUARE) / l;
1620 result.y = (v.y * RCL_UNITS_PER_SQUARE) / l;
1622 return result;
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,
1636 RCL_Camera camera)
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
1655 + camera.shear;
1657 RCL_Unit middleColumn = camera.resolution.x / 2;
1659 RCL_Unit a = RCL_sqrtInt(d * d - result.depth * result.depth);
1661 RCL_Ray r;
1662 r.start = camera.position;
1663 r.direction = cameraDir;
1665 if (!RCL_pointIfLeftOfRay(worldPosition,r))
1666 a *= -1;
1668 RCL_Unit cos = RCL_cosInt(RCL_HORIZONTAL_FOV_HALF);
1670 RCL_Unit b = (result.depth * RCL_sinInt(RCL_HORIZONTAL_FOV_HALF)) /
1671 RCL_nonZero(cos);
1672 // sin/cos = tan
1674 result.position.x = (a * middleColumn) / RCL_nonZero(b);
1675 result.position.x = middleColumn - result.position.x;
1677 return result;
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)
1692 : 0;
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)
1702 : RCL_INFINITY;
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;
1735 if (computeHeight)
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)\
1745 if (computeHeight)\
1747 RCL_Unit height = floorHeightFunc(s1,s2);\
1748 if (height > bottomLimit)\
1749 dir##Collides = 1;\
1750 else if (ceilingHeightFunc != 0)\
1752 height = ceilingHeightFunc(s1,s2);\
1753 if (height < topLimit)\
1754 dir##Collides = 1;\
1757 else\
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)\
1772 if (x)\
1773 collCheck(dir,dir##SquareNew,dir2##Square2)\
1774 else\
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)\
1786 if (dir##Collides)\
1787 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1788 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1789 dir##Dir;\
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)
1799 if (xyCollides)
1801 // normally should slide, but let's KISS
1802 cornerNew = corner;
1807 collHandle(x)
1808 collHandle(y)
1810 #undef collCheck
1811 #undef collHandle
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;
1837 RCL_Unit height;
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)
1849 checkSquares(2,1)
1851 if (ySquare2 != ySquare1)
1852 checkSquares(1,2)
1854 if (xSquare2 != xSquare1 && ySquare2 != ySquare1)
1855 checkSquares(2,2)
1857 camera->height = RCL_clamp(camera->height,
1858 bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW,
1859 topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE);
1861 #undef checkSquares
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;
1872 camera->shear = 0;
1873 camera->height = RCL_UNITS_PER_SQUARE;
1876 void RCL_initRayConstraints(RCL_RayConstraints *constraints)
1878 constraints->maxHits = 1;
1879 constraints->maxSteps = 20;
1882 #endif