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