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