Update README.md
[raycastlib.git] / raycastlib.h
blob37d34f178d9080b54bbaf3b2e77b9d1dd51d5062
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.906
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 / 5)
111 #endif
113 #define RCL_VERTICAL_FOV_TAN (RCL_VERTICAL_FOV * 4) ///< tan approximation
115 #ifndef RCL_HORIZONTAL_FOV
116 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
117 #endif
119 #define RCL_HORIZONTAL_FOV_TAN (RCL_VERTICAL_FOV * 4)
121 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
123 #ifndef RCL_CAMERA_COLL_RADIUS
124 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
125 #endif
127 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
128 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
129 #endif
131 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
132 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
133 #endif
135 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
136 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
137 #endif
139 #ifndef RCL_TEXTURE_INTERPOLATION_SCALE
140 #define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed
141 poit vertical texture coord
142 computation. This should be power
143 of two! Higher number can look more
144 accurate but may cause overflow. */
145 #endif
147 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
148 horizon has (the floor
149 depth is only
150 approximated with the
151 help of this
152 constant). */
153 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
154 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
155 difference when approximating floor/ceil
156 depth. */
157 #endif
159 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
160 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
161 #define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions.
162 #define RCL_zeroClamp(x) ((x) * ((x) >= 0))
163 #define RCL_likely(cond) __builtin_expect(!!(cond),1)
164 #define RCL_unlikely(cond) __builtin_expect(!!(cond),0)
166 #define RCL_logV2D(v)\
167 printf("[%d,%d]\n",v.x,v.y);
169 #define RCL_logRay(r){\
170 printf("ray:\n");\
171 printf(" start: ");\
172 RCL_logV2D(r.start);\
173 printf(" dir: ");\
174 RCL_logV2D(r.direction);}
176 #define RCL_logHitResult(h){\
177 printf("hit:\n");\
178 printf(" square: ");\
179 RCL_logV2D(h.square);\
180 printf(" pos: ");\
181 RCL_logV2D(h.position);\
182 printf(" dist: %d\n", h.distance);\
183 printf(" dir: %d\n", h.direction);\
184 printf(" texcoord: %d\n", h.textureCoord);}
186 #define RCL_logPixelInfo(p){\
187 printf("pixel:\n");\
188 printf(" position: ");\
189 RCL_logV2D(p.position);\
190 printf(" texCoord: ");\
191 RCL_logV2D(p.texCoords);\
192 printf(" depth: %d\n", p.depth);\
193 printf(" height: %d\n", p.height);\
194 printf(" wall: %d\n", p.isWall);\
195 printf(" hit: ");\
196 RCL_logHitResult(p.hit);\
199 #define RCL_logCamera(c){\
200 printf("camera:\n");\
201 printf(" position: ");\
202 RCL_logV2D(c.position);\
203 printf(" height: %d\n",c.height);\
204 printf(" direction: %d\n",c.direction);\
205 printf(" shear: %d\n",c.shear);\
206 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
209 /// Position in 2D space.
210 typedef struct
212 RCL_Unit x;
213 RCL_Unit y;
214 } RCL_Vector2D;
216 typedef struct
218 RCL_Vector2D start;
219 RCL_Vector2D direction;
220 } RCL_Ray;
222 typedef struct
224 RCL_Unit distance; /**< Distance to the hit position, or -1 if no
225 collision happened. If RCL_RECTILINEAR != 0, then
226 the distance is perpendicular to the projection
227 plane (fish eye correction), otherwise it is
228 the straight distance to the ray start
229 position. */
230 uint8_t direction; /**< Direction of hit. The convention for angle
231 units is explained above. */
232 RCL_Unit textureCoord; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
233 texture coordinate (horizontal). */
234 RCL_Vector2D square; ///< Collided square coordinates.
235 RCL_Vector2D position; ///< Exact collision position in RCL_Units.
236 RCL_Unit arrayValue; /** Value returned by array function (most often
237 this will be the floor height). */
238 RCL_Unit type; /**< Integer identifying type of square (number
239 returned by type function, e.g. texture
240 index).*/
241 RCL_Unit doorRoll; ///< Holds value of door roll.
242 } RCL_HitResult;
244 typedef struct
246 RCL_Vector2D position;
247 RCL_Unit direction; // TODO: rename to "angle" to keep consistency
248 RCL_Vector2D resolution;
249 int16_t shear; /**< Shear offset in pixels (0 => no shear), can simulate
250 looking up/down. */
251 RCL_Unit height;
252 } RCL_Camera;
255 Holds an information about a single rendered pixel (for a pixel function
256 that works as a fragment shader).
258 typedef struct
260 RCL_Vector2D position; ///< On-screen position.
261 int8_t isWall; ///< Whether the pixel is a wall or a floor/ceiling.
262 int8_t isFloor; ///< Whether the pixel is floor or ceiling.
263 int8_t isHorizon; ///< If the pixel belongs to horizon segment.
264 RCL_Unit depth; ///< Corrected depth.
265 RCL_Unit wallHeight;///< Only for wall pixels, says its height.
266 RCL_Unit height; ///< World height (mostly for floor).
267 RCL_HitResult hit; ///< Corresponding ray hit.
268 RCL_Vector2D texCoords; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
269 texture coordinates. */
270 } RCL_PixelInfo;
272 void RCL_PIXEL_FUNCTION (RCL_PixelInfo *pixel);
274 typedef struct
276 uint16_t maxHits;
277 uint16_t maxSteps;
278 } RCL_RayConstraints;
281 Function used to retrieve some information about cells of the rendered scene.
282 It should return a characteristic of given square as an integer (e.g. square
283 height, texture index, ...) - between squares that return different numbers
284 there is considered to be a collision.
286 This function should be as fast as possible as it will typically be called
287 very often.
289 typedef RCL_Unit (*RCL_ArrayFunction)(int16_t x, int16_t y);
291 TODO: maybe array functions should be replaced by defines of funtion names
292 like with pixelFunc? Could be more efficient than function pointers.
296 Function that renders a single pixel at the display. It is handed an info
297 about the pixel it should draw.
299 This function should be as fast as possible as it will typically be called
300 very often.
302 typedef void (*RCL_PixelFunction)(RCL_PixelInfo *info);
304 typedef void
305 (*RCL_ColumnFunction)(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
306 RCL_Ray ray);
309 Simple-interface function to cast a single ray.
311 @return The first collision result.
313 RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc);
316 Casts a 3D ray in 3D environment with floor and optional ceiling
317 (ceilingHeightFunc can be 0). This can be useful for hitscan shooting,
318 visibility checking etc.
320 @return normalized ditance (0 to RCL_UNITS_PER_SQUARE) along the ray at which
321 the environment was hit, RCL_UNITS_PER_SQUARE means nothing was hit
323 RCL_Unit RCL_castRay3D(
324 RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2,
325 RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc,
326 RCL_RayConstraints constraints);
329 Maps a single point in the world to the screen (2D position + depth).
331 RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
332 RCL_Camera camera);
335 Casts a single ray and returns a list of collisions.
337 @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
338 distance is divided by the ray direction vector length (to correct
339 the fish eye effect)
340 @param arrayFunc function that will be used to determine collisions (hits)
341 with the ray (squares for which this function returns different values
342 are considered to have a collision between them), this will typically
343 be a function returning floor height
344 @param typeFunc optional (can be 0) function - if provided, it will be used
345 to mark the hit result with the number returned by this function
346 (it can be e.g. a texture index)
347 @param hitResults array in which the hit results will be stored (has to be
348 preallocated with at space for at least as many hit results as
349 maxHits specified with the constraints parameter)
350 @param hitResultsLen in this variable the number of hit results will be
351 returned
352 @param constraints specifies constraints for the ray cast
354 void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
355 RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
356 uint16_t *hitResultsLen, RCL_RayConstraints constraints);
358 RCL_Vector2D RCL_angleToDirection(RCL_Unit angle);
361 Cos function.
363 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
364 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
365 RCL_UNITS_PER_SQUARE)
367 RCL_Unit RCL_cos(RCL_Unit input);
369 RCL_Unit RCL_sin(RCL_Unit input);
371 RCL_Unit RCL_tan(RCL_Unit input);
373 RCL_Unit RCL_ctg(RCL_Unit input);
375 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
376 RCL_Vector2D RCL_normalize(RCL_Vector2D v);
378 /// Computes a cos of an angle between two vectors.
379 RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2);
381 uint16_t RCL_sqrt(RCL_Unit value);
382 RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2);
383 RCL_Unit RCL_len(RCL_Vector2D v);
386 Converts an angle in whole degrees to an angle in RCL_Units that this library
387 uses.
389 RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees);
391 ///< Computes the change in size of an object due to perspective (vertical FOV).
392 RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance);
394 RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize,
395 RCL_Unit scaledSize);
397 RCL_Unit
398 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance);
400 RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize,
401 RCL_Unit scaledSize);
404 Casts rays for given camera view and for each hit calls a user provided
405 function.
407 void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
408 RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
409 RCL_RayConstraints constraints);
412 Using provided functions, renders a complete complex (multilevel) camera
413 view.
415 This function should render each screen pixel exactly once.
417 function rendering summary:
418 - performance: slower
419 - accuracy: higher
420 - wall textures: yes
421 - different wall heights: yes
422 - floor/ceiling textures: no
423 - floor geometry: yes, multilevel
424 - ceiling geometry: yes (optional), multilevel
425 - rolling door: no
426 - camera shearing: yes
427 - rendering order: left-to-right, not specifically ordered vertically
429 @param cam camera whose view to render
430 @param floorHeightFunc function that returns floor height (in RCL_Units)
431 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
432 0 (no ceiling will be rendered)
433 @param typeFunction function that says a type of square (e.g. its texture
434 index), can be 0 (no type in hit result)
435 @param pixelFunc callback function to draw a single pixel on screen
436 @param constraints constraints for each cast ray
438 void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
439 RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
440 RCL_RayConstraints constraints);
443 Renders given camera view, with help of provided functions. This function is
444 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
445 flat levels.
447 function rendering summary:
448 - performance: faster
449 - accuracy: lower
450 - wall textures: yes
451 - different wall heights: yes
452 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
453 - floor geometry: no (just flat floor, with depth information)
454 - ceiling geometry: no (just flat ceiling, with depth information)
455 - rolling door: yes
456 - camera shearing: no
457 - rendering order: left-to-right, top-to-bottom
459 Additionally this function supports rendering rolling doors.
461 This function should render each screen pixel exactly once.
463 @param rollFunc function that for given square says its door roll in
464 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
465 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
466 rendering should also be faster as fewer intersections will be tested)
468 void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
469 RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
470 RCL_RayConstraints constraints);
473 Function that moves given camera and makes it collide with walls and
474 potentially also floor and ceilings. It's meant to help implement player
475 movement.
477 @param camera camera to move
478 @param planeOffset offset to move the camera in
479 @param heightOffset height offset to move the camera in
480 @param floorHeightFunc function used to retrieve the floor height
481 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
482 (camera won't collide with ceiling)
483 @param computeHeight whether to compute height - if false (0), floor and
484 ceiling functions won't be used and the camera will
485 only collide horizontally with walls (good for simpler
486 game, also faster)
487 @param force if true, forces to recompute collision even if position doesn't
488 change
490 void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
491 RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
492 RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force);
494 void RCL_initCamera(RCL_Camera *camera);
495 void RCL_initRayConstraints(RCL_RayConstraints *constraints);
497 //=============================================================================
498 // privates
500 #define _RCL_UNUSED(what) (void)(what);
502 // global helper variables, for precomputing stuff etc.
503 RCL_Camera _RCL_camera;
504 RCL_Unit _RCL_horizontalDepthStep = 0;
505 RCL_Unit _RCL_startFloorHeight = 0;
506 RCL_Unit _RCL_startCeil_Height = 0;
507 RCL_Unit _RCL_camResYLimit = 0;
508 RCL_Unit _RCL_middleRow = 0;
509 RCL_ArrayFunction _RCL_floorFunction = 0;
510 RCL_ArrayFunction _RCL_ceilFunction = 0;
511 RCL_Unit _RCL_fHorizontalDepthStart = 0;
512 RCL_Unit _RCL_cHorizontalDepthStart = 0;
513 int16_t _RCL_cameraHeightScreen = 0;
514 RCL_ArrayFunction _RCL_rollFunction = 0; // says door rolling
515 RCL_Unit *_RCL_floorPixelDistances = 0;
517 RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax)
519 if (value >= valueMin)
521 if (value <= valueMax)
522 return value;
523 else
524 return valueMax;
526 else
527 return valueMin;
530 static inline RCL_Unit RCL_abs(RCL_Unit value)
532 return value * (((value >= 0) << 1) - 1);
535 /// Like mod, but behaves differently for negative values.
536 static inline RCL_Unit RCL_wrap(RCL_Unit value, RCL_Unit mod)
538 RCL_Unit cmp = value < 0;
539 return cmp * mod + (value % mod) - cmp;
542 /// Performs division, rounding down, NOT towards zero.
543 static inline RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor)
545 return value / divisor - ((value >= 0) ? 0 : 1);
548 // Bhaskara's cosine approximation formula
549 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
550 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
551 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
553 #if RCL_USE_COS_LUT == 1
555 #ifdef RCL_RAYCAST_TINY
556 const RCL_Unit cosLUT[64] =
558 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
560 #else
561 const RCL_Unit cosLUT[64] =
563 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
564 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
565 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
566 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
568 #endif
570 #elif RCL_USE_COS_LUT == 2
571 const RCL_Unit cosLUT[128] =
573 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
574 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
575 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
576 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
577 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
578 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
579 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
580 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
582 #endif
584 RCL_Unit RCL_cos(RCL_Unit input)
586 input = RCL_wrap(input,RCL_UNITS_PER_SQUARE);
588 #if RCL_USE_COS_LUT == 1
590 #ifdef RCL_RAYCAST_TINY
591 return cosLUT[input];
592 #else
593 return cosLUT[input / 16];
594 #endif
596 #elif RCL_USE_COS_LUT == 2
597 return cosLUT[input / 8];
598 #else
599 if (input < RCL_UNITS_PER_SQUARE / 4)
600 return trigHelper(input);
601 else if (input < RCL_UNITS_PER_SQUARE / 2)
602 return -1 * trigHelper(RCL_UNITS_PER_SQUARE / 2 - input);
603 else if (input < 3 * RCL_UNITS_PER_SQUARE / 4)
604 return -1 * trigHelper(input - RCL_UNITS_PER_SQUARE / 2);
605 else
606 return trigHelper(RCL_UNITS_PER_SQUARE - input);
607 #endif
610 #undef trigHelper
612 RCL_Unit RCL_sin(RCL_Unit input)
614 return RCL_cos(input - RCL_UNITS_PER_SQUARE / 4);
617 RCL_Unit RCL_tan(RCL_Unit input)
619 return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_cos(input);
622 RCL_Unit RCL_ctg(RCL_Unit input)
624 return (RCL_cos(input) * RCL_UNITS_PER_SQUARE) / RCL_sin(input);
627 RCL_Vector2D RCL_angleToDirection(RCL_Unit angle)
629 RCL_Vector2D result;
631 result.x = RCL_cos(angle);
632 result.y = -1 * RCL_sin(angle);
634 return result;
637 uint16_t RCL_sqrt(RCL_Unit value)
639 #ifdef RCL_RAYCAST_TINY
640 uint16_t result = 0;
641 uint16_t a = value;
642 uint16_t b = 1u << 14;
643 #else
644 uint32_t result = 0;
645 uint32_t a = value;
646 uint32_t b = 1u << 30;
647 #endif
649 while (b > a)
650 b >>= 2;
652 while (b != 0)
654 if (a >= result + b)
656 a -= result + b;
657 result = result + 2 * b;
660 b >>= 2;
661 result >>= 1;
664 return result;
667 RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2)
669 RCL_Unit dx = p2.x - p1.x;
670 RCL_Unit dy = p2.y - p1.y;
672 #if RCL_USE_DIST_APPROX == 2
673 // octagonal approximation
675 dx = RCL_abs(dx);
676 dy = RCL_abs(dy);
678 return dy > dx ? dx / 2 + dy : dy / 2 + dx;
679 #elif RCL_USE_DIST_APPROX == 1
680 // more accurate approximation
682 RCL_Unit a, b, result;
684 dx = ((dx < 0) * 2 - 1) * dx;
685 dy = ((dy < 0) * 2 - 1) * dy;
687 if (dx < dy)
689 a = dy;
690 b = dx;
692 else
694 a = dx;
695 b = dy;
698 result = a + (44 * b) / 102;
700 if (a < (b << 4))
701 result -= (5 * a) / 128;
703 return result;
704 #else
705 dx = dx * dx;
706 dy = dy * dy;
708 return RCL_sqrt((RCL_Unit) (dx + dy));
709 #endif
712 RCL_Unit RCL_len(RCL_Vector2D v)
714 RCL_Vector2D zero;
715 zero.x = 0;
716 zero.y = 0;
718 return RCL_dist(zero,v);
721 static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point, RCL_Ray ray)
723 RCL_Unit dX = point.x - ray.start.x;
724 RCL_Unit dY = point.y - ray.start.y;
725 return (ray.direction.x * dY - ray.direction.y * dX) > 0;
726 // ^ Z component of cross-product
729 void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
730 RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
731 uint16_t *hitResultsLen, RCL_RayConstraints constraints)
733 RCL_Vector2D currentPos = ray.start;
734 RCL_Vector2D currentSquare;
736 currentSquare.x = RCL_divRoundDown(ray.start.x,RCL_UNITS_PER_SQUARE);
737 currentSquare.y = RCL_divRoundDown(ray.start.y,RCL_UNITS_PER_SQUARE);
739 *hitResultsLen = 0;
741 RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
743 // DDA variables
744 RCL_Vector2D nextSideDist; // dist. from start to the next side in given axis
745 RCL_Vector2D delta;
746 RCL_Vector2D step; // -1 or 1 for each axis
747 int8_t stepHorizontal = 0; // whether the last step was hor. or vert.
749 nextSideDist.x = 0;
750 nextSideDist.y = 0;
752 RCL_Unit dirVecLengthNorm = RCL_len(ray.direction) * RCL_UNITS_PER_SQUARE;
754 delta.x = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.x));
755 delta.y = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.y));
757 // init DDA
759 if (ray.direction.x < 0)
761 step.x = -1;
762 nextSideDist.x = (RCL_wrap(ray.start.x,RCL_UNITS_PER_SQUARE) * delta.x) /
763 RCL_UNITS_PER_SQUARE;
765 else
767 step.x = 1;
768 nextSideDist.x =
769 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.x,RCL_UNITS_PER_SQUARE)) *
770 delta.x) / RCL_UNITS_PER_SQUARE;
773 if (ray.direction.y < 0)
775 step.y = -1;
776 nextSideDist.y = (RCL_wrap(ray.start.y,RCL_UNITS_PER_SQUARE) * delta.y) /
777 RCL_UNITS_PER_SQUARE;
779 else
781 step.y = 1;
782 nextSideDist.y =
783 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.y,RCL_UNITS_PER_SQUARE)) *
784 delta.y) / RCL_UNITS_PER_SQUARE;
787 // DDA loop
789 #define RECIP_SCALE 65536
791 RCL_Unit rayDirXRecip = RECIP_SCALE / RCL_nonZero(ray.direction.x);
792 RCL_Unit rayDirYRecip = RECIP_SCALE / RCL_nonZero(ray.direction.y);
793 // ^ we precompute reciprocals to avoid divisions in the loop
795 for (uint16_t i = 0; i < constraints.maxSteps; ++i)
797 RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y);
799 if (RCL_unlikely(currentType != squareType))
801 // collision
803 RCL_HitResult h;
805 h.arrayValue = currentType;
806 h.doorRoll = 0;
807 h.position = currentPos;
808 h.square = currentSquare;
810 if (stepHorizontal)
812 h.position.x = currentSquare.x * RCL_UNITS_PER_SQUARE;
813 h.direction = 3;
815 if (step.x == -1)
817 h.direction = 1;
818 h.position.x += RCL_UNITS_PER_SQUARE;
821 RCL_Unit diff = h.position.x - ray.start.x;
823 h.position.y = // avoid division by multiplying with reciprocal
824 ray.start.y + (ray.direction.y * diff * rayDirXRecip) / RECIP_SCALE;
826 #if RCL_RECTILINEAR
827 /* Here we compute the fish eye corrected distance (perpendicular to
828 the projection plane) as the Euclidean distance divided by the length
829 of the ray direction vector. This can be computed without actually
830 computing Euclidean distances as a hypothenuse A (distance) divided
831 by hypothenuse B (length) is equal to leg A (distance along one axis)
832 divided by leg B (length along the same axis). */
834 h.distance =
835 (((h.position.x - ray.start.x) / 4) *
836 RCL_UNITS_PER_SQUARE * rayDirXRecip)
837 / (RECIP_SCALE / 4);
839 // ^ / 4 is here to prevent overflow
840 #endif
842 else
844 h.position.y = currentSquare.y * RCL_UNITS_PER_SQUARE;
845 h.direction = 2;
847 if (step.y == -1)
849 h.direction = 0;
850 h.position.y += RCL_UNITS_PER_SQUARE;
853 RCL_Unit diff = h.position.y - ray.start.y;
855 h.position.x =
856 ray.start.x + (ray.direction.x * diff * rayDirYRecip) / RECIP_SCALE;
858 #if RCL_RECTILINEAR
859 h.distance =
860 (((h.position.y - ray.start.y) / 4) *
861 RCL_UNITS_PER_SQUARE * rayDirYRecip)
862 / (RECIP_SCALE / 4);
864 // ^ / 4 is here to prevent overflow
865 #endif
868 #if !RCL_RECTILINEAR
869 h.distance = RCL_dist(h.position,ray.start);
870 #endif
871 if (typeFunc != 0)
872 h.type = typeFunc(currentSquare.x,currentSquare.y);
874 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
875 switch (h.direction)
877 case 0: h.textureCoord =
878 RCL_wrap(-1 * h.position.x,RCL_UNITS_PER_SQUARE); break;
880 case 1: h.textureCoord =
881 RCL_wrap(h.position.y,RCL_UNITS_PER_SQUARE); break;
883 case 2: h.textureCoord =
884 RCL_wrap(h.position.x,RCL_UNITS_PER_SQUARE); break;
886 case 3: h.textureCoord =
887 RCL_wrap(-1 * h.position.y,RCL_UNITS_PER_SQUARE); break;
889 default: h.textureCoord = 0; break;
892 if (_RCL_rollFunction != 0)
894 h.doorRoll = _RCL_rollFunction(currentSquare.x,currentSquare.y);
896 if (h.direction == 0 || h.direction == 1)
897 h.doorRoll *= -1;
900 #else
901 h.textureCoord = 0;
902 #endif
904 hitResults[*hitResultsLen] = h;
906 *hitResultsLen += 1;
908 squareType = currentType;
910 if (*hitResultsLen >= constraints.maxHits)
911 break;
914 // DDA step
916 if (nextSideDist.x < nextSideDist.y)
918 nextSideDist.x += delta.x;
919 currentSquare.x += step.x;
920 stepHorizontal = 1;
922 else
924 nextSideDist.y += delta.y;
925 currentSquare.y += step.y;
926 stepHorizontal = 0;
931 RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc)
933 RCL_HitResult result;
934 uint16_t RCL_len;
935 RCL_RayConstraints c;
937 c.maxSteps = 1000;
938 c.maxHits = 1;
940 RCL_castRayMultiHit(ray,arrayFunc,0,&result,&RCL_len,c);
942 if (RCL_len == 0)
943 result.distance = -1;
945 return result;
948 void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
949 RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
950 RCL_RayConstraints constraints)
952 RCL_Vector2D dir1 =
953 RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF);
955 RCL_Vector2D dir2 =
956 RCL_angleToDirection(cam.direction + RCL_HORIZONTAL_FOV_HALF);
958 /* We scale the side distances so that the middle one is
959 RCL_UNITS_PER_SQUARE, which has to be this way. */
961 RCL_Unit cos = RCL_nonZero(RCL_cos(RCL_HORIZONTAL_FOV_HALF));
963 dir1.x = (dir1.x * RCL_UNITS_PER_SQUARE) / cos;
964 dir1.y = (dir1.y * RCL_UNITS_PER_SQUARE) / cos;
966 dir2.x = (dir2.x * RCL_UNITS_PER_SQUARE) / cos;
967 dir2.y = (dir2.y * RCL_UNITS_PER_SQUARE) / cos;
969 RCL_Unit dX = dir2.x - dir1.x;
970 RCL_Unit dY = dir2.y - dir1.y;
972 RCL_HitResult hits[constraints.maxHits];
973 uint16_t hitCount;
975 RCL_Ray r;
976 r.start = cam.position;
978 RCL_Unit currentDX = 0;
979 RCL_Unit currentDY = 0;
981 for (int16_t i = 0; i < cam.resolution.x; ++i)
983 /* Here by linearly interpolating the direction vector its length changes,
984 which in result achieves correcting the fish eye effect (computing
985 perpendicular distance). */
987 r.direction.x = dir1.x + currentDX / cam.resolution.x;
988 r.direction.y = dir1.y + currentDY / cam.resolution.x;
990 RCL_castRayMultiHit(r,arrayFunc,typeFunction,hits,&hitCount,constraints);
992 columnFunc(hits,hitCount,i,r);
994 currentDX += dX;
995 currentDY += dY;
1000 Helper function that determines intersection with both ceiling and floor.
1002 RCL_Unit _RCL_floorCeilFunction(int16_t x, int16_t y)
1004 RCL_Unit f = _RCL_floorFunction(x,y);
1006 if (_RCL_ceilFunction == 0)
1007 return f;
1009 RCL_Unit c = _RCL_ceilFunction(x,y);
1011 #ifndef RCL_RAYCAST_TINY
1012 return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff);
1013 #else
1014 return ((f & 0x00ff) << 8) | (c & 0x00ff);
1015 #endif
1018 RCL_Unit _floorHeightNotZeroFunction(int16_t x, int16_t y)
1020 return _RCL_floorFunction(x,y) == 0 ? 0 :
1021 RCL_nonZero((x & 0x00FF) | ((y & 0x00FF) << 8));
1022 // ^ this makes collisions between all squares - needed for rolling doors
1025 RCL_Unit RCL_adjustDistance(RCL_Unit distance, RCL_Camera *camera,
1026 RCL_Ray *ray)
1028 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1029 possibly be computed more efficiently by not computing Euclidean
1030 distance at all, but rather compute the distance of the collision
1031 point from the projection plane (line). */
1033 RCL_Unit result =
1034 (distance *
1035 RCL_vectorsAngleCos(RCL_angleToDirection(camera->direction),
1036 ray->direction)) / RCL_UNITS_PER_SQUARE;
1038 return RCL_nonZero(result);
1039 // ^ prevent division by zero
1042 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1043 static inline int16_t _RCL_drawHorizontalColumn(
1044 RCL_Unit yCurrent,
1045 RCL_Unit yTo,
1046 RCL_Unit limit1, // TODO: int16_t?
1047 RCL_Unit limit2,
1048 RCL_Unit verticalOffset,
1049 int16_t increment,
1050 int8_t computeDepth,
1051 int8_t computeCoords,
1052 int16_t depthIncrementMultiplier,
1053 RCL_Ray *ray,
1054 RCL_PixelInfo *pixelInfo
1057 _RCL_UNUSED(ray);
1059 RCL_Unit depthIncrement;
1060 RCL_Unit dx;
1061 RCL_Unit dy;
1063 pixelInfo->isWall = 0;
1065 int16_t limit = RCL_clamp(yTo,limit1,limit2);
1067 RCL_Unit depth = 0; /* TODO: this is for clamping depth to 0 so that we don't
1068 have negative depths, but we should do it more
1069 elegantly and efficiently */
1071 _RCL_UNUSED(depth);
1073 /* for performance reasons have different version of the critical loop
1074 to be able to branch early */
1075 #define loop(doDepth,doCoords)\
1077 if (doDepth) /*constant condition - compiler should optimize it out*/\
1079 depth = pixelInfo->depth + RCL_abs(verticalOffset) *\
1080 RCL_VERTICAL_DEPTH_MULTIPLY;\
1081 depthIncrement = depthIncrementMultiplier *\
1082 _RCL_horizontalDepthStep;\
1084 if (doCoords) /*constant condition - compiler should optimize it out*/\
1086 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1087 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1089 for (int16_t i = yCurrent + increment;\
1090 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1091 i += increment)\
1093 pixelInfo->position.y = i;\
1094 if (doDepth) /*constant condition - compiler should optimize it out*/\
1096 depth += depthIncrement;\
1097 pixelInfo->depth = RCL_zeroClamp(depth); \
1098 /* ^ int comparison is fast, it is not braching! (= test instr.) */\
1100 if (doCoords) /*constant condition - compiler should optimize it out*/\
1102 RCL_Unit d = _RCL_floorPixelDistances[i];\
1103 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1104 pixelInfo->texCoords.x =\
1105 _RCL_camera.position.x + ((d * dx) / d2);\
1106 pixelInfo->texCoords.y =\
1107 _RCL_camera.position.y + ((d * dy) / d2);\
1109 RCL_PIXEL_FUNCTION(pixelInfo);\
1113 if (computeDepth) // branch early
1115 if (!computeCoords)
1116 loop(1,0)
1117 else
1118 loop(1,1)
1120 else
1122 if (!computeCoords)
1123 loop(0,0)
1124 else
1125 loop(1,1)
1128 #undef loop
1130 return limit;
1133 /// Helper for drawing walls. Returns the last drawn pixel position.
1134 static inline int16_t _RCL_drawWall(
1135 RCL_Unit yCurrent,
1136 RCL_Unit yFrom,
1137 RCL_Unit yTo,
1138 RCL_Unit limit1, // TODO: int16_t?
1139 RCL_Unit limit2,
1140 RCL_Unit height,
1141 int16_t increment,
1142 RCL_PixelInfo *pixelInfo
1145 _RCL_UNUSED(height)
1147 height = RCL_abs(height);
1149 pixelInfo->isWall = 1;
1151 RCL_Unit limit = RCL_clamp(yTo,limit1,limit2);
1153 RCL_Unit wallLength = RCL_nonZero(RCL_abs(yTo - yFrom - 1));
1155 RCL_Unit wallPosition = RCL_abs(yFrom - yCurrent) - increment;
1157 RCL_Unit heightScaled = height * RCL_TEXTURE_INTERPOLATION_SCALE;
1158 _RCL_UNUSED(heightScaled);
1160 RCL_Unit coordStepScaled = RCL_COMPUTE_WALL_TEXCOORDS ?
1161 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1162 ((RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE) / wallLength)
1163 #else
1164 (heightScaled / wallLength)
1165 #endif
1166 : 0;
1168 pixelInfo->texCoords.y = RCL_COMPUTE_WALL_TEXCOORDS ?
1169 (wallPosition * coordStepScaled) : 0;
1171 if (increment < 0)
1173 coordStepScaled *= -1;
1174 pixelInfo->texCoords.y =
1175 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1176 (RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE)
1177 - pixelInfo->texCoords.y;
1178 #else
1179 heightScaled - pixelInfo->texCoords.y;
1180 #endif
1182 else
1184 // with floor wall, don't start under 0
1185 pixelInfo->texCoords.y = RCL_zeroClamp(pixelInfo->texCoords.y);
1188 RCL_Unit textureCoordScaled = pixelInfo->texCoords.y;
1190 for (RCL_Unit i = yCurrent + increment;
1191 increment == -1 ? i >= limit : i <= limit; // TODO: is efficient?
1192 i += increment)
1194 pixelInfo->position.y = i;
1196 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1197 pixelInfo->texCoords.y =
1198 textureCoordScaled / RCL_TEXTURE_INTERPOLATION_SCALE;
1200 textureCoordScaled += coordStepScaled;
1201 #endif
1203 RCL_PIXEL_FUNCTION(pixelInfo);
1206 return limit;
1209 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1210 static inline void _RCL_makeInfiniteHit(RCL_HitResult *hit, RCL_Ray *ray)
1212 hit->distance = RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE;
1213 /* ^ horizon is at infinity, but we can't use too big infinity
1214 (RCL_INFINITY) because it would overflow in the following mult. */
1215 hit->position.x = (ray->direction.x * hit->distance) / RCL_UNITS_PER_SQUARE;
1216 hit->position.y = (ray->direction.y * hit->distance) / RCL_UNITS_PER_SQUARE;
1218 hit->direction = 0;
1219 hit->textureCoord = 0;
1220 hit->arrayValue = 0;
1221 hit->doorRoll = 0;
1222 hit->type = 0;
1225 void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
1226 RCL_Ray ray)
1228 // last written Y position, can never go backwards
1229 RCL_Unit fPosY = _RCL_camera.resolution.y;
1230 RCL_Unit cPosY = -1;
1232 // world coordinates (relative to camera height though)
1233 RCL_Unit fZ1World = _RCL_startFloorHeight;
1234 RCL_Unit cZ1World = _RCL_startCeil_Height;
1236 RCL_PixelInfo p;
1237 p.position.x = x;
1238 p.height = 0;
1239 p.wallHeight = 0;
1240 p.texCoords.x = 0;
1241 p.texCoords.y = 0;
1243 // we'll be simulatenously drawing the floor and the ceiling now
1244 for (RCL_Unit j = 0; j <= hitCount; ++j)
1245 { // ^ = add extra iteration for horizon plane
1246 int8_t drawingHorizon = j == hitCount;
1248 RCL_HitResult hit;
1249 RCL_Unit distance = 1;
1251 RCL_Unit fWallHeight = 0, cWallHeight = 0;
1252 RCL_Unit fZ2World = 0, cZ2World = 0;
1253 RCL_Unit fZ1Screen = 0, cZ1Screen = 0;
1254 RCL_Unit fZ2Screen = 0, cZ2Screen = 0;
1256 if (!drawingHorizon)
1258 hit = hits[j];
1259 distance = RCL_nonZero(hit.distance);
1260 p.hit = hit;
1262 fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y);
1263 fZ2World = fWallHeight - _RCL_camera.height;
1264 fZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1265 (fZ1World * _RCL_camera.resolution.y) /
1266 RCL_UNITS_PER_SQUARE,distance);
1267 fZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1268 (fZ2World * _RCL_camera.resolution.y) /
1269 RCL_UNITS_PER_SQUARE,distance);
1271 if (_RCL_ceilFunction != 0)
1273 cWallHeight = _RCL_ceilFunction(hit.square.x,hit.square.y);
1274 cZ2World = cWallHeight - _RCL_camera.height;
1275 cZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1276 (cZ1World * _RCL_camera.resolution.y) /
1277 RCL_UNITS_PER_SQUARE,distance);
1278 cZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1279 (cZ2World * _RCL_camera.resolution.y) /
1280 RCL_UNITS_PER_SQUARE,distance);
1283 else
1285 fZ1Screen = _RCL_middleRow;
1286 cZ1Screen = _RCL_middleRow + 1;
1287 _RCL_makeInfiniteHit(&p.hit,&ray);
1290 RCL_Unit limit;
1292 p.isWall = 0;
1293 p.isHorizon = drawingHorizon;
1295 // draw floor until wall
1296 p.isFloor = 1;
1297 p.height = fZ1World + _RCL_camera.height;
1298 p.wallHeight = 0;
1300 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1301 p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep;
1302 #else
1303 p.depth = 0;
1304 #endif
1306 limit = _RCL_drawHorizontalColumn(fPosY,fZ1Screen,cPosY + 1,
1307 _RCL_camera.resolution.y,fZ1World,-1,RCL_COMPUTE_FLOOR_DEPTH,
1308 // ^ purposfully allow outside screen bounds
1309 RCL_COMPUTE_FLOOR_TEXCOORDS && p.height == RCL_FLOOR_TEXCOORDS_HEIGHT,
1310 1,&ray,&p);
1312 if (fPosY > limit)
1313 fPosY = limit;
1315 if (_RCL_ceilFunction != 0 || drawingHorizon)
1317 // draw ceiling until wall
1318 p.isFloor = 0;
1319 p.height = cZ1World + _RCL_camera.height;
1321 #if RCL_COMPUTE_CEILING_DEPTH == 1
1322 p.depth = (cPosY - _RCL_cHorizontalDepthStart) *
1323 _RCL_horizontalDepthStep;
1324 #endif
1326 limit = _RCL_drawHorizontalColumn(cPosY,cZ1Screen,
1327 -1,fPosY - 1,cZ1World,1,RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p);
1328 // ^ purposfully allow outside screen bounds here
1330 if (cPosY < limit)
1331 cPosY = limit;
1334 if (!drawingHorizon) // don't draw walls for horizon plane
1336 p.isWall = 1;
1337 p.depth = distance;
1338 p.isFloor = 1;
1339 p.texCoords.x = hit.textureCoord;
1340 p.height = fZ1World + _RCL_camera.height;
1341 p.wallHeight = fWallHeight;
1343 // draw floor wall
1345 if (fPosY > 0) // still pixels left?
1347 p.isFloor = 1;
1349 limit = _RCL_drawWall(fPosY,fZ1Screen,fZ2Screen,cPosY + 1,
1350 _RCL_camera.resolution.y,
1351 // ^ purposfully allow outside screen bounds here
1352 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1353 RCL_UNITS_PER_SQUARE
1354 #else
1355 fZ2World - fZ1World
1356 #endif
1357 ,-1,&p);
1360 if (fPosY > limit)
1361 fPosY = limit;
1363 fZ1World = fZ2World; // for the next iteration
1364 } // ^ purposfully allow outside screen bounds here
1366 // draw ceiling wall
1368 if (_RCL_ceilFunction != 0 && cPosY < _RCL_camResYLimit) // pixels left?
1370 p.isFloor = 0;
1371 p.height = cZ1World + _RCL_camera.height;
1372 p.wallHeight = cWallHeight;
1374 limit = _RCL_drawWall(cPosY,cZ1Screen,cZ2Screen,
1375 -1,fPosY - 1,
1376 // ^ puposfully allow outside screen bounds here
1377 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1378 RCL_UNITS_PER_SQUARE
1379 #else
1380 cZ1World - cZ2World
1381 #endif
1382 ,1,&p);
1384 if (cPosY < limit)
1385 cPosY = limit;
1387 cZ1World = cZ2World; // for the next iteration
1388 } // ^ puposfully allow outside screen bounds here
1393 void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount,
1394 uint16_t x, RCL_Ray ray)
1396 RCL_Unit y = 0;
1397 RCL_Unit wallHeightScreen = 0;
1398 RCL_Unit wallStart = _RCL_middleRow;
1400 RCL_Unit dist = 1;
1402 RCL_PixelInfo p;
1403 p.position.x = x;
1404 p.wallHeight = RCL_UNITS_PER_SQUARE;
1406 if (hitCount > 0)
1408 RCL_HitResult hit = hits[0];
1410 uint8_t goOn = 1;
1412 if (_RCL_rollFunction != 0 && RCL_COMPUTE_WALL_TEXCOORDS == 1)
1414 if (hit.arrayValue == 0)
1416 // standing inside door square, looking out => move to the next hit
1418 if (hitCount > 1)
1419 hit = hits[1];
1420 else
1421 goOn = 0;
1423 else
1425 // normal hit, check the door roll
1427 RCL_Unit texCoordMod = hit.textureCoord % RCL_UNITS_PER_SQUARE;
1429 int8_t unrolled = hit.doorRoll >= 0 ?
1430 (hit.doorRoll > texCoordMod) :
1431 (texCoordMod > RCL_UNITS_PER_SQUARE + hit.doorRoll);
1433 if (unrolled)
1435 goOn = 0;
1437 if (hitCount > 1) /* should probably always be true (hit on square
1438 exit) */
1440 if (hit.direction % 2 != hits[1].direction % 2)
1442 // hit on the inner side
1443 hit = hits[1];
1444 goOn = 1;
1446 else if (hitCount > 2)
1448 // hit on the opposite side
1449 hit = hits[2];
1450 goOn = 1;
1457 p.hit = hit;
1459 if (goOn)
1461 dist = hit.distance;
1463 RCL_Unit wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y);
1465 if (wallHeightWorld < 0)
1467 /* We can't just do wallHeightWorld = max(0,wallHeightWorld) because
1468 we would be processing an actual hit with height 0, which shouldn't
1469 ever happen, so we assign some arbitrary height. */
1471 wallHeightWorld = RCL_UNITS_PER_SQUARE;
1474 RCL_Unit worldPointTop = wallHeightWorld - _RCL_camera.height;
1475 RCL_Unit worldPointBottom = -1 * _RCL_camera.height;
1477 wallStart = _RCL_middleRow -
1478 (RCL_perspectiveScaleVertical(worldPointTop,dist)
1479 * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1481 int16_t wallEnd = _RCL_middleRow -
1482 (RCL_perspectiveScaleVertical(worldPointBottom,dist)
1483 * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1485 wallHeightScreen = wallEnd - wallStart;
1487 if (wallHeightScreen <= 0) // can happen because of rounding errors
1488 wallHeightScreen = 1;
1491 else
1493 _RCL_makeInfiniteHit(&p.hit,&ray);
1496 // draw ceiling
1498 p.isWall = 0;
1499 p.isFloor = 0;
1500 p.isHorizon = 1;
1501 p.depth = 1;
1502 p.height = RCL_UNITS_PER_SQUARE;
1504 y = _RCL_drawHorizontalColumn(-1,wallStart,-1,_RCL_middleRow,_RCL_camera.height,1,
1505 RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p);
1507 // draw wall
1509 p.isWall = 1;
1510 p.isFloor = 1;
1511 p.depth = dist;
1512 p.height = 0;
1514 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1515 p.hit.textureCoord -= p.hit.doorRoll;
1516 #endif
1518 p.texCoords.x = p.hit.textureCoord;
1519 p.texCoords.y = 0;
1521 RCL_Unit limit = _RCL_drawWall(y,wallStart,wallStart + wallHeightScreen - 1,
1522 -1,_RCL_camResYLimit,p.hit.arrayValue,1,&p);
1524 y = RCL_max(y,limit); // take max, in case no wall was drawn
1525 y = RCL_max(y,wallStart);
1527 // draw floor
1529 p.isWall = 0;
1531 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1532 p.depth = (_RCL_camera.resolution.y - y) * _RCL_horizontalDepthStep + 1;
1533 #endif
1535 _RCL_drawHorizontalColumn(y,_RCL_camResYLimit,-1,_RCL_camResYLimit,
1536 _RCL_camera.height,1,RCL_COMPUTE_FLOOR_DEPTH,RCL_COMPUTE_FLOOR_TEXCOORDS,
1537 -1,&ray,&p);
1541 Precomputes a distance from camera to the floor at each screen row into an
1542 array (must be preallocated with sufficient (camera.resolution.y) length).
1544 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera,
1545 RCL_Unit *dest, uint16_t startIndex)
1547 RCL_Unit camHeightScreenSize =
1548 (camera.height * camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1550 for (uint16_t i = startIndex; i < camera.resolution.y; ++i)
1551 dest[i] = RCL_perspectiveScaleVerticalInverse(camHeightScreenSize,
1552 RCL_abs(i - _RCL_middleRow));
1555 void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
1556 RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
1557 RCL_RayConstraints constraints)
1559 _RCL_floorFunction = floorHeightFunc;
1560 _RCL_ceilFunction = ceilingHeightFunc;
1561 _RCL_camera = cam;
1562 _RCL_camResYLimit = cam.resolution.y - 1;
1564 uint16_t halfResY = cam.resolution.y / 2;
1566 _RCL_middleRow = halfResY + cam.shear;
1568 _RCL_fHorizontalDepthStart = _RCL_middleRow + halfResY;
1569 _RCL_cHorizontalDepthStart = _RCL_middleRow - halfResY;
1571 _RCL_startFloorHeight = floorHeightFunc(
1572 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1573 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height;
1575 _RCL_startCeil_Height =
1576 ceilingHeightFunc != 0 ?
1577 ceilingHeightFunc(
1578 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1579 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height
1580 : RCL_INFINITY;
1582 _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y;
1584 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1585 RCL_Unit floorPixelDistances[cam.resolution.y];
1586 _RCL_precomputeFloorDistances(cam,floorPixelDistances,0);
1587 _RCL_floorPixelDistances = floorPixelDistances; // pass to column function
1588 #endif
1590 RCL_castRaysMultiHit(cam,_RCL_floorCeilFunction,typeFunction,
1591 _RCL_columnFunctionComplex,constraints);
1594 void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
1595 RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
1596 RCL_RayConstraints constraints)
1598 _RCL_floorFunction = floorHeightFunc;
1599 _RCL_camera = cam;
1600 _RCL_camResYLimit = cam.resolution.y - 1;
1601 _RCL_middleRow = cam.resolution.y / 2;
1602 _RCL_rollFunction = rollFunc;
1604 _RCL_cameraHeightScreen =
1605 (_RCL_camera.resolution.y * (_RCL_camera.height - RCL_UNITS_PER_SQUARE)) /
1606 RCL_UNITS_PER_SQUARE;
1608 _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y;
1610 constraints.maxHits =
1611 _RCL_rollFunction == 0 ?
1612 1 : // no door => 1 hit is enough
1613 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1615 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1616 RCL_Unit floorPixelDistances[cam.resolution.y];
1617 _RCL_precomputeFloorDistances(cam,floorPixelDistances,_RCL_middleRow);
1618 _RCL_floorPixelDistances = floorPixelDistances; // pass to column function
1619 #endif
1621 RCL_castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc,
1622 _RCL_columnFunctionSimple, constraints);
1624 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1625 _RCL_floorPixelDistances = 0;
1626 #endif
1629 RCL_Vector2D RCL_normalize(RCL_Vector2D v)
1631 RCL_Vector2D result;
1632 RCL_Unit l = RCL_len(v);
1633 l = RCL_nonZero(l);
1635 result.x = (v.x * RCL_UNITS_PER_SQUARE) / l;
1636 result.y = (v.y * RCL_UNITS_PER_SQUARE) / l;
1638 return result;
1641 RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2)
1643 v1 = RCL_normalize(v1);
1644 v2 = RCL_normalize(v2);
1646 return (v1.x * v2.x + v1.y * v2.y) / RCL_UNITS_PER_SQUARE;
1649 RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
1650 RCL_Camera camera)
1652 RCL_PixelInfo result;
1654 RCL_Vector2D toPoint;
1656 toPoint.x = worldPosition.x - camera.position.x;
1657 toPoint.y = worldPosition.y - camera.position.y;
1659 RCL_Unit middleColumn = camera.resolution.x / 2;
1661 // rotate the point to camera space (y left/right, x forw/backw)
1663 RCL_Unit cos = RCL_cos(camera.direction);
1664 RCL_Unit sin = RCL_sin(camera.direction);
1666 RCL_Unit tmp = toPoint.x;
1668 toPoint.x = (toPoint.x * cos - toPoint.y * sin) / RCL_UNITS_PER_SQUARE;
1669 toPoint.y = (tmp * sin + toPoint.y * cos) / RCL_UNITS_PER_SQUARE;
1671 result.depth = toPoint.x;
1673 result.position.x =
1674 middleColumn + (-1 * toPoint.y * middleColumn) / RCL_nonZero(result.depth);
1676 result.position.y =
1677 camera.resolution.y / 2 -
1678 (RCL_perspectiveScaleVertical(height - camera.height,result.depth)
1679 * camera.resolution.y) / RCL_UNITS_PER_SQUARE
1680 + 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_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance)
1692 return distance != 0 ?
1693 (originalSize * RCL_UNITS_PER_SQUARE) /
1694 ((RCL_VERTICAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE)
1695 : 0;
1698 RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize,
1699 RCL_Unit scaledSize)
1701 return scaledSize != 0 ?
1702 (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) /
1703 // ^ take the middle
1704 ((RCL_VERTICAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE)
1705 : RCL_INFINITY;
1708 RCL_Unit
1709 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance)
1711 return distance != 0 ?
1712 (originalSize * RCL_UNITS_PER_SQUARE) /
1713 ((RCL_HORIZONTAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE)
1714 : 0;
1717 RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize,
1718 RCL_Unit scaledSize)
1720 return scaledSize != 0 ?
1721 (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) /
1722 ((RCL_HORIZONTAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE)
1723 : RCL_INFINITY;
1726 RCL_Unit RCL_castRay3D(
1727 RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2,
1728 RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc,
1729 RCL_RayConstraints constraints)
1731 RCL_HitResult hits[constraints.maxHits];
1732 uint16_t numHits;
1734 RCL_Ray ray;
1736 ray.start = pos1;
1738 RCL_Unit distance;
1740 ray.direction.x = pos2.x - pos1.x;
1741 ray.direction.y = pos2.y - pos1.y;
1743 distance = RCL_len(ray.direction);
1745 ray.direction = RCL_normalize(ray.direction);
1747 RCL_Unit heightDiff = height2 - height1;
1749 RCL_castRayMultiHit(ray,floorHeightFunc,0,hits,&numHits,constraints);
1751 RCL_Unit result = RCL_UNITS_PER_SQUARE;
1753 int16_t squareX = RCL_divRoundDown(pos1.x,RCL_UNITS_PER_SQUARE);
1754 int16_t squareY = RCL_divRoundDown(pos1.y,RCL_UNITS_PER_SQUARE);
1756 RCL_Unit startHeight = floorHeightFunc(squareX,squareY);
1758 #define checkHits(comp,res) \
1760 RCL_Unit currentHeight = startHeight; \
1761 for (uint16_t i = 0; i < numHits; ++i) \
1763 if (hits[i].distance > distance) \
1764 break;\
1765 RCL_Unit h = hits[i].arrayValue; \
1766 if ((currentHeight comp h ? currentHeight : h) \
1767 comp (height1 + (hits[i].distance * heightDiff) / distance)) \
1769 res = (hits[i].distance * RCL_UNITS_PER_SQUARE) / distance; \
1770 break; \
1772 currentHeight = h; \
1776 checkHits(>,result)
1778 if (ceilingHeightFunc != 0)
1780 RCL_Unit result2 = RCL_UNITS_PER_SQUARE;
1782 startHeight = ceilingHeightFunc(squareX,squareY);
1784 RCL_castRayMultiHit(ray,ceilingHeightFunc,0,hits,&numHits,constraints);
1786 checkHits(<,result2)
1788 if (result2 < result)
1789 result = result2;
1792 #undef checkHits
1794 return result;
1797 void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
1798 RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
1799 RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force)
1801 int8_t movesInPlane = planeOffset.x != 0 || planeOffset.y != 0;
1802 int16_t xSquareNew, ySquareNew;
1804 if (movesInPlane || force)
1806 RCL_Vector2D corner; // BBox corner in the movement direction
1807 RCL_Vector2D cornerNew;
1809 int16_t xDir = planeOffset.x > 0 ? 1 : -1;
1810 int16_t yDir = planeOffset.y > 0 ? 1 : -1;
1812 corner.x = camera->position.x + xDir * RCL_CAMERA_COLL_RADIUS;
1813 corner.y = camera->position.y + yDir * RCL_CAMERA_COLL_RADIUS;
1815 int16_t xSquare = RCL_divRoundDown(corner.x,RCL_UNITS_PER_SQUARE);
1816 int16_t ySquare = RCL_divRoundDown(corner.y,RCL_UNITS_PER_SQUARE);
1818 cornerNew.x = corner.x + planeOffset.x;
1819 cornerNew.y = corner.y + planeOffset.y;
1821 xSquareNew = RCL_divRoundDown(cornerNew.x,RCL_UNITS_PER_SQUARE);
1822 ySquareNew = RCL_divRoundDown(cornerNew.y,RCL_UNITS_PER_SQUARE);
1824 RCL_Unit bottomLimit = -1 * RCL_INFINITY;
1825 RCL_Unit topLimit = RCL_INFINITY;
1827 RCL_Unit currCeilHeight = RCL_INFINITY;
1829 if (computeHeight)
1831 bottomLimit = camera->height - RCL_CAMERA_COLL_HEIGHT_BELOW +
1832 RCL_CAMERA_COLL_STEP_HEIGHT;
1834 topLimit = camera->height + RCL_CAMERA_COLL_HEIGHT_ABOVE;
1836 if (ceilingHeightFunc != 0)
1837 currCeilHeight = ceilingHeightFunc(xSquare,ySquare);
1840 // checks a single square for collision against the camera
1841 #define collCheck(dir,s1,s2)\
1842 if (computeHeight)\
1844 RCL_Unit height = floorHeightFunc(s1,s2);\
1845 if (height > bottomLimit || \
1846 currCeilHeight - height < \
1847 RCL_CAMERA_COLL_HEIGHT_BELOW + RCL_CAMERA_COLL_HEIGHT_ABOVE)\
1848 dir##Collides = 1;\
1849 else if (ceilingHeightFunc != 0)\
1851 RCL_Unit height2 = ceilingHeightFunc(s1,s2);\
1852 if ((height2 < topLimit) || ((height2 - height) < \
1853 (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))\
1854 dir##Collides = 1;\
1857 else\
1858 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1860 // check a collision against non-diagonal square
1861 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1862 if (dir##SquareNew != dir##Square)\
1864 collCheck(dir,s1,s2)\
1866 if (!dir##Collides)\
1867 { /* now also check for coll on the neighbouring square */ \
1868 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1869 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1870 if (dir2##Square2 != dir2##Square)\
1872 if (x)\
1873 collCheck(dir,dir##SquareNew,dir2##Square2)\
1874 else\
1875 collCheck(dir,dir2##Square2,dir##SquareNew)\
1879 int8_t xCollides = 0;
1880 collCheckOrtho(x,y,xSquareNew,ySquare,1)
1882 int8_t yCollides = 0;
1883 collCheckOrtho(y,x,xSquare,ySquareNew,0)
1885 #define collHandle(dir)\
1886 if (dir##Collides)\
1887 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1888 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1889 dir##Dir;\
1891 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1892 collision can't happen */
1894 if (xSquare != xSquareNew && ySquare != ySquareNew) // corner?
1896 int8_t xyCollides = 0;
1897 collCheck(xy,xSquareNew,ySquareNew)
1899 if (xyCollides)
1901 // normally should slide, but let's KISS
1902 cornerNew = corner;
1907 collHandle(x)
1908 collHandle(y)
1910 #undef collCheck
1911 #undef collHandle
1913 camera->position.x = cornerNew.x - xDir * RCL_CAMERA_COLL_RADIUS;
1914 camera->position.y = cornerNew.y - yDir * RCL_CAMERA_COLL_RADIUS;
1917 if (computeHeight && (movesInPlane || heightOffset != 0 || force))
1919 camera->height += heightOffset;
1921 int16_t xSquare1 = RCL_divRoundDown(camera->position.x -
1922 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1924 int16_t xSquare2 = RCL_divRoundDown(camera->position.x +
1925 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1927 int16_t ySquare1 = RCL_divRoundDown(camera->position.y -
1928 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1930 int16_t ySquare2 = RCL_divRoundDown(camera->position.y +
1931 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1933 RCL_Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1);
1934 RCL_Unit topLimit = ceilingHeightFunc != 0 ?
1935 ceilingHeightFunc(xSquare1,ySquare1) : RCL_INFINITY;
1937 RCL_Unit height;
1939 #define checkSquares(s1,s2)\
1941 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1942 bottomLimit = RCL_max(bottomLimit,height);\
1943 height = ceilingHeightFunc != 0 ?\
1944 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
1945 topLimit = RCL_min(topLimit,height);\
1948 if (xSquare2 != xSquare1)
1949 checkSquares(2,1)
1951 if (ySquare2 != ySquare1)
1952 checkSquares(1,2)
1954 if (xSquare2 != xSquare1 && ySquare2 != ySquare1)
1955 checkSquares(2,2)
1957 camera->height = RCL_clamp(camera->height,
1958 bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW,
1959 topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE);
1961 #undef checkSquares
1965 void RCL_initCamera(RCL_Camera *camera)
1967 camera->position.x = 0;
1968 camera->position.y = 0;
1969 camera->direction = 0;
1970 camera->resolution.x = 20;
1971 camera->resolution.y = 15;
1972 camera->shear = 0;
1973 camera->height = RCL_UNITS_PER_SQUARE;
1976 void RCL_initRayConstraints(RCL_RayConstraints *constraints)
1978 constraints->maxHits = 1;
1979 constraints->maxSteps = 20;
1982 #endif