Fix ugly fix
[raycastlib.git] / raycastlib.h
blobbda7c597e9115b5236787faa5dd84fe50b780ee7
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.904
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_cosInt(RCL_Unit input);
369 RCL_Unit RCL_sinInt(RCL_Unit input);
371 RCL_Unit RCL_tanInt(RCL_Unit input);
373 RCL_Unit RCL_ctgInt(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_sqrtInt(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 #ifdef RCL_PROFILE
518 // function call counters for profiling
519 uint32_t profile_RCL_sqrtInt = 0;
520 uint32_t profile_RCL_clamp = 0;
521 uint32_t profile_RCL_cosInt = 0;
522 uint32_t profile_RCL_angleToDirection = 0;
523 uint32_t profile_RCL_dist = 0;
524 uint32_t profile_RCL_len = 0;
525 uint32_t profile_RCL_pointIsLeftOfRay = 0;
526 uint32_t profile_RCL_castRayMultiHit = 0;
527 uint32_t profile_RCL_castRay = 0;
528 uint32_t profile_RCL_absVal = 0;
529 uint32_t profile_RCL_normalize = 0;
530 uint32_t profile_RCL_vectorsAngleCos = 0;
531 uint32_t profile_RCL_perspectiveScaleVertical = 0;
532 uint32_t profile_RCL_wrap = 0;
533 uint32_t profile_RCL_divRoundDown = 0;
534 #define RCL_profileCall(c) profile_##c += 1
536 #define printProfile() {\
537 printf("profile:\n");\
538 printf(" RCL_sqrtInt: %d\n",profile_RCL_sqrtInt);\
539 printf(" RCL_clamp: %d\n",profile_RCL_clamp);\
540 printf(" RCL_cosInt: %d\n",profile_RCL_cosInt);\
541 printf(" RCL_angleToDirection: %d\n",profile_RCL_angleToDirection);\
542 printf(" RCL_dist: %d\n",profile_RCL_dist);\
543 printf(" RCL_len: %d\n",profile_RCL_len);\
544 printf(" RCL_pointIsLeftOfRay: %d\n",profile_RCL_pointIsLeftOfRay);\
545 printf(" RCL_castRayMultiHit : %d\n",profile_RCL_castRayMultiHit);\
546 printf(" RCL_castRay: %d\n",profile_RCL_castRay);\
547 printf(" RCL_normalize: %d\n",profile_RCL_normalize);\
548 printf(" RCL_vectorsAngleCos: %d\n",profile_RCL_vectorsAngleCos);\
549 printf(" RCL_absVal: %d\n",profile_RCL_absVal);\
550 printf(" RCL_perspectiveScaleVertical: %d\n",profile_RCL_perspectiveScaleVertical);\
551 printf(" RCL_wrap: %d\n",profile_RCL_wrap);\
552 printf(" RCL_divRoundDown: %d\n",profile_RCL_divRoundDown); }
553 #else
554 #define RCL_profileCall(c)
555 #endif
557 RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax)
559 RCL_profileCall(RCL_clamp);
561 if (value >= valueMin)
563 if (value <= valueMax)
564 return value;
565 else
566 return valueMax;
568 else
569 return valueMin;
572 static inline RCL_Unit RCL_absVal(RCL_Unit value)
574 RCL_profileCall(RCL_absVal);
576 return value * (((value >= 0) << 1) - 1);
579 /// Like mod, but behaves differently for negative values.
580 static inline RCL_Unit RCL_wrap(RCL_Unit value, RCL_Unit mod)
582 RCL_profileCall(RCL_wrap);
583 RCL_Unit cmp = value < 0;
584 return cmp * mod + (value % mod) - cmp;
587 /// Performs division, rounding down, NOT towards zero.
588 static inline RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor)
590 RCL_profileCall(RCL_divRoundDown);
592 return value / divisor - ((value >= 0) ? 0 : 1);
595 // Bhaskara's cosine approximation formula
596 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
597 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
598 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
600 #if RCL_USE_COS_LUT == 1
602 #ifdef RCL_RAYCAST_TINY
603 const RCL_Unit cosLUT[64] =
605 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
607 #else
608 const RCL_Unit cosLUT[64] =
610 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
611 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
612 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
613 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
615 #endif
617 #elif RCL_USE_COS_LUT == 2
618 const RCL_Unit cosLUT[128] =
620 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
621 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
622 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
623 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
624 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
625 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
626 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
627 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
629 #endif
631 RCL_Unit RCL_cosInt(RCL_Unit input)
633 RCL_profileCall(RCL_cosInt);
635 input = RCL_wrap(input,RCL_UNITS_PER_SQUARE);
637 #if RCL_USE_COS_LUT == 1
639 #ifdef RCL_RAYCAST_TINY
640 return cosLUT[input];
641 #else
642 return cosLUT[input / 16];
643 #endif
645 #elif RCL_USE_COS_LUT == 2
646 return cosLUT[input / 8];
647 #else
648 if (input < RCL_UNITS_PER_SQUARE / 4)
649 return trigHelper(input);
650 else if (input < RCL_UNITS_PER_SQUARE / 2)
651 return -1 * trigHelper(RCL_UNITS_PER_SQUARE / 2 - input);
652 else if (input < 3 * RCL_UNITS_PER_SQUARE / 4)
653 return -1 * trigHelper(input - RCL_UNITS_PER_SQUARE / 2);
654 else
655 return trigHelper(RCL_UNITS_PER_SQUARE - input);
656 #endif
659 #undef trigHelper
661 RCL_Unit RCL_sinInt(RCL_Unit input)
663 return RCL_cosInt(input - RCL_UNITS_PER_SQUARE / 4);
666 RCL_Unit RCL_tanInt(RCL_Unit input)
668 return (RCL_sinInt(input) * RCL_UNITS_PER_SQUARE) / RCL_cosInt(input);
671 RCL_Unit RCL_ctgInt(RCL_Unit input)
673 return (RCL_cosInt(input) * RCL_UNITS_PER_SQUARE) / RCL_sinInt(input);
676 RCL_Vector2D RCL_angleToDirection(RCL_Unit angle)
678 RCL_profileCall(RCL_angleToDirection);
680 RCL_Vector2D result;
682 result.x = RCL_cosInt(angle);
683 result.y = -1 * RCL_sinInt(angle);
685 return result;
688 uint16_t RCL_sqrtInt(RCL_Unit value)
690 RCL_profileCall(RCL_sqrtInt);
692 #ifdef RCL_RAYCAST_TINY
693 uint16_t result = 0;
694 uint16_t a = value;
695 uint16_t b = 1u << 14;
696 #else
697 uint32_t result = 0;
698 uint32_t a = value;
699 uint32_t b = 1u << 30;
700 #endif
702 while (b > a)
703 b >>= 2;
705 while (b != 0)
707 if (a >= result + b)
709 a -= result + b;
710 result = result + 2 * b;
713 b >>= 2;
714 result >>= 1;
717 return result;
720 RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2)
722 RCL_profileCall(RCL_dist);
724 RCL_Unit dx = p2.x - p1.x;
725 RCL_Unit dy = p2.y - p1.y;
727 #if RCL_USE_DIST_APPROX == 2
728 // octagonal approximation
730 dx = RCL_absVal(dx);
731 dy = RCL_absVal(dy);
733 return dy > dx ? dx / 2 + dy : dy / 2 + dx;
734 #elif RCL_USE_DIST_APPROX == 1
735 // more accurate approximation
737 RCL_Unit a, b, result;
739 dx = ((dx < 0) * 2 - 1) * dx;
740 dy = ((dy < 0) * 2 - 1) * dy;
742 if (dx < dy)
744 a = dy;
745 b = dx;
747 else
749 a = dx;
750 b = dy;
753 result = a + (44 * b) / 102;
755 if (a < (b << 4))
756 result -= (5 * a) / 128;
758 return result;
759 #else
760 dx = dx * dx;
761 dy = dy * dy;
763 return RCL_sqrtInt((RCL_Unit) (dx + dy));
764 #endif
767 RCL_Unit RCL_len(RCL_Vector2D v)
769 RCL_profileCall(RCL_len);
771 RCL_Vector2D zero;
772 zero.x = 0;
773 zero.y = 0;
775 return RCL_dist(zero,v);
778 static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point, RCL_Ray ray)
780 RCL_profileCall(RCL_pointIsLeftOfRay);
782 RCL_Unit dX = point.x - ray.start.x;
783 RCL_Unit dY = point.y - ray.start.y;
784 return (ray.direction.x * dY - ray.direction.y * dX) > 0;
785 // ^ Z component of cross-product
788 void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
789 RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
790 uint16_t *hitResultsLen, RCL_RayConstraints constraints)
792 RCL_profileCall(RCL_castRayMultiHit);
794 RCL_Vector2D currentPos = ray.start;
795 RCL_Vector2D currentSquare;
797 currentSquare.x = RCL_divRoundDown(ray.start.x,RCL_UNITS_PER_SQUARE);
798 currentSquare.y = RCL_divRoundDown(ray.start.y,RCL_UNITS_PER_SQUARE);
800 *hitResultsLen = 0;
802 RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
804 // DDA variables
805 RCL_Vector2D nextSideDist; // dist. from start to the next side in given axis
806 RCL_Vector2D delta;
807 RCL_Vector2D step; // -1 or 1 for each axis
808 int8_t stepHorizontal = 0; // whether the last step was hor. or vert.
810 nextSideDist.x = 0;
811 nextSideDist.y = 0;
813 RCL_Unit dirVecLengthNorm = RCL_len(ray.direction) * RCL_UNITS_PER_SQUARE;
815 delta.x = RCL_absVal(dirVecLengthNorm / RCL_nonZero(ray.direction.x));
816 delta.y = RCL_absVal(dirVecLengthNorm / RCL_nonZero(ray.direction.y));
818 // init DDA
820 if (ray.direction.x < 0)
822 step.x = -1;
823 nextSideDist.x = (RCL_wrap(ray.start.x,RCL_UNITS_PER_SQUARE) * delta.x) /
824 RCL_UNITS_PER_SQUARE;
826 else
828 step.x = 1;
829 nextSideDist.x =
830 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.x,RCL_UNITS_PER_SQUARE)) *
831 delta.x) / RCL_UNITS_PER_SQUARE;
834 if (ray.direction.y < 0)
836 step.y = -1;
837 nextSideDist.y = (RCL_wrap(ray.start.y,RCL_UNITS_PER_SQUARE) * delta.y) /
838 RCL_UNITS_PER_SQUARE;
840 else
842 step.y = 1;
843 nextSideDist.y =
844 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.y,RCL_UNITS_PER_SQUARE)) *
845 delta.y) / RCL_UNITS_PER_SQUARE;
848 // DDA loop
850 #define RECIP_SCALE 65536
852 RCL_Unit rayDirXRecip = RECIP_SCALE / RCL_nonZero(ray.direction.x);
853 RCL_Unit rayDirYRecip = RECIP_SCALE / RCL_nonZero(ray.direction.y);
854 // ^ we precompute reciprocals to avoid divisions in the loop
856 for (uint16_t i = 0; i < constraints.maxSteps; ++i)
858 RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y);
860 if (RCL_unlikely(currentType != squareType))
862 // collision
864 RCL_HitResult h;
866 h.arrayValue = currentType;
867 h.doorRoll = 0;
868 h.position = currentPos;
869 h.square = currentSquare;
871 if (stepHorizontal)
873 h.position.x = currentSquare.x * RCL_UNITS_PER_SQUARE;
874 h.direction = 3;
876 if (step.x == -1)
878 h.direction = 1;
879 h.position.x += RCL_UNITS_PER_SQUARE;
882 RCL_Unit diff = h.position.x - ray.start.x;
884 h.position.y = // avoid division by multiplying with reciprocal
885 ray.start.y + (ray.direction.y * diff * rayDirXRecip) / RECIP_SCALE;
887 #if RCL_RECTILINEAR
888 /* Here we compute the fish eye corrected distance (perpendicular to
889 the projection plane) as the Euclidean distance divided by the length
890 of the ray direction vector. This can be computed without actually
891 computing Euclidean distances as a hypothenuse A (distance) divided
892 by hypothenuse B (length) is equal to leg A (distance along one axis)
893 divided by leg B (length along the same axis). */
895 h.distance =
896 (((h.position.x - ray.start.x) / 4) *
897 RCL_UNITS_PER_SQUARE * rayDirXRecip)
898 / (RECIP_SCALE / 4);
900 // ^ / 4 is here to prevent overflow
901 #endif
903 else
905 h.position.y = currentSquare.y * RCL_UNITS_PER_SQUARE;
906 h.direction = 2;
908 if (step.y == -1)
910 h.direction = 0;
911 h.position.y += RCL_UNITS_PER_SQUARE;
914 RCL_Unit diff = h.position.y - ray.start.y;
916 h.position.x =
917 ray.start.x + (ray.direction.x * diff * rayDirYRecip) / RECIP_SCALE;
919 #if RCL_RECTILINEAR
920 h.distance =
921 (((h.position.y - ray.start.y) / 4) *
922 RCL_UNITS_PER_SQUARE * rayDirYRecip)
923 / (RECIP_SCALE / 4);
925 // ^ / 4 is here to prevent overflow
926 #endif
929 #if !RCL_RECTILINEAR
930 h.distance = RCL_dist(h.position,ray.start);
931 #endif
932 if (typeFunc != 0)
933 h.type = typeFunc(currentSquare.x,currentSquare.y);
935 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
936 switch (h.direction)
938 case 0: h.textureCoord =
939 RCL_wrap(-1 * h.position.x,RCL_UNITS_PER_SQUARE); break;
941 case 1: h.textureCoord =
942 RCL_wrap(h.position.y,RCL_UNITS_PER_SQUARE); break;
944 case 2: h.textureCoord =
945 RCL_wrap(h.position.x,RCL_UNITS_PER_SQUARE); break;
947 case 3: h.textureCoord =
948 RCL_wrap(-1 * h.position.y,RCL_UNITS_PER_SQUARE); break;
950 default: h.textureCoord = 0; break;
953 if (_RCL_rollFunction != 0)
955 h.doorRoll = _RCL_rollFunction(currentSquare.x,currentSquare.y);
957 if (h.direction == 0 || h.direction == 1)
958 h.doorRoll *= -1;
961 #else
962 h.textureCoord = 0;
963 #endif
965 hitResults[*hitResultsLen] = h;
967 *hitResultsLen += 1;
969 squareType = currentType;
971 if (*hitResultsLen >= constraints.maxHits)
972 break;
975 // DDA step
977 if (nextSideDist.x < nextSideDist.y)
979 nextSideDist.x += delta.x;
980 currentSquare.x += step.x;
981 stepHorizontal = 1;
983 else
985 nextSideDist.y += delta.y;
986 currentSquare.y += step.y;
987 stepHorizontal = 0;
992 RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc)
994 RCL_profileCall(RCL_castRay);
996 RCL_HitResult result;
997 uint16_t RCL_len;
998 RCL_RayConstraints c;
1000 c.maxSteps = 1000;
1001 c.maxHits = 1;
1003 RCL_castRayMultiHit(ray,arrayFunc,0,&result,&RCL_len,c);
1005 if (RCL_len == 0)
1006 result.distance = -1;
1008 return result;
1011 void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
1012 RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
1013 RCL_RayConstraints constraints)
1015 RCL_Vector2D dir1 =
1016 RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF);
1018 RCL_Vector2D dir2 =
1019 RCL_angleToDirection(cam.direction + RCL_HORIZONTAL_FOV_HALF);
1021 /* We scale the side distances so that the middle one is
1022 RCL_UNITS_PER_SQUARE, which has to be this way. */
1024 RCL_Unit cos = RCL_nonZero(RCL_cosInt(RCL_HORIZONTAL_FOV_HALF));
1026 dir1.x = (dir1.x * RCL_UNITS_PER_SQUARE) / cos;
1027 dir1.y = (dir1.y * RCL_UNITS_PER_SQUARE) / cos;
1029 dir2.x = (dir2.x * RCL_UNITS_PER_SQUARE) / cos;
1030 dir2.y = (dir2.y * RCL_UNITS_PER_SQUARE) / cos;
1032 RCL_Unit dX = dir2.x - dir1.x;
1033 RCL_Unit dY = dir2.y - dir1.y;
1035 RCL_HitResult hits[constraints.maxHits];
1036 uint16_t hitCount;
1038 RCL_Ray r;
1039 r.start = cam.position;
1041 RCL_Unit currentDX = 0;
1042 RCL_Unit currentDY = 0;
1044 for (int16_t i = 0; i < cam.resolution.x; ++i)
1046 /* Here by linearly interpolating the direction vector its length changes,
1047 which in result achieves correcting the fish eye effect (computing
1048 perpendicular distance). */
1050 r.direction.x = dir1.x + currentDX / cam.resolution.x;
1051 r.direction.y = dir1.y + currentDY / cam.resolution.x;
1053 RCL_castRayMultiHit(r,arrayFunc,typeFunction,hits,&hitCount,constraints);
1055 columnFunc(hits,hitCount,i,r);
1057 currentDX += dX;
1058 currentDY += dY;
1063 Helper function that determines intersection with both ceiling and floor.
1065 RCL_Unit _RCL_floorCeilFunction(int16_t x, int16_t y)
1067 RCL_Unit f = _RCL_floorFunction(x,y);
1069 if (_RCL_ceilFunction == 0)
1070 return f;
1072 RCL_Unit c = _RCL_ceilFunction(x,y);
1074 #ifndef RCL_RAYCAST_TINY
1075 return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff);
1076 #else
1077 return ((f & 0x00ff) << 8) | (c & 0x00ff);
1078 #endif
1081 RCL_Unit _floorHeightNotZeroFunction(int16_t x, int16_t y)
1083 return _RCL_floorFunction(x,y) == 0 ? 0 :
1084 RCL_nonZero((x & 0x00FF) | ((y & 0x00FF) << 8));
1085 // ^ this makes collisions between all squares - needed for rolling doors
1088 RCL_Unit RCL_adjustDistance(RCL_Unit distance, RCL_Camera *camera,
1089 RCL_Ray *ray)
1091 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1092 possibly be computed more efficiently by not computing Euclidean
1093 distance at all, but rather compute the distance of the collision
1094 point from the projection plane (line). */
1096 RCL_Unit result =
1097 (distance *
1098 RCL_vectorsAngleCos(RCL_angleToDirection(camera->direction),
1099 ray->direction)) / RCL_UNITS_PER_SQUARE;
1101 return RCL_nonZero(result);
1102 // ^ prevent division by zero
1105 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1106 static inline int16_t _RCL_drawHorizontalColumn(
1107 RCL_Unit yCurrent,
1108 RCL_Unit yTo,
1109 RCL_Unit limit1, // TODO: int16_t?
1110 RCL_Unit limit2,
1111 RCL_Unit verticalOffset,
1112 int16_t increment,
1113 int8_t computeDepth,
1114 int8_t computeCoords,
1115 int16_t depthIncrementMultiplier,
1116 RCL_Ray *ray,
1117 RCL_PixelInfo *pixelInfo
1120 _RCL_UNUSED(ray);
1122 RCL_Unit depthIncrement;
1123 RCL_Unit dx;
1124 RCL_Unit dy;
1126 pixelInfo->isWall = 0;
1128 int16_t limit = RCL_clamp(yTo,limit1,limit2);
1130 RCL_Unit depth = 0; /* TODO: this is for clamping depth to 0 so that we don't
1131 have negative depths, but we should do it more
1132 elegantly and efficiently */
1134 _RCL_UNUSED(depth);
1136 /* for performance reasons have different version of the critical loop
1137 to be able to branch early */
1138 #define loop(doDepth,doCoords)\
1140 if (doDepth) /*constant condition - compiler should optimize it out*/\
1142 depth = pixelInfo->depth + RCL_absVal(verticalOffset) *\
1143 RCL_VERTICAL_DEPTH_MULTIPLY;\
1144 depthIncrement = depthIncrementMultiplier *\
1145 _RCL_horizontalDepthStep;\
1147 if (doCoords) /*constant condition - compiler should optimize it out*/\
1149 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1150 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1152 for (int16_t i = yCurrent + increment;\
1153 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1154 i += increment)\
1156 pixelInfo->position.y = i;\
1157 if (doDepth) /*constant condition - compiler should optimize it out*/\
1159 depth += depthIncrement;\
1160 pixelInfo->depth = RCL_zeroClamp(depth); \
1161 /* ^ int comparison is fast, it is not braching! (= test instr.) */\
1163 if (doCoords) /*constant condition - compiler should optimize it out*/\
1165 RCL_Unit d = _RCL_floorPixelDistances[i];\
1166 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1167 pixelInfo->texCoords.x =\
1168 _RCL_camera.position.x + ((d * dx) / d2);\
1169 pixelInfo->texCoords.y =\
1170 _RCL_camera.position.y + ((d * dy) / d2);\
1172 RCL_PIXEL_FUNCTION(pixelInfo);\
1176 if (computeDepth) // branch early
1178 if (!computeCoords)
1179 loop(1,0)
1180 else
1181 loop(1,1)
1183 else
1185 if (!computeCoords)
1186 loop(0,0)
1187 else
1188 loop(1,1)
1191 #undef loop
1193 return limit;
1196 /// Helper for drawing walls. Returns the last drawn pixel position.
1197 static inline int16_t _RCL_drawWall(
1198 RCL_Unit yCurrent,
1199 RCL_Unit yFrom,
1200 RCL_Unit yTo,
1201 RCL_Unit limit1, // TODO: int16_t?
1202 RCL_Unit limit2,
1203 RCL_Unit height,
1204 int16_t increment,
1205 RCL_PixelInfo *pixelInfo
1208 _RCL_UNUSED(height)
1210 height = RCL_absVal(height);
1212 pixelInfo->isWall = 1;
1214 RCL_Unit limit = RCL_clamp(yTo,limit1,limit2);
1216 RCL_Unit wallLength = RCL_nonZero(RCL_absVal(yTo - yFrom - 1));
1218 RCL_Unit wallPosition = RCL_absVal(yFrom - yCurrent) - increment;
1220 RCL_Unit heightScaled = height * RCL_TEXTURE_INTERPOLATION_SCALE;
1221 _RCL_UNUSED(heightScaled);
1223 RCL_Unit coordStepScaled = RCL_COMPUTE_WALL_TEXCOORDS ?
1224 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1225 ((RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE) / wallLength)
1226 #else
1227 (heightScaled / wallLength)
1228 #endif
1229 : 0;
1231 pixelInfo->texCoords.y = RCL_COMPUTE_WALL_TEXCOORDS ?
1232 (wallPosition * coordStepScaled) : 0;
1234 if (increment < 0)
1236 coordStepScaled *= -1;
1237 pixelInfo->texCoords.y =
1238 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1239 (RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE)
1240 - pixelInfo->texCoords.y;
1241 #else
1242 heightScaled - pixelInfo->texCoords.y;
1243 #endif
1245 else
1247 // with floor wall, don't start under 0
1248 pixelInfo->texCoords.y = RCL_zeroClamp(pixelInfo->texCoords.y);
1251 RCL_Unit textureCoordScaled = pixelInfo->texCoords.y;
1253 for (RCL_Unit i = yCurrent + increment;
1254 increment == -1 ? i >= limit : i <= limit; // TODO: is efficient?
1255 i += increment)
1257 pixelInfo->position.y = i;
1259 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1260 pixelInfo->texCoords.y =
1261 textureCoordScaled / RCL_TEXTURE_INTERPOLATION_SCALE;
1263 textureCoordScaled += coordStepScaled;
1264 #endif
1266 RCL_PIXEL_FUNCTION(pixelInfo);
1269 return limit;
1272 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1273 static inline void _RCL_makeInfiniteHit(RCL_HitResult *hit, RCL_Ray *ray)
1275 hit->distance = RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE;
1276 /* ^ horizon is at infinity, but we can't use too big infinity
1277 (RCL_INFINITY) because it would overflow in the following mult. */
1278 hit->position.x = (ray->direction.x * hit->distance) / RCL_UNITS_PER_SQUARE;
1279 hit->position.y = (ray->direction.y * hit->distance) / RCL_UNITS_PER_SQUARE;
1281 hit->direction = 0;
1282 hit->textureCoord = 0;
1283 hit->arrayValue = 0;
1284 hit->doorRoll = 0;
1285 hit->type = 0;
1288 void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
1289 RCL_Ray ray)
1291 // last written Y position, can never go backwards
1292 RCL_Unit fPosY = _RCL_camera.resolution.y;
1293 RCL_Unit cPosY = -1;
1295 // world coordinates (relative to camera height though)
1296 RCL_Unit fZ1World = _RCL_startFloorHeight;
1297 RCL_Unit cZ1World = _RCL_startCeil_Height;
1299 RCL_PixelInfo p;
1300 p.position.x = x;
1301 p.height = 0;
1302 p.wallHeight = 0;
1303 p.texCoords.x = 0;
1304 p.texCoords.y = 0;
1306 // we'll be simulatenously drawing the floor and the ceiling now
1307 for (RCL_Unit j = 0; j <= hitCount; ++j)
1308 { // ^ = add extra iteration for horizon plane
1309 int8_t drawingHorizon = j == hitCount;
1311 RCL_HitResult hit;
1312 RCL_Unit distance = 1;
1314 RCL_Unit fWallHeight = 0, cWallHeight = 0;
1315 RCL_Unit fZ2World = 0, cZ2World = 0;
1316 RCL_Unit fZ1Screen = 0, cZ1Screen = 0;
1317 RCL_Unit fZ2Screen = 0, cZ2Screen = 0;
1319 if (!drawingHorizon)
1321 hit = hits[j];
1322 distance = RCL_nonZero(hit.distance);
1323 p.hit = hit;
1325 fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y);
1326 fZ2World = fWallHeight - _RCL_camera.height;
1327 fZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1328 (fZ1World * _RCL_camera.resolution.y) /
1329 RCL_UNITS_PER_SQUARE,distance);
1330 fZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1331 (fZ2World * _RCL_camera.resolution.y) /
1332 RCL_UNITS_PER_SQUARE,distance);
1334 if (_RCL_ceilFunction != 0)
1336 cWallHeight = _RCL_ceilFunction(hit.square.x,hit.square.y);
1337 cZ2World = cWallHeight - _RCL_camera.height;
1338 cZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1339 (cZ1World * _RCL_camera.resolution.y) /
1340 RCL_UNITS_PER_SQUARE,distance);
1341 cZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1342 (cZ2World * _RCL_camera.resolution.y) /
1343 RCL_UNITS_PER_SQUARE,distance);
1346 else
1348 fZ1Screen = _RCL_middleRow;
1349 cZ1Screen = _RCL_middleRow + 1;
1350 _RCL_makeInfiniteHit(&p.hit,&ray);
1353 RCL_Unit limit;
1355 p.isWall = 0;
1356 p.isHorizon = drawingHorizon;
1358 // draw floor until wall
1359 p.isFloor = 1;
1360 p.height = fZ1World + _RCL_camera.height;
1361 p.wallHeight = 0;
1363 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1364 p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep;
1365 #else
1366 p.depth = 0;
1367 #endif
1369 limit = _RCL_drawHorizontalColumn(fPosY,fZ1Screen,cPosY + 1,
1370 _RCL_camera.resolution.y,fZ1World,-1,RCL_COMPUTE_FLOOR_DEPTH,
1371 // ^ purposfully allow outside screen bounds
1372 RCL_COMPUTE_FLOOR_TEXCOORDS && p.height == RCL_FLOOR_TEXCOORDS_HEIGHT,
1373 1,&ray,&p);
1375 if (fPosY > limit)
1376 fPosY = limit;
1378 if (_RCL_ceilFunction != 0 || drawingHorizon)
1380 // draw ceiling until wall
1381 p.isFloor = 0;
1382 p.height = cZ1World + _RCL_camera.height;
1384 #if RCL_COMPUTE_CEILING_DEPTH == 1
1385 p.depth = (cPosY - _RCL_cHorizontalDepthStart) *
1386 _RCL_horizontalDepthStep;
1387 #endif
1389 limit = _RCL_drawHorizontalColumn(cPosY,cZ1Screen,
1390 -1,fPosY - 1,cZ1World,1,RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p);
1391 // ^ purposfully allow outside screen bounds here
1393 if (cPosY < limit)
1394 cPosY = limit;
1397 if (!drawingHorizon) // don't draw walls for horizon plane
1399 p.isWall = 1;
1400 p.depth = distance;
1401 p.isFloor = 1;
1402 p.texCoords.x = hit.textureCoord;
1403 p.height = fZ1World + _RCL_camera.height;
1404 p.wallHeight = fWallHeight;
1406 // draw floor wall
1408 if (fPosY > 0) // still pixels left?
1410 p.isFloor = 1;
1412 limit = _RCL_drawWall(fPosY,fZ1Screen,fZ2Screen,cPosY + 1,
1413 _RCL_camera.resolution.y,
1414 // ^ purposfully allow outside screen bounds here
1415 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1416 RCL_UNITS_PER_SQUARE
1417 #else
1418 fZ2World - fZ1World
1419 #endif
1420 ,-1,&p);
1423 if (fPosY > limit)
1424 fPosY = limit;
1426 fZ1World = fZ2World; // for the next iteration
1427 } // ^ purposfully allow outside screen bounds here
1429 // draw ceiling wall
1431 if (_RCL_ceilFunction != 0 && cPosY < _RCL_camResYLimit) // pixels left?
1433 p.isFloor = 0;
1434 p.height = cZ1World + _RCL_camera.height;
1435 p.wallHeight = cWallHeight;
1437 limit = _RCL_drawWall(cPosY,cZ1Screen,cZ2Screen,
1438 -1,fPosY - 1,
1439 // ^ puposfully allow outside screen bounds here
1440 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1441 RCL_UNITS_PER_SQUARE
1442 #else
1443 cZ1World - cZ2World
1444 #endif
1445 ,1,&p);
1447 if (cPosY < limit)
1448 cPosY = limit;
1450 cZ1World = cZ2World; // for the next iteration
1451 } // ^ puposfully allow outside screen bounds here
1456 void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount,
1457 uint16_t x, RCL_Ray ray)
1459 RCL_Unit y = 0;
1460 RCL_Unit wallHeightScreen = 0;
1461 RCL_Unit wallStart = _RCL_middleRow;
1462 RCL_Unit heightOffset = 0;
1464 RCL_Unit dist = 1;
1466 RCL_PixelInfo p;
1467 p.position.x = x;
1468 p.wallHeight = RCL_UNITS_PER_SQUARE;
1470 if (hitCount > 0)
1472 RCL_HitResult hit = hits[0];
1474 uint8_t goOn = 1;
1476 if (_RCL_rollFunction != 0 && RCL_COMPUTE_WALL_TEXCOORDS == 1)
1478 if (hit.arrayValue == 0)
1480 // standing inside door square, looking out => move to the next hit
1482 if (hitCount > 1)
1483 hit = hits[1];
1484 else
1485 goOn = 0;
1487 else
1489 // normal hit, check the door roll
1491 RCL_Unit texCoordMod = hit.textureCoord % RCL_UNITS_PER_SQUARE;
1493 int8_t unrolled = hit.doorRoll >= 0 ?
1494 (hit.doorRoll > texCoordMod) :
1495 (texCoordMod > RCL_UNITS_PER_SQUARE + hit.doorRoll);
1497 if (unrolled)
1499 goOn = 0;
1501 if (hitCount > 1) /* should probably always be true (hit on square
1502 exit) */
1504 if (hit.direction % 2 != hits[1].direction % 2)
1506 // hit on the inner side
1507 hit = hits[1];
1508 goOn = 1;
1510 else if (hitCount > 2)
1512 // hit on the opposite side
1513 hit = hits[2];
1514 goOn = 1;
1521 p.hit = hit;
1523 if (goOn)
1525 dist = hit.distance;
1527 int16_t wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y);
1529 wallHeightScreen = RCL_perspectiveScaleVertical((wallHeightWorld *
1530 _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE,dist);
1532 int16_t RCL_normalizedWallHeight = wallHeightWorld != 0 ?
1533 ((RCL_UNITS_PER_SQUARE * wallHeightScreen) / wallHeightWorld) : 0;
1535 heightOffset = RCL_perspectiveScaleVertical(_RCL_cameraHeightScreen,dist);
1537 wallStart = _RCL_middleRow - wallHeightScreen + heightOffset +
1538 RCL_normalizedWallHeight;
1541 else
1543 _RCL_makeInfiniteHit(&p.hit,&ray);
1546 // draw ceiling
1548 p.isWall = 0;
1549 p.isFloor = 0;
1550 p.isHorizon = 1;
1551 p.depth = 1;
1552 p.height = RCL_UNITS_PER_SQUARE;
1554 y = _RCL_drawHorizontalColumn(-1,wallStart,-1,_RCL_middleRow,_RCL_camera.height,1,
1555 RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p);
1557 // draw wall
1559 p.isWall = 1;
1560 p.isFloor = 1;
1561 p.depth = dist;
1562 p.height = 0;
1564 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1565 p.hit.textureCoord -= p.hit.doorRoll;
1566 #endif
1568 p.texCoords.x = p.hit.textureCoord;
1569 p.texCoords.y = 0;
1571 RCL_Unit limit = _RCL_drawWall(y,wallStart,wallStart + wallHeightScreen - 1,
1572 -1,_RCL_camResYLimit,p.hit.arrayValue,1,&p);
1574 y = RCL_max(y,limit); // take max, in case no wall was drawn
1575 y = RCL_max(y,wallStart);
1577 // draw floor
1579 p.isWall = 0;
1581 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1582 p.depth = (_RCL_camera.resolution.y - y) * _RCL_horizontalDepthStep + 1;
1583 #endif
1585 _RCL_drawHorizontalColumn(y,_RCL_camResYLimit,-1,_RCL_camResYLimit,
1586 _RCL_camera.height,1,RCL_COMPUTE_FLOOR_DEPTH,RCL_COMPUTE_FLOOR_TEXCOORDS,
1587 -1,&ray,&p);
1591 Precomputes a distance from camera to the floor at each screen row into an
1592 array (must be preallocated with sufficient (camera.resolution.y) length).
1594 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera,
1595 RCL_Unit *dest, uint16_t startIndex)
1597 RCL_Unit camHeightScreenSize =
1598 (camera.height * camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1600 for (uint16_t i = startIndex; i < camera.resolution.y; ++i)
1601 dest[i] = RCL_perspectiveScaleVerticalInverse(camHeightScreenSize,
1602 RCL_absVal(i - _RCL_middleRow));
1605 void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
1606 RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
1607 RCL_RayConstraints constraints)
1609 _RCL_floorFunction = floorHeightFunc;
1610 _RCL_ceilFunction = ceilingHeightFunc;
1611 _RCL_camera = cam;
1612 _RCL_camResYLimit = cam.resolution.y - 1;
1614 uint16_t halfResY = cam.resolution.y / 2;
1616 _RCL_middleRow = halfResY + cam.shear;
1618 _RCL_fHorizontalDepthStart = _RCL_middleRow + halfResY;
1619 _RCL_cHorizontalDepthStart = _RCL_middleRow - halfResY;
1621 _RCL_startFloorHeight = floorHeightFunc(
1622 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1623 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height;
1625 _RCL_startCeil_Height =
1626 ceilingHeightFunc != 0 ?
1627 ceilingHeightFunc(
1628 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1629 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height
1630 : RCL_INFINITY;
1632 _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y;
1634 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1635 RCL_Unit floorPixelDistances[cam.resolution.y];
1636 _RCL_precomputeFloorDistances(cam,floorPixelDistances,0);
1637 _RCL_floorPixelDistances = floorPixelDistances; // pass to column function
1638 #endif
1640 RCL_castRaysMultiHit(cam,_RCL_floorCeilFunction,typeFunction,
1641 _RCL_columnFunctionComplex,constraints);
1644 void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
1645 RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
1646 RCL_RayConstraints constraints)
1648 _RCL_floorFunction = floorHeightFunc;
1649 _RCL_camera = cam;
1650 _RCL_camResYLimit = cam.resolution.y - 1;
1651 _RCL_middleRow = cam.resolution.y / 2;
1652 _RCL_rollFunction = rollFunc;
1654 _RCL_cameraHeightScreen =
1655 (_RCL_camera.resolution.y * (_RCL_camera.height - RCL_UNITS_PER_SQUARE)) /
1656 RCL_UNITS_PER_SQUARE;
1658 _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y;
1660 constraints.maxHits =
1661 _RCL_rollFunction == 0 ?
1662 1 : // no door => 1 hit is enough
1663 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1665 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1666 RCL_Unit floorPixelDistances[cam.resolution.y];
1667 _RCL_precomputeFloorDistances(cam,floorPixelDistances,_RCL_middleRow);
1668 _RCL_floorPixelDistances = floorPixelDistances; // pass to column function
1669 #endif
1671 RCL_castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc,
1672 _RCL_columnFunctionSimple, constraints);
1674 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1675 _RCL_floorPixelDistances = 0;
1676 #endif
1679 RCL_Vector2D RCL_normalize(RCL_Vector2D v)
1681 RCL_profileCall(RCL_normalize);
1683 RCL_Vector2D result;
1684 RCL_Unit l = RCL_len(v);
1685 l = RCL_nonZero(l);
1687 result.x = (v.x * RCL_UNITS_PER_SQUARE) / l;
1688 result.y = (v.y * RCL_UNITS_PER_SQUARE) / l;
1690 return result;
1693 RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2)
1695 RCL_profileCall(RCL_vectorsAngleCos);
1697 v1 = RCL_normalize(v1);
1698 v2 = RCL_normalize(v2);
1700 return (v1.x * v2.x + v1.y * v2.y) / RCL_UNITS_PER_SQUARE;
1703 RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
1704 RCL_Camera camera)
1706 RCL_PixelInfo result;
1708 RCL_Vector2D toPoint;
1710 toPoint.x = worldPosition.x - camera.position.x;
1711 toPoint.y = worldPosition.y - camera.position.y;
1713 RCL_Unit middleColumn = camera.resolution.x / 2;
1715 // rotate the point
1717 RCL_Unit cos = RCL_cosInt(camera.direction);
1718 RCL_Unit sin = RCL_sinInt(camera.direction);
1720 RCL_Unit tmp = toPoint.x;
1722 toPoint.x = (toPoint.x * cos - toPoint.y * sin) / RCL_UNITS_PER_SQUARE;
1723 toPoint.y = (tmp * sin + toPoint.y * cos) / RCL_UNITS_PER_SQUARE;
1725 result.depth = toPoint.x;
1727 result.position.x =
1728 middleColumn + (-1 * toPoint.y * middleColumn) / RCL_nonZero(result.depth);
1730 result.position.y =
1731 camera.resolution.y / 2 -
1732 (RCL_perspectiveScaleVertical(height - camera.height,result.depth)
1733 * camera.resolution.y) / RCL_UNITS_PER_SQUARE
1734 + camera.shear;
1736 return result;
1739 RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees)
1741 return (degrees * RCL_UNITS_PER_SQUARE) / 360;
1744 RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance)
1746 RCL_profileCall(RCL_perspectiveScaleVertical);
1748 return distance != 0 ?
1749 (originalSize * RCL_UNITS_PER_SQUARE) /
1750 ((RCL_VERTICAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE)
1751 : 0;
1754 RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize,
1755 RCL_Unit scaledSize)
1757 return scaledSize != 0 ?
1758 (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) /
1759 // ^ take the middle
1760 ((RCL_VERTICAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE)
1761 : RCL_INFINITY;
1764 RCL_Unit
1765 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance)
1767 return distance != 0 ?
1768 (originalSize * RCL_UNITS_PER_SQUARE) /
1769 ((RCL_HORIZONTAL_FOV_TAN * 2 * distance) / RCL_UNITS_PER_SQUARE)
1770 : 0;
1773 RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize,
1774 RCL_Unit scaledSize)
1776 return scaledSize != 0 ?
1777 (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) /
1778 ((RCL_HORIZONTAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE)
1779 : RCL_INFINITY;
1782 RCL_Unit RCL_castRay3D(
1783 RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2,
1784 RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc,
1785 RCL_RayConstraints constraints)
1787 RCL_HitResult hits[constraints.maxHits];
1788 uint16_t numHits;
1790 RCL_Ray ray;
1792 ray.start = pos1;
1794 RCL_Unit distance;
1796 ray.direction.x = pos2.x - pos1.x;
1797 ray.direction.y = pos2.y - pos1.y;
1799 distance = RCL_len(ray.direction);
1801 ray.direction = RCL_normalize(ray.direction);
1803 RCL_Unit heightDiff = height2 - height1;
1805 RCL_castRayMultiHit(ray,floorHeightFunc,0,hits,&numHits,constraints);
1807 RCL_Unit result = RCL_UNITS_PER_SQUARE;
1809 int16_t squareX = RCL_divRoundDown(pos1.x,RCL_UNITS_PER_SQUARE);
1810 int16_t squareY = RCL_divRoundDown(pos1.y,RCL_UNITS_PER_SQUARE);
1812 RCL_Unit startHeight = floorHeightFunc(squareX,squareY);
1814 #define checkHits(comp,res) \
1816 RCL_Unit currentHeight = startHeight; \
1817 for (uint16_t i = 0; i < numHits; ++i) \
1819 if (hits[i].distance > distance) \
1820 break;\
1821 RCL_Unit h = hits[i].arrayValue; \
1822 if ((currentHeight comp h ? currentHeight : h) \
1823 comp (height1 + (hits[i].distance * heightDiff) / distance)) \
1825 res = (hits[i].distance * RCL_UNITS_PER_SQUARE) / distance; \
1826 break; \
1828 currentHeight = h; \
1832 checkHits(>,result)
1834 if (ceilingHeightFunc != 0)
1836 RCL_Unit result2 = RCL_UNITS_PER_SQUARE;
1838 startHeight = ceilingHeightFunc(squareX,squareY);
1840 RCL_castRayMultiHit(ray,ceilingHeightFunc,0,hits,&numHits,constraints);
1842 checkHits(<,result2)
1844 if (result2 < result)
1845 result = result2;
1848 #undef checkHits
1850 return result;
1853 void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
1854 RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
1855 RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force)
1857 int8_t movesInPlane = planeOffset.x != 0 || planeOffset.y != 0;
1858 int16_t xSquareNew, ySquareNew;
1860 if (movesInPlane || force)
1862 RCL_Vector2D corner; // BBox corner in the movement direction
1863 RCL_Vector2D cornerNew;
1865 int16_t xDir = planeOffset.x > 0 ? 1 : -1;
1866 int16_t yDir = planeOffset.y > 0 ? 1 : -1;
1868 corner.x = camera->position.x + xDir * RCL_CAMERA_COLL_RADIUS;
1869 corner.y = camera->position.y + yDir * RCL_CAMERA_COLL_RADIUS;
1871 int16_t xSquare = RCL_divRoundDown(corner.x,RCL_UNITS_PER_SQUARE);
1872 int16_t ySquare = RCL_divRoundDown(corner.y,RCL_UNITS_PER_SQUARE);
1874 cornerNew.x = corner.x + planeOffset.x;
1875 cornerNew.y = corner.y + planeOffset.y;
1877 xSquareNew = RCL_divRoundDown(cornerNew.x,RCL_UNITS_PER_SQUARE);
1878 ySquareNew = RCL_divRoundDown(cornerNew.y,RCL_UNITS_PER_SQUARE);
1880 RCL_Unit bottomLimit = -1 * RCL_INFINITY;
1881 RCL_Unit topLimit = RCL_INFINITY;
1883 RCL_Unit currCeilHeight = RCL_INFINITY;
1885 if (computeHeight)
1887 bottomLimit = camera->height - RCL_CAMERA_COLL_HEIGHT_BELOW +
1888 RCL_CAMERA_COLL_STEP_HEIGHT;
1890 topLimit = camera->height + RCL_CAMERA_COLL_HEIGHT_ABOVE;
1892 if (ceilingHeightFunc != 0)
1893 currCeilHeight = ceilingHeightFunc(xSquare,ySquare);
1896 // checks a single square for collision against the camera
1897 #define collCheck(dir,s1,s2)\
1898 if (computeHeight)\
1900 RCL_Unit height = floorHeightFunc(s1,s2);\
1901 if (height > bottomLimit || \
1902 currCeilHeight - height < \
1903 RCL_CAMERA_COLL_HEIGHT_BELOW + RCL_CAMERA_COLL_HEIGHT_ABOVE)\
1904 dir##Collides = 1;\
1905 else if (ceilingHeightFunc != 0)\
1907 RCL_Unit height2 = ceilingHeightFunc(s1,s2);\
1908 if ((height2 < topLimit) || ((height2 - height) < \
1909 (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))\
1910 dir##Collides = 1;\
1913 else\
1914 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1916 // check a collision against non-diagonal square
1917 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1918 if (dir##SquareNew != dir##Square)\
1920 collCheck(dir,s1,s2)\
1922 if (!dir##Collides)\
1923 { /* now also check for coll on the neighbouring square */ \
1924 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1925 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1926 if (dir2##Square2 != dir2##Square)\
1928 if (x)\
1929 collCheck(dir,dir##SquareNew,dir2##Square2)\
1930 else\
1931 collCheck(dir,dir2##Square2,dir##SquareNew)\
1935 int8_t xCollides = 0;
1936 collCheckOrtho(x,y,xSquareNew,ySquare,1)
1938 int8_t yCollides = 0;
1939 collCheckOrtho(y,x,xSquare,ySquareNew,0)
1941 #define collHandle(dir)\
1942 if (dir##Collides)\
1943 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1944 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1945 dir##Dir;\
1947 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1948 collision can't happen */
1950 if (xSquare != xSquareNew && ySquare != ySquareNew) // corner?
1952 int8_t xyCollides = 0;
1953 collCheck(xy,xSquareNew,ySquareNew)
1955 if (xyCollides)
1957 // normally should slide, but let's KISS
1958 cornerNew = corner;
1963 collHandle(x)
1964 collHandle(y)
1966 #undef collCheck
1967 #undef collHandle
1969 camera->position.x = cornerNew.x - xDir * RCL_CAMERA_COLL_RADIUS;
1970 camera->position.y = cornerNew.y - yDir * RCL_CAMERA_COLL_RADIUS;
1973 if (computeHeight && (movesInPlane || heightOffset != 0 || force))
1975 camera->height += heightOffset;
1977 int16_t xSquare1 = RCL_divRoundDown(camera->position.x -
1978 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1980 int16_t xSquare2 = RCL_divRoundDown(camera->position.x +
1981 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1983 int16_t ySquare1 = RCL_divRoundDown(camera->position.y -
1984 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1986 int16_t ySquare2 = RCL_divRoundDown(camera->position.y +
1987 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
1989 RCL_Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1);
1990 RCL_Unit topLimit = ceilingHeightFunc != 0 ?
1991 ceilingHeightFunc(xSquare1,ySquare1) : RCL_INFINITY;
1993 RCL_Unit height;
1995 #define checkSquares(s1,s2)\
1997 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1998 bottomLimit = RCL_max(bottomLimit,height);\
1999 height = ceilingHeightFunc != 0 ?\
2000 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
2001 topLimit = RCL_min(topLimit,height);\
2004 if (xSquare2 != xSquare1)
2005 checkSquares(2,1)
2007 if (ySquare2 != ySquare1)
2008 checkSquares(1,2)
2010 if (xSquare2 != xSquare1 && ySquare2 != ySquare1)
2011 checkSquares(2,2)
2013 camera->height = RCL_clamp(camera->height,
2014 bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW,
2015 topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE);
2017 #undef checkSquares
2021 void RCL_initCamera(RCL_Camera *camera)
2023 camera->position.x = 0;
2024 camera->position.y = 0;
2025 camera->direction = 0;
2026 camera->resolution.x = 20;
2027 camera->resolution.y = 15;
2028 camera->shear = 0;
2029 camera->height = RCL_UNITS_PER_SQUARE;
2032 void RCL_initRayConstraints(RCL_RayConstraints *constraints)
2034 constraints->maxHits = 1;
2035 constraints->maxSteps = 20;
2038 #endif