Fix division by zero
[raycastlib.git] / raycastlib.h
blobb1a51eb953272c987c0e2a1786793678be3a4bac
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.909d
31 Version numbering: major.minor[d], id 'd' is appended, this is a
32 in-development version based on the previous stable major.minor version. Two
33 'd' versions with the same version number, .e.g. 1.0d, may be different.
36 #include <stdint.h>
38 #ifndef RCL_RAYCAST_TINY /** Turns on super efficient version of this library.
39 Only use if neccesarry, looks ugly. Also not done
40 yet. */
41 #define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a
42 spatial square, i.e. the fixed point
43 scaling. */
44 typedef int32_t RCL_Unit; /**< Smallest spatial unit, there is
45 RCL_UNITS_PER_SQUARE units in a square's
46 length. This effectively serves the purpose of
47 a fixed-point arithmetic. */
48 #define RCL_INFINITY 2000000000
49 #else
50 #define RCL_UNITS_PER_SQUARE 32
51 typedef int16_t RCL_Unit;
52 #define RCL_INFINITY 30000
53 #define RCL_USE_DIST_APPROX 2
54 #endif
56 #define RCL_U RCL_UNITS_PER_SQUARE ///< shorthand for RCL_UNITS_PER_SQUARE
58 #ifndef RCL_COMPUTE_WALL_TEXCOORDS
59 #define RCL_COMPUTE_WALL_TEXCOORDS 1
60 #endif
62 #ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
63 #define RCL_COMPUTE_FLOOR_TEXCOORDS 0
64 #endif
66 #ifndef RCL_FLOOR_TEXCOORDS_HEIGHT
67 #define RCL_FLOOR_TEXCOORDS_HEIGHT 0 /** If RCL_COMPUTE_FLOOR_TEXCOORDS == 1,
68 this says for what height level the
69 texture coords will be computed for
70 (for simplicity/performance only one
71 level is allowed). */
72 #endif
74 #ifndef RCL_USE_COS_LUT
75 #define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
76 0: none (compute)
77 1: 64 items
78 2: 128 items */
79 #endif
81 #ifndef RCL_USE_DIST_APPROX
82 #define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use:
83 0: none (compute full Euclidean distance)
84 1: accurate approximation
85 2: octagonal approximation (LQ) */
86 #endif
88 #ifndef RCL_RECTILINEAR
89 #define RCL_RECTILINEAR 1 /**< Whether to use rectilinear perspective (normally
90 used), or curvilinear perspective (fish eye). */
91 #endif
93 #ifndef RCL_TEXTURE_VERTICAL_STRETCH
94 #define RCL_TEXTURE_VERTICAL_STRETCH 1 /**< Whether textures should be
95 stretched to wall height (possibly
96 slightly slower if on). */
97 #endif
99 #ifndef RCL_COMPUTE_FLOOR_DEPTH
100 #define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for
101 floor pixels - turns this off if not
102 needed. */
103 #endif
105 #ifndef RCL_COMPUTE_CEILING_DEPTH
106 #define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for
107 ceiling. */
108 #endif
110 #ifndef RCL_ROLL_TEXTURE_COORDS
111 #define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
112 roll the texture coordinates along (mostly
113 desired for doors). */
114 #endif
116 #ifndef RCL_VERTICAL_FOV
117 #define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 3)
118 #endif
120 #define RCL_VERTICAL_FOV_TAN (RCL_VERTICAL_FOV * 4) ///< tan approximation
122 #ifndef RCL_HORIZONTAL_FOV
123 #define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
124 #endif
126 #define RCL_HORIZONTAL_FOV_TAN (RCL_HORIZONTAL_FOV * 4)
128 #define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
130 #ifndef RCL_CAMERA_COLL_RADIUS
131 #define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
132 #endif
134 #ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
135 #define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
136 #endif
138 #ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
139 #define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
140 #endif
142 #ifndef RCL_CAMERA_COLL_STEP_HEIGHT
143 #define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
144 #endif
146 #ifndef RCL_TEXTURE_INTERPOLATION_SCALE
147 #define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed
148 poit vertical texture coord
149 computation. This should be power
150 of two! Higher number can look more
151 accurate but may cause overflow. */
152 #endif
154 #define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
155 horizon has (the floor
156 depth is only
157 approximated with the
158 help of this
159 constant). */
160 #ifndef RCL_VERTICAL_DEPTH_MULTIPLY
161 #define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
162 difference when approximating floor/ceil
163 depth. */
164 #endif
166 #define RCL_min(a,b) ((a) < (b) ? (a) : (b))
167 #define RCL_max(a,b) ((a) > (b) ? (a) : (b))
168 #define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions.
169 #define RCL_zeroClamp(x) ((x) * ((x) >= 0))
170 #define RCL_likely(cond) __builtin_expect(!!(cond),1)
171 #define RCL_unlikely(cond) __builtin_expect(!!(cond),0)
173 #define RCL_logV2D(v)\
174 printf("[%d,%d]\n",v.x,v.y);
176 #define RCL_logRay(r){\
177 printf("ray:\n");\
178 printf(" start: ");\
179 RCL_logV2D(r.start);\
180 printf(" dir: ");\
181 RCL_logV2D(r.direction);}
183 #define RCL_logHitResult(h){\
184 printf("hit:\n");\
185 printf(" square: ");\
186 RCL_logV2D(h.square);\
187 printf(" pos: ");\
188 RCL_logV2D(h.position);\
189 printf(" dist: %d\n", h.distance);\
190 printf(" dir: %d\n", h.direction);\
191 printf(" texcoord: %d\n", h.textureCoord);}
193 #define RCL_logPixelInfo(p){\
194 printf("pixel:\n");\
195 printf(" position: ");\
196 RCL_logV2D(p.position);\
197 printf(" texCoord: ");\
198 RCL_logV2D(p.texCoords);\
199 printf(" depth: %d\n", p.depth);\
200 printf(" height: %d\n", p.height);\
201 printf(" wall: %d\n", p.isWall);\
202 printf(" hit: ");\
203 RCL_logHitResult(p.hit);\
206 #define RCL_logCamera(c){\
207 printf("camera:\n");\
208 printf(" position: ");\
209 RCL_logV2D(c.position);\
210 printf(" height: %d\n",c.height);\
211 printf(" direction: %d\n",c.direction);\
212 printf(" shear: %d\n",c.shear);\
213 printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
216 /// Position in 2D space.
217 typedef struct
219 RCL_Unit x;
220 RCL_Unit y;
221 } RCL_Vector2D;
223 typedef struct
225 RCL_Vector2D start;
226 RCL_Vector2D direction;
227 } RCL_Ray;
229 typedef struct
231 RCL_Unit distance; /**< Distance to the hit position, or -1 if no
232 collision happened. If RCL_RECTILINEAR != 0, then
233 the distance is perpendicular to the projection
234 plane (fish eye correction), otherwise it is
235 the straight distance to the ray start
236 position. */
237 uint8_t direction; /**< Direction of hit. The convention for angle
238 units is explained above. */
239 RCL_Unit textureCoord; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
240 texture coordinate (horizontal). */
241 RCL_Vector2D square; ///< Collided square coordinates.
242 RCL_Vector2D position; ///< Exact collision position in RCL_Units.
243 RCL_Unit arrayValue; /** Value returned by array function (most often
244 this will be the floor height). */
245 RCL_Unit type; /**< Integer identifying type of square (number
246 returned by type function, e.g. texture
247 index).*/
248 RCL_Unit doorRoll; ///< Holds value of door roll.
249 } RCL_HitResult;
251 typedef struct
253 RCL_Vector2D position;
254 RCL_Unit direction; // TODO: rename to "angle" to keep consistency
255 RCL_Vector2D resolution;
256 int16_t shear; /**< Shear offset in pixels (0 => no shear), can simulate
257 looking up/down. */
258 RCL_Unit height;
259 } RCL_Camera;
262 Holds an information about a single rendered pixel (for a pixel function
263 that works as a fragment shader).
265 typedef struct
267 RCL_Vector2D position; ///< On-screen position.
268 int8_t isWall; ///< Whether the pixel is a wall or a floor/ceiling.
269 int8_t isFloor; ///< Whether the pixel is floor or ceiling.
270 int8_t isHorizon; ///< If the pixel belongs to horizon segment.
271 RCL_Unit depth; ///< Corrected depth.
272 RCL_Unit wallHeight;///< Only for wall pixels, says its height.
273 RCL_Unit height; ///< World height (mostly for floor).
274 RCL_HitResult hit; ///< Corresponding ray hit.
275 RCL_Vector2D texCoords; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
276 texture coordinates. */
277 } RCL_PixelInfo;
279 void RCL_PIXEL_FUNCTION (RCL_PixelInfo *pixel);
281 typedef struct
283 uint16_t maxHits;
284 uint16_t maxSteps;
285 } RCL_RayConstraints;
288 Function used to retrieve some information about cells of the rendered scene.
289 It should return a characteristic of given square as an integer (e.g. square
290 height, texture index, ...) - between squares that return different numbers
291 there is considered to be a collision.
293 This function should be as fast as possible as it will typically be called
294 very often.
296 typedef RCL_Unit (*RCL_ArrayFunction)(int16_t x, int16_t y);
298 TODO: maybe array functions should be replaced by defines of funtion names
299 like with pixelFunc? Could be more efficient than function pointers.
303 Function that renders a single pixel at the display. It is handed an info
304 about the pixel it should draw.
306 This function should be as fast as possible as it will typically be called
307 very often.
309 typedef void (*RCL_PixelFunction)(RCL_PixelInfo *info);
311 typedef void
312 (*RCL_ColumnFunction)(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
313 RCL_Ray ray);
316 Simple-interface function to cast a single ray.
318 @return The first collision result.
320 RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc);
323 Casts a 3D ray in 3D environment with floor and optional ceiling
324 (ceilingHeightFunc can be 0). This can be useful for hitscan shooting,
325 visibility checking etc.
327 @return normalized ditance (0 to RCL_UNITS_PER_SQUARE) along the ray at which
328 the environment was hit, RCL_UNITS_PER_SQUARE means nothing was hit
330 RCL_Unit RCL_castRay3D(
331 RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2,
332 RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc,
333 RCL_RayConstraints constraints);
336 Maps a single point in the world to the screen (2D position + depth).
338 RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
339 RCL_Camera camera);
342 Casts a single ray and returns a list of collisions.
344 @param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
345 distance is divided by the ray direction vector length (to correct
346 the fish eye effect)
347 @param arrayFunc function that will be used to determine collisions (hits)
348 with the ray (squares for which this function returns different values
349 are considered to have a collision between them), this will typically
350 be a function returning floor height
351 @param typeFunc optional (can be 0) function - if provided, it will be used
352 to mark the hit result with the number returned by this function
353 (it can be e.g. a texture index)
354 @param hitResults array in which the hit results will be stored (has to be
355 preallocated with at space for at least as many hit results as
356 maxHits specified with the constraints parameter)
357 @param hitResultsLen in this variable the number of hit results will be
358 returned
359 @param constraints specifies constraints for the ray cast
361 void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
362 RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
363 uint16_t *hitResultsLen, RCL_RayConstraints constraints);
365 RCL_Vector2D RCL_angleToDirection(RCL_Unit angle);
368 Cos function.
370 @param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
371 @return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
372 RCL_UNITS_PER_SQUARE)
374 RCL_Unit RCL_cos(RCL_Unit input);
376 RCL_Unit RCL_sin(RCL_Unit input);
378 RCL_Unit RCL_tan(RCL_Unit input);
380 RCL_Unit RCL_ctg(RCL_Unit input);
382 /// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
383 RCL_Vector2D RCL_normalize(RCL_Vector2D v);
385 /// Computes a cos of an angle between two vectors.
386 RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2);
388 uint16_t RCL_sqrt(RCL_Unit value);
389 RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2);
390 RCL_Unit RCL_len(RCL_Vector2D v);
393 Converts an angle in whole degrees to an angle in RCL_Units that this library
394 uses.
396 RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees);
398 ///< Computes the change in size of an object due to perspective (vertical FOV).
399 RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance);
401 RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize,
402 RCL_Unit scaledSize);
404 RCL_Unit
405 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance);
407 RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize,
408 RCL_Unit scaledSize);
411 Casts rays for given camera view and for each hit calls a user provided
412 function.
414 void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
415 RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
416 RCL_RayConstraints constraints);
419 Using provided functions, renders a complete complex (multilevel) camera
420 view.
422 This function should render each screen pixel exactly once.
424 function rendering summary:
425 - performance: slower
426 - accuracy: higher
427 - wall textures: yes
428 - different wall heights: yes
429 - floor/ceiling textures: no
430 - floor geometry: yes, multilevel
431 - ceiling geometry: yes (optional), multilevel
432 - rolling door: no
433 - camera shearing: yes
434 - rendering order: left-to-right, not specifically ordered vertically
436 @param cam camera whose view to render
437 @param floorHeightFunc function that returns floor height (in RCL_Units)
438 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
439 0 (no ceiling will be rendered)
440 @param typeFunction function that says a type of square (e.g. its texture
441 index), can be 0 (no type in hit result)
442 @param pixelFunc callback function to draw a single pixel on screen
443 @param constraints constraints for each cast ray
445 void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
446 RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
447 RCL_RayConstraints constraints);
450 Renders given camera view, with help of provided functions. This function is
451 simpler and faster than RCL_renderComplex(...) and is meant to be rendering
452 flat levels.
454 function rendering summary:
455 - performance: faster
456 - accuracy: lower
457 - wall textures: yes
458 - different wall heights: yes
459 - floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
460 - floor geometry: no (just flat floor, with depth information)
461 - ceiling geometry: no (just flat ceiling, with depth information)
462 - rolling door: yes
463 - camera shearing: no
464 - rendering order: left-to-right, top-to-bottom
466 Additionally this function supports rendering rolling doors.
468 This function should render each screen pixel exactly once.
470 @param rollFunc function that for given square says its door roll in
471 RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
472 -RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
473 rendering should also be faster as fewer intersections will be tested)
475 void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
476 RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
477 RCL_RayConstraints constraints);
480 Function that moves given camera and makes it collide with walls and
481 potentially also floor and ceilings. It's meant to help implement player
482 movement.
484 @param camera camera to move
485 @param planeOffset offset to move the camera in
486 @param heightOffset height offset to move the camera in
487 @param floorHeightFunc function used to retrieve the floor height
488 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
489 (camera won't collide with ceiling)
490 @param computeHeight whether to compute height - if false (0), floor and
491 ceiling functions won't be used and the camera will
492 only collide horizontally with walls (good for simpler
493 game, also faster)
494 @param force if true, forces to recompute collision even if position doesn't
495 change
497 void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
498 RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
499 RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force);
501 void RCL_initCamera(RCL_Camera *camera);
502 void RCL_initRayConstraints(RCL_RayConstraints *constraints);
504 //=============================================================================
505 // privates
507 #define _RCL_UNUSED(what) (void)(what);
509 // global helper variables, for precomputing stuff etc.
510 RCL_Camera _RCL_camera;
511 RCL_Unit _RCL_horizontalDepthStep = 0;
512 RCL_Unit _RCL_startFloorHeight = 0;
513 RCL_Unit _RCL_startCeil_Height = 0;
514 RCL_Unit _RCL_camResYLimit = 0;
515 RCL_Unit _RCL_middleRow = 0;
516 RCL_ArrayFunction _RCL_floorFunction = 0;
517 RCL_ArrayFunction _RCL_ceilFunction = 0;
518 RCL_Unit _RCL_fHorizontalDepthStart = 0;
519 RCL_Unit _RCL_cHorizontalDepthStart = 0;
520 int16_t _RCL_cameraHeightScreen = 0;
521 RCL_ArrayFunction _RCL_rollFunction = 0; // says door rolling
522 RCL_Unit *_RCL_floorPixelDistances = 0;
523 RCL_Unit _RCL_fovCorrectionFactors[2] = {0,0}; //correction for hor/vert fov
525 RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax)
527 if (value >= valueMin)
529 if (value <= valueMax)
530 return value;
531 else
532 return valueMax;
534 else
535 return valueMin;
538 static inline RCL_Unit RCL_abs(RCL_Unit value)
540 return value * (((value >= 0) << 1) - 1);
543 /// Like mod, but behaves differently for negative values.
544 static inline RCL_Unit RCL_wrap(RCL_Unit value, RCL_Unit mod)
546 RCL_Unit cmp = value < 0;
547 return cmp * mod + (value % mod) - cmp;
550 /// Performs division, rounding down, NOT towards zero.
551 static inline RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor)
553 return value / divisor - ((value >= 0) ? 0 : 1);
556 // Bhaskara's cosine approximation formula
557 #define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
558 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
559 (RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
561 #if RCL_USE_COS_LUT == 1
563 #ifdef RCL_RAYCAST_TINY
564 const RCL_Unit cosLUT[64] =
566 16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
568 #else
569 const RCL_Unit cosLUT[64] =
571 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
572 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
573 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
574 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
576 #endif
578 #elif RCL_USE_COS_LUT == 2
579 const RCL_Unit cosLUT[128] =
581 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
582 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
583 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
584 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
585 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
586 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
587 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
588 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
590 #endif
592 RCL_Unit RCL_cos(RCL_Unit input)
594 input = RCL_wrap(input,RCL_UNITS_PER_SQUARE);
596 #if RCL_USE_COS_LUT == 1
598 #ifdef RCL_RAYCAST_TINY
599 return cosLUT[input];
600 #else
601 return cosLUT[input / 16];
602 #endif
604 #elif RCL_USE_COS_LUT == 2
605 return cosLUT[input / 8];
606 #else
607 if (input < RCL_UNITS_PER_SQUARE / 4)
608 return trigHelper(input);
609 else if (input < RCL_UNITS_PER_SQUARE / 2)
610 return -1 * trigHelper(RCL_UNITS_PER_SQUARE / 2 - input);
611 else if (input < 3 * RCL_UNITS_PER_SQUARE / 4)
612 return -1 * trigHelper(input - RCL_UNITS_PER_SQUARE / 2);
613 else
614 return trigHelper(RCL_UNITS_PER_SQUARE - input);
615 #endif
618 #undef trigHelper
620 RCL_Unit RCL_sin(RCL_Unit input)
622 return RCL_cos(input - RCL_UNITS_PER_SQUARE / 4);
625 RCL_Unit RCL_tan(RCL_Unit input)
627 return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_nonZero(RCL_cos(input)
630 return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_nonZero(RCL_cos(input));
633 RCL_Unit RCL_ctg(RCL_Unit input)
635 return (RCL_cos(input) * RCL_UNITS_PER_SQUARE) / RCL_sin(input);
638 RCL_Vector2D RCL_angleToDirection(RCL_Unit angle)
640 RCL_Vector2D result;
642 result.x = RCL_cos(angle);
643 result.y = -1 * RCL_sin(angle);
645 return result;
648 uint16_t RCL_sqrt(RCL_Unit value)
650 #ifdef RCL_RAYCAST_TINY
651 uint16_t result = 0;
652 uint16_t a = value;
653 uint16_t b = 1u << 14;
654 #else
655 uint32_t result = 0;
656 uint32_t a = value;
657 uint32_t b = 1u << 30;
658 #endif
660 while (b > a)
661 b >>= 2;
663 while (b != 0)
665 if (a >= result + b)
667 a -= result + b;
668 result = result + 2 * b;
671 b >>= 2;
672 result >>= 1;
675 return result;
678 RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2)
680 RCL_Unit dx = p2.x - p1.x;
681 RCL_Unit dy = p2.y - p1.y;
683 #if RCL_USE_DIST_APPROX == 2
684 // octagonal approximation
686 dx = RCL_abs(dx);
687 dy = RCL_abs(dy);
689 return dy > dx ? dx / 2 + dy : dy / 2 + dx;
690 #elif RCL_USE_DIST_APPROX == 1
691 // more accurate approximation
693 RCL_Unit a, b, result;
695 dx = ((dx < 0) * 2 - 1) * dx;
696 dy = ((dy < 0) * 2 - 1) * dy;
698 if (dx < dy)
700 a = dy;
701 b = dx;
703 else
705 a = dx;
706 b = dy;
709 result = a + (44 * b) / 102;
711 if (a < (b << 4))
712 result -= (5 * a) / 128;
714 return result;
715 #else
716 dx = dx * dx;
717 dy = dy * dy;
719 return RCL_sqrt((RCL_Unit) (dx + dy));
720 #endif
723 RCL_Unit RCL_len(RCL_Vector2D v)
725 RCL_Vector2D zero;
726 zero.x = 0;
727 zero.y = 0;
729 return RCL_dist(zero,v);
732 static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point, RCL_Ray ray)
734 RCL_Unit dX = point.x - ray.start.x;
735 RCL_Unit dY = point.y - ray.start.y;
736 return (ray.direction.x * dY - ray.direction.y * dX) > 0;
737 // ^ Z component of cross-product
740 void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
741 RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
742 uint16_t *hitResultsLen, RCL_RayConstraints constraints)
744 RCL_Vector2D currentPos = ray.start;
745 RCL_Vector2D currentSquare;
747 currentSquare.x = RCL_divRoundDown(ray.start.x,RCL_UNITS_PER_SQUARE);
748 currentSquare.y = RCL_divRoundDown(ray.start.y,RCL_UNITS_PER_SQUARE);
750 *hitResultsLen = 0;
752 RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
754 // DDA variables
755 RCL_Vector2D nextSideDist; // dist. from start to the next side in given axis
756 RCL_Vector2D delta;
757 RCL_Vector2D step; // -1 or 1 for each axis
758 int8_t stepHorizontal = 0; // whether the last step was hor. or vert.
760 nextSideDist.x = 0;
761 nextSideDist.y = 0;
763 RCL_Unit dirVecLengthNorm = RCL_len(ray.direction) * RCL_UNITS_PER_SQUARE;
765 delta.x = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.x));
766 delta.y = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.y));
768 // init DDA
770 if (ray.direction.x < 0)
772 step.x = -1;
773 nextSideDist.x = (RCL_wrap(ray.start.x,RCL_UNITS_PER_SQUARE) * delta.x) /
774 RCL_UNITS_PER_SQUARE;
776 else
778 step.x = 1;
779 nextSideDist.x =
780 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.x,RCL_UNITS_PER_SQUARE)) *
781 delta.x) / RCL_UNITS_PER_SQUARE;
784 if (ray.direction.y < 0)
786 step.y = -1;
787 nextSideDist.y = (RCL_wrap(ray.start.y,RCL_UNITS_PER_SQUARE) * delta.y) /
788 RCL_UNITS_PER_SQUARE;
790 else
792 step.y = 1;
793 nextSideDist.y =
794 ((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.y,RCL_UNITS_PER_SQUARE)) *
795 delta.y) / RCL_UNITS_PER_SQUARE;
798 // DDA loop
800 #define RECIP_SCALE 65536
802 RCL_Unit rayDirXRecip = RECIP_SCALE / RCL_nonZero(ray.direction.x);
803 RCL_Unit rayDirYRecip = RECIP_SCALE / RCL_nonZero(ray.direction.y);
804 // ^ we precompute reciprocals to avoid divisions in the loop
806 for (uint16_t i = 0; i < constraints.maxSteps; ++i)
808 RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y);
810 if (RCL_unlikely(currentType != squareType))
812 // collision
814 RCL_HitResult h;
816 h.arrayValue = currentType;
817 h.doorRoll = 0;
818 h.position = currentPos;
819 h.square = currentSquare;
821 if (stepHorizontal)
823 h.position.x = currentSquare.x * RCL_UNITS_PER_SQUARE;
824 h.direction = 3;
826 if (step.x == -1)
828 h.direction = 1;
829 h.position.x += RCL_UNITS_PER_SQUARE;
832 RCL_Unit diff = h.position.x - ray.start.x;
834 h.position.y = // avoid division by multiplying with reciprocal
835 ray.start.y + (ray.direction.y * diff * rayDirXRecip) / RECIP_SCALE;
837 #if RCL_RECTILINEAR
838 /* Here we compute the fish eye corrected distance (perpendicular to
839 the projection plane) as the Euclidean distance (of hit from camera
840 position) divided by the length of the ray direction vector. This can
841 be computed without actually computing Euclidean distances as a
842 hypothenuse A (distance) divided by hypothenuse B (length) is equal to
843 leg A (distance along principal axis) divided by leg B (length along
844 the same principal axis). */
846 #define CORRECT(dir1,dir2)\
847 RCL_Unit tmp = diff / 4; /* 4 to prevent overflow */ \
848 h.distance = ((tmp / 8) != 0) ? /* prevent a bug with small dists */ \
849 ((tmp * RCL_UNITS_PER_SQUARE * rayDir ## dir1 ## Recip) / (RECIP_SCALE / 4)):\
850 RCL_abs(h.position.dir2 - ray.start.dir2);
852 CORRECT(X,y)
854 #endif // RCL_RECTILINEAR
856 else
858 h.position.y = currentSquare.y * RCL_UNITS_PER_SQUARE;
859 h.direction = 2;
861 if (step.y == -1)
863 h.direction = 0;
864 h.position.y += RCL_UNITS_PER_SQUARE;
867 RCL_Unit diff = h.position.y - ray.start.y;
869 h.position.x =
870 ray.start.x + (ray.direction.x * diff * rayDirYRecip) / RECIP_SCALE;
872 #if RCL_RECTILINEAR
874 CORRECT(Y,x) // same as above but for different axis
876 #undef CORRECT
878 #endif // RCL_RECTILINEAR
881 #if !RCL_RECTILINEAR
882 h.distance = RCL_dist(h.position,ray.start);
883 #endif
884 if (typeFunc != 0)
885 h.type = typeFunc(currentSquare.x,currentSquare.y);
887 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
888 switch (h.direction)
890 case 0: h.textureCoord =
891 RCL_wrap(-1 * h.position.x,RCL_UNITS_PER_SQUARE); break;
893 case 1: h.textureCoord =
894 RCL_wrap(h.position.y,RCL_UNITS_PER_SQUARE); break;
896 case 2: h.textureCoord =
897 RCL_wrap(h.position.x,RCL_UNITS_PER_SQUARE); break;
899 case 3: h.textureCoord =
900 RCL_wrap(-1 * h.position.y,RCL_UNITS_PER_SQUARE); break;
902 default: h.textureCoord = 0; break;
905 if (_RCL_rollFunction != 0)
907 h.doorRoll = _RCL_rollFunction(currentSquare.x,currentSquare.y);
909 if (h.direction == 0 || h.direction == 1)
910 h.doorRoll *= -1;
913 #else
914 h.textureCoord = 0;
915 #endif
917 hitResults[*hitResultsLen] = h;
919 *hitResultsLen += 1;
921 squareType = currentType;
923 if (*hitResultsLen >= constraints.maxHits)
924 break;
927 // DDA step
929 if (nextSideDist.x < nextSideDist.y)
931 nextSideDist.x += delta.x;
932 currentSquare.x += step.x;
933 stepHorizontal = 1;
935 else
937 nextSideDist.y += delta.y;
938 currentSquare.y += step.y;
939 stepHorizontal = 0;
944 RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc)
946 RCL_HitResult result;
947 uint16_t len;
948 RCL_RayConstraints c;
950 c.maxSteps = 1000;
951 c.maxHits = 1;
953 RCL_castRayMultiHit(ray,arrayFunc,0,&result,&len,c);
955 if (len == 0)
956 result.distance = -1;
958 return result;
961 void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
962 RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
963 RCL_RayConstraints constraints)
965 RCL_Vector2D dir1 =
966 RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF);
968 RCL_Vector2D dir2 =
969 RCL_angleToDirection(cam.direction + RCL_HORIZONTAL_FOV_HALF);
971 /* We scale the side distances so that the middle one is
972 RCL_UNITS_PER_SQUARE, which has to be this way. */
974 RCL_Unit cos = RCL_nonZero(RCL_cos(RCL_HORIZONTAL_FOV_HALF));
976 dir1.x = (dir1.x * RCL_UNITS_PER_SQUARE) / cos;
977 dir1.y = (dir1.y * RCL_UNITS_PER_SQUARE) / cos;
979 dir2.x = (dir2.x * RCL_UNITS_PER_SQUARE) / cos;
980 dir2.y = (dir2.y * RCL_UNITS_PER_SQUARE) / cos;
982 RCL_Unit dX = dir2.x - dir1.x;
983 RCL_Unit dY = dir2.y - dir1.y;
985 RCL_HitResult hits[constraints.maxHits];
986 uint16_t hitCount;
988 RCL_Ray r;
989 r.start = cam.position;
991 RCL_Unit currentDX = 0;
992 RCL_Unit currentDY = 0;
994 for (int16_t i = 0; i < cam.resolution.x; ++i)
996 /* Here by linearly interpolating the direction vector its length changes,
997 which in result achieves correcting the fish eye effect (computing
998 perpendicular distance). */
1000 r.direction.x = dir1.x + currentDX / cam.resolution.x;
1001 r.direction.y = dir1.y + currentDY / cam.resolution.x;
1003 RCL_castRayMultiHit(r,arrayFunc,typeFunction,hits,&hitCount,constraints);
1005 columnFunc(hits,hitCount,i,r);
1007 currentDX += dX;
1008 currentDY += dY;
1013 Helper function that determines intersection with both ceiling and floor.
1015 RCL_Unit _RCL_floorCeilFunction(int16_t x, int16_t y)
1017 RCL_Unit f = _RCL_floorFunction(x,y);
1019 if (_RCL_ceilFunction == 0)
1020 return f;
1022 RCL_Unit c = _RCL_ceilFunction(x,y);
1024 #ifndef RCL_RAYCAST_TINY
1025 return ((f & 0x0000ffff) << 16) | (c & 0x0000ffff);
1026 #else
1027 return ((f & 0x00ff) << 8) | (c & 0x00ff);
1028 #endif
1031 RCL_Unit _floorHeightNotZeroFunction(int16_t x, int16_t y)
1033 return _RCL_floorFunction(x,y) == 0 ? 0 :
1034 RCL_nonZero((x & 0x00FF) | ((y & 0x00FF) << 8));
1035 // ^ this makes collisions between all squares - needed for rolling doors
1038 RCL_Unit RCL_adjustDistance(RCL_Unit distance, RCL_Camera *camera,
1039 RCL_Ray *ray)
1041 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
1042 possibly be computed more efficiently by not computing Euclidean
1043 distance at all, but rather compute the distance of the collision
1044 point from the projection plane (line). */
1046 RCL_Unit result =
1047 (distance *
1048 RCL_vectorsAngleCos(RCL_angleToDirection(camera->direction),
1049 ray->direction)) / RCL_UNITS_PER_SQUARE;
1051 return RCL_nonZero(result);
1052 // ^ prevent division by zero
1055 /// Helper for drawing floor or ceiling. Returns the last drawn pixel position.
1056 static inline int16_t _RCL_drawHorizontalColumn(
1057 RCL_Unit yCurrent,
1058 RCL_Unit yTo,
1059 RCL_Unit limit1, // TODO: int16_t?
1060 RCL_Unit limit2,
1061 RCL_Unit verticalOffset,
1062 int16_t increment,
1063 int8_t computeDepth,
1064 int8_t computeCoords,
1065 int16_t depthIncrementMultiplier,
1066 RCL_Ray *ray,
1067 RCL_PixelInfo *pixelInfo
1070 _RCL_UNUSED(ray);
1072 RCL_Unit depthIncrement;
1073 RCL_Unit dx;
1074 RCL_Unit dy;
1076 pixelInfo->isWall = 0;
1078 int16_t limit = RCL_clamp(yTo,limit1,limit2);
1080 RCL_Unit depth = 0; /* TODO: this is for clamping depth to 0 so that we don't
1081 have negative depths, but we should do it more
1082 elegantly and efficiently */
1084 _RCL_UNUSED(depth);
1086 /* for performance reasons have different version of the critical loop
1087 to be able to branch early */
1088 #define loop(doDepth,doCoords)\
1090 if (doDepth) /*constant condition - compiler should optimize it out*/\
1092 depth = pixelInfo->depth + RCL_abs(verticalOffset) *\
1093 RCL_VERTICAL_DEPTH_MULTIPLY;\
1094 depthIncrement = depthIncrementMultiplier *\
1095 _RCL_horizontalDepthStep;\
1097 if (doCoords) /*constant condition - compiler should optimize it out*/\
1099 dx = pixelInfo->hit.position.x - _RCL_camera.position.x;\
1100 dy = pixelInfo->hit.position.y - _RCL_camera.position.y;\
1102 for (int16_t i = yCurrent + increment;\
1103 increment == -1 ? i >= limit : i <= limit; /* TODO: is efficient? */\
1104 i += increment)\
1106 pixelInfo->position.y = i;\
1107 if (doDepth) /*constant condition - compiler should optimize it out*/\
1109 depth += depthIncrement;\
1110 pixelInfo->depth = RCL_zeroClamp(depth); \
1111 /* ^ int comparison is fast, it is not braching! (= test instr.) */\
1113 if (doCoords) /*constant condition - compiler should optimize it out*/\
1115 RCL_Unit d = _RCL_floorPixelDistances[i];\
1116 RCL_Unit d2 = RCL_nonZero(pixelInfo->hit.distance);\
1117 pixelInfo->texCoords.x =\
1118 _RCL_camera.position.x + ((d * dx) / d2);\
1119 pixelInfo->texCoords.y =\
1120 _RCL_camera.position.y + ((d * dy) / d2);\
1122 RCL_PIXEL_FUNCTION(pixelInfo);\
1126 if (computeDepth) // branch early
1128 if (!computeCoords)
1129 loop(1,0)
1130 else
1131 loop(1,1)
1133 else
1135 if (!computeCoords)
1136 loop(0,0)
1137 else
1138 loop(1,1)
1141 #undef loop
1143 return limit;
1146 /// Helper for drawing walls. Returns the last drawn pixel position.
1147 static inline int16_t _RCL_drawWall(
1148 RCL_Unit yCurrent,
1149 RCL_Unit yFrom,
1150 RCL_Unit yTo,
1151 RCL_Unit limit1, // TODO: int16_t?
1152 RCL_Unit limit2,
1153 RCL_Unit height,
1154 int16_t increment,
1155 RCL_PixelInfo *pixelInfo
1158 _RCL_UNUSED(height)
1160 height = RCL_abs(height);
1162 pixelInfo->isWall = 1;
1164 RCL_Unit limit = RCL_clamp(yTo,limit1,limit2);
1166 RCL_Unit wallLength = RCL_nonZero(RCL_abs(yTo - yFrom - 1));
1168 RCL_Unit wallPosition = RCL_abs(yFrom - yCurrent) - increment;
1170 RCL_Unit heightScaled = height * RCL_TEXTURE_INTERPOLATION_SCALE;
1171 _RCL_UNUSED(heightScaled);
1173 RCL_Unit coordStepScaled = RCL_COMPUTE_WALL_TEXCOORDS ?
1174 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1175 ((RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE) / wallLength)
1176 #else
1177 (heightScaled / wallLength)
1178 #endif
1179 : 0;
1181 pixelInfo->texCoords.y = RCL_COMPUTE_WALL_TEXCOORDS ?
1182 (wallPosition * coordStepScaled) : 0;
1184 if (increment < 0)
1186 coordStepScaled *= -1;
1187 pixelInfo->texCoords.y =
1188 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1189 (RCL_UNITS_PER_SQUARE * RCL_TEXTURE_INTERPOLATION_SCALE)
1190 - pixelInfo->texCoords.y;
1191 #else
1192 heightScaled - pixelInfo->texCoords.y;
1193 #endif
1195 else
1197 // with floor wall, don't start under 0
1198 pixelInfo->texCoords.y = RCL_zeroClamp(pixelInfo->texCoords.y);
1201 RCL_Unit textureCoordScaled = pixelInfo->texCoords.y;
1203 for (RCL_Unit i = yCurrent + increment;
1204 increment == -1 ? i >= limit : i <= limit; // TODO: is efficient?
1205 i += increment)
1207 pixelInfo->position.y = i;
1209 #if RCL_COMPUTE_WALL_TEXCOORDS == 1
1210 pixelInfo->texCoords.y =
1211 textureCoordScaled / RCL_TEXTURE_INTERPOLATION_SCALE;
1213 textureCoordScaled += coordStepScaled;
1214 #endif
1216 RCL_PIXEL_FUNCTION(pixelInfo);
1219 return limit;
1222 /// Fills a RCL_HitResult struct with info for a hit at infinity.
1223 static inline void _RCL_makeInfiniteHit(RCL_HitResult *hit, RCL_Ray *ray)
1225 hit->distance = RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE;
1226 /* ^ horizon is at infinity, but we can't use too big infinity
1227 (RCL_INFINITY) because it would overflow in the following mult. */
1228 hit->position.x = (ray->direction.x * hit->distance) / RCL_UNITS_PER_SQUARE;
1229 hit->position.y = (ray->direction.y * hit->distance) / RCL_UNITS_PER_SQUARE;
1231 hit->direction = 0;
1232 hit->textureCoord = 0;
1233 hit->arrayValue = 0;
1234 hit->doorRoll = 0;
1235 hit->type = 0;
1238 void _RCL_columnFunctionComplex(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
1239 RCL_Ray ray)
1241 // last written Y position, can never go backwards
1242 RCL_Unit fPosY = _RCL_camera.resolution.y;
1243 RCL_Unit cPosY = -1;
1245 // world coordinates (relative to camera height though)
1246 RCL_Unit fZ1World = _RCL_startFloorHeight;
1247 RCL_Unit cZ1World = _RCL_startCeil_Height;
1249 RCL_PixelInfo p;
1250 p.position.x = x;
1251 p.height = 0;
1252 p.wallHeight = 0;
1253 p.texCoords.x = 0;
1254 p.texCoords.y = 0;
1256 // we'll be simulatenously drawing the floor and the ceiling now
1257 for (RCL_Unit j = 0; j <= hitCount; ++j)
1258 { // ^ = add extra iteration for horizon plane
1259 int8_t drawingHorizon = j == hitCount;
1261 RCL_HitResult hit;
1262 RCL_Unit distance = 1;
1264 RCL_Unit fWallHeight = 0, cWallHeight = 0;
1265 RCL_Unit fZ2World = 0, cZ2World = 0;
1266 RCL_Unit fZ1Screen = 0, cZ1Screen = 0;
1267 RCL_Unit fZ2Screen = 0, cZ2Screen = 0;
1269 if (!drawingHorizon)
1271 hit = hits[j];
1272 distance = RCL_nonZero(hit.distance);
1273 p.hit = hit;
1275 fWallHeight = _RCL_floorFunction(hit.square.x,hit.square.y);
1276 fZ2World = fWallHeight - _RCL_camera.height;
1277 fZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1278 (fZ1World * _RCL_camera.resolution.y) /
1279 RCL_UNITS_PER_SQUARE,distance);
1280 fZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1281 (fZ2World * _RCL_camera.resolution.y) /
1282 RCL_UNITS_PER_SQUARE,distance);
1284 if (_RCL_ceilFunction != 0)
1286 cWallHeight = _RCL_ceilFunction(hit.square.x,hit.square.y);
1287 cZ2World = cWallHeight - _RCL_camera.height;
1288 cZ1Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1289 (cZ1World * _RCL_camera.resolution.y) /
1290 RCL_UNITS_PER_SQUARE,distance);
1291 cZ2Screen = _RCL_middleRow - RCL_perspectiveScaleVertical(
1292 (cZ2World * _RCL_camera.resolution.y) /
1293 RCL_UNITS_PER_SQUARE,distance);
1296 else
1298 fZ1Screen = _RCL_middleRow;
1299 cZ1Screen = _RCL_middleRow + 1;
1300 _RCL_makeInfiniteHit(&p.hit,&ray);
1303 RCL_Unit limit;
1305 p.isWall = 0;
1306 p.isHorizon = drawingHorizon;
1308 // draw floor until wall
1309 p.isFloor = 1;
1310 p.height = fZ1World + _RCL_camera.height;
1311 p.wallHeight = 0;
1313 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1314 p.depth = (_RCL_fHorizontalDepthStart - fPosY) * _RCL_horizontalDepthStep;
1315 #else
1316 p.depth = 0;
1317 #endif
1319 limit = _RCL_drawHorizontalColumn(fPosY,fZ1Screen,cPosY + 1,
1320 _RCL_camera.resolution.y,fZ1World,-1,RCL_COMPUTE_FLOOR_DEPTH,
1321 // ^ purposfully allow outside screen bounds
1322 RCL_COMPUTE_FLOOR_TEXCOORDS && p.height == RCL_FLOOR_TEXCOORDS_HEIGHT,
1323 1,&ray,&p);
1325 if (fPosY > limit)
1326 fPosY = limit;
1328 if (_RCL_ceilFunction != 0 || drawingHorizon)
1330 // draw ceiling until wall
1331 p.isFloor = 0;
1332 p.height = cZ1World + _RCL_camera.height;
1334 #if RCL_COMPUTE_CEILING_DEPTH == 1
1335 p.depth = (cPosY - _RCL_cHorizontalDepthStart) *
1336 _RCL_horizontalDepthStep;
1337 #endif
1339 limit = _RCL_drawHorizontalColumn(cPosY,cZ1Screen,
1340 -1,fPosY - 1,cZ1World,1,RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p);
1341 // ^ purposfully allow outside screen bounds here
1343 if (cPosY < limit)
1344 cPosY = limit;
1347 if (!drawingHorizon) // don't draw walls for horizon plane
1349 p.isWall = 1;
1350 p.depth = distance;
1351 p.isFloor = 1;
1352 p.texCoords.x = hit.textureCoord;
1353 p.height = fZ1World + _RCL_camera.height;
1354 p.wallHeight = fWallHeight;
1356 // draw floor wall
1358 if (fPosY > 0) // still pixels left?
1360 p.isFloor = 1;
1362 limit = _RCL_drawWall(fPosY,fZ1Screen,fZ2Screen,cPosY + 1,
1363 _RCL_camera.resolution.y,
1364 // ^ purposfully allow outside screen bounds here
1365 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1366 RCL_UNITS_PER_SQUARE
1367 #else
1368 fZ2World - fZ1World
1369 #endif
1370 ,-1,&p);
1373 if (fPosY > limit)
1374 fPosY = limit;
1376 fZ1World = fZ2World; // for the next iteration
1377 } // ^ purposfully allow outside screen bounds here
1379 // draw ceiling wall
1381 if (_RCL_ceilFunction != 0 && cPosY < _RCL_camResYLimit) // pixels left?
1383 p.isFloor = 0;
1384 p.height = cZ1World + _RCL_camera.height;
1385 p.wallHeight = cWallHeight;
1387 limit = _RCL_drawWall(cPosY,cZ1Screen,cZ2Screen,
1388 -1,fPosY - 1,
1389 // ^ puposfully allow outside screen bounds here
1390 #if RCL_TEXTURE_VERTICAL_STRETCH == 1
1391 RCL_UNITS_PER_SQUARE
1392 #else
1393 cZ1World - cZ2World
1394 #endif
1395 ,1,&p);
1397 if (cPosY < limit)
1398 cPosY = limit;
1400 cZ1World = cZ2World; // for the next iteration
1401 } // ^ puposfully allow outside screen bounds here
1406 void _RCL_columnFunctionSimple(RCL_HitResult *hits, uint16_t hitCount,
1407 uint16_t x, RCL_Ray ray)
1409 RCL_Unit y = 0;
1410 RCL_Unit wallHeightScreen = 0;
1411 RCL_Unit wallStart = _RCL_middleRow;
1413 RCL_Unit dist = 1;
1415 RCL_PixelInfo p;
1416 p.position.x = x;
1417 p.wallHeight = RCL_UNITS_PER_SQUARE;
1419 if (hitCount > 0)
1421 RCL_HitResult hit = hits[0];
1423 uint8_t goOn = 1;
1425 if (_RCL_rollFunction != 0 && RCL_COMPUTE_WALL_TEXCOORDS == 1)
1427 if (hit.arrayValue == 0)
1429 // standing inside door square, looking out => move to the next hit
1431 if (hitCount > 1)
1432 hit = hits[1];
1433 else
1434 goOn = 0;
1436 else
1438 // normal hit, check the door roll
1440 RCL_Unit texCoordMod = hit.textureCoord % RCL_UNITS_PER_SQUARE;
1442 int8_t unrolled = hit.doorRoll >= 0 ?
1443 (hit.doorRoll > texCoordMod) :
1444 (texCoordMod > RCL_UNITS_PER_SQUARE + hit.doorRoll);
1446 if (unrolled)
1448 goOn = 0;
1450 if (hitCount > 1) /* should probably always be true (hit on square
1451 exit) */
1453 if (hit.direction % 2 != hits[1].direction % 2)
1455 // hit on the inner side
1456 hit = hits[1];
1457 goOn = 1;
1459 else if (hitCount > 2)
1461 // hit on the opposite side
1462 hit = hits[2];
1463 goOn = 1;
1470 p.hit = hit;
1472 if (goOn)
1474 dist = hit.distance;
1476 RCL_Unit wallHeightWorld = _RCL_floorFunction(hit.square.x,hit.square.y);
1478 if (wallHeightWorld < 0)
1480 /* We can't just do wallHeightWorld = max(0,wallHeightWorld) because
1481 we would be processing an actual hit with height 0, which shouldn't
1482 ever happen, so we assign some arbitrary height. */
1484 wallHeightWorld = RCL_UNITS_PER_SQUARE;
1487 RCL_Unit worldPointTop = wallHeightWorld - _RCL_camera.height;
1488 RCL_Unit worldPointBottom = -1 * _RCL_camera.height;
1490 wallStart = _RCL_middleRow -
1491 (RCL_perspectiveScaleVertical(worldPointTop,dist)
1492 * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1494 int16_t wallEnd = _RCL_middleRow -
1495 (RCL_perspectiveScaleVertical(worldPointBottom,dist)
1496 * _RCL_camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1498 wallHeightScreen = wallEnd - wallStart;
1500 if (wallHeightScreen <= 0) // can happen because of rounding errors
1501 wallHeightScreen = 1;
1504 else
1506 _RCL_makeInfiniteHit(&p.hit,&ray);
1509 // draw ceiling
1511 p.isWall = 0;
1512 p.isFloor = 0;
1513 p.isHorizon = 1;
1514 p.depth = 1;
1515 p.height = RCL_UNITS_PER_SQUARE;
1517 y = _RCL_drawHorizontalColumn(-1,wallStart,-1,_RCL_middleRow,_RCL_camera.height,1,
1518 RCL_COMPUTE_CEILING_DEPTH,0,1,&ray,&p);
1520 // draw wall
1522 p.isWall = 1;
1523 p.isFloor = 1;
1524 p.depth = dist;
1525 p.height = 0;
1527 #if RCL_ROLL_TEXTURE_COORDS == 1 && RCL_COMPUTE_WALL_TEXCOORDS == 1
1528 p.hit.textureCoord -= p.hit.doorRoll;
1529 #endif
1531 p.texCoords.x = p.hit.textureCoord;
1532 p.texCoords.y = 0;
1534 RCL_Unit limit = _RCL_drawWall(y,wallStart,wallStart + wallHeightScreen - 1,
1535 -1,_RCL_camResYLimit,p.hit.arrayValue,1,&p);
1537 y = RCL_max(y,limit); // take max, in case no wall was drawn
1538 y = RCL_max(y,wallStart);
1540 // draw floor
1542 p.isWall = 0;
1544 #if RCL_COMPUTE_FLOOR_DEPTH == 1
1545 p.depth = (_RCL_camera.resolution.y - y) * _RCL_horizontalDepthStep + 1;
1546 #endif
1548 _RCL_drawHorizontalColumn(y,_RCL_camResYLimit,-1,_RCL_camResYLimit,
1549 _RCL_camera.height,1,RCL_COMPUTE_FLOOR_DEPTH,RCL_COMPUTE_FLOOR_TEXCOORDS,
1550 -1,&ray,&p);
1554 Precomputes a distance from camera to the floor at each screen row into an
1555 array (must be preallocated with sufficient (camera.resolution.y) length).
1557 static inline void _RCL_precomputeFloorDistances(RCL_Camera camera,
1558 RCL_Unit *dest, uint16_t startIndex)
1560 RCL_Unit camHeightScreenSize =
1561 (camera.height * camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1563 for (uint16_t i = startIndex; i < camera.resolution.y; ++i)
1564 dest[i] = RCL_perspectiveScaleVerticalInverse(camHeightScreenSize,
1565 RCL_abs(i - _RCL_middleRow));
1568 void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
1569 RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
1570 RCL_RayConstraints constraints)
1572 _RCL_floorFunction = floorHeightFunc;
1573 _RCL_ceilFunction = ceilingHeightFunc;
1574 _RCL_camera = cam;
1575 _RCL_camResYLimit = cam.resolution.y - 1;
1577 uint16_t halfResY = cam.resolution.y / 2;
1579 _RCL_middleRow = halfResY + cam.shear;
1581 _RCL_fHorizontalDepthStart = _RCL_middleRow + halfResY;
1582 _RCL_cHorizontalDepthStart = _RCL_middleRow - halfResY;
1584 _RCL_startFloorHeight = floorHeightFunc(
1585 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1586 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height;
1588 _RCL_startCeil_Height =
1589 ceilingHeightFunc != 0 ?
1590 ceilingHeightFunc(
1591 RCL_divRoundDown(cam.position.x,RCL_UNITS_PER_SQUARE),
1592 RCL_divRoundDown(cam.position.y,RCL_UNITS_PER_SQUARE)) -1 * cam.height
1593 : RCL_INFINITY;
1595 _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y;
1597 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1598 RCL_Unit floorPixelDistances[cam.resolution.y];
1599 _RCL_precomputeFloorDistances(cam,floorPixelDistances,0);
1600 _RCL_floorPixelDistances = floorPixelDistances; // pass to column function
1601 #endif
1603 RCL_castRaysMultiHit(cam,_RCL_floorCeilFunction,typeFunction,
1604 _RCL_columnFunctionComplex,constraints);
1607 void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
1608 RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
1609 RCL_RayConstraints constraints)
1611 _RCL_floorFunction = floorHeightFunc;
1612 _RCL_camera = cam;
1613 _RCL_camResYLimit = cam.resolution.y - 1;
1614 _RCL_middleRow = cam.resolution.y / 2;
1615 _RCL_rollFunction = rollFunc;
1617 _RCL_cameraHeightScreen =
1618 (_RCL_camera.resolution.y * (_RCL_camera.height - RCL_UNITS_PER_SQUARE)) /
1619 RCL_UNITS_PER_SQUARE;
1621 _RCL_horizontalDepthStep = RCL_HORIZON_DEPTH / cam.resolution.y;
1623 constraints.maxHits =
1624 _RCL_rollFunction == 0 ?
1625 1 : // no door => 1 hit is enough
1626 3; // for correctly rendering rolling doors we'll need 3 hits (NOT 2)
1628 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1629 RCL_Unit floorPixelDistances[cam.resolution.y];
1630 _RCL_precomputeFloorDistances(cam,floorPixelDistances,_RCL_middleRow);
1631 _RCL_floorPixelDistances = floorPixelDistances; // pass to column function
1632 #endif
1634 RCL_castRaysMultiHit(cam,_floorHeightNotZeroFunction,typeFunc,
1635 _RCL_columnFunctionSimple, constraints);
1637 #if RCL_COMPUTE_FLOOR_TEXCOORDS == 1
1638 _RCL_floorPixelDistances = 0;
1639 #endif
1642 RCL_Vector2D RCL_normalize(RCL_Vector2D v)
1644 RCL_Vector2D result;
1645 RCL_Unit l = RCL_len(v);
1646 l = RCL_nonZero(l);
1648 result.x = (v.x * RCL_UNITS_PER_SQUARE) / l;
1649 result.y = (v.y * RCL_UNITS_PER_SQUARE) / l;
1651 return result;
1654 RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2)
1656 v1 = RCL_normalize(v1);
1657 v2 = RCL_normalize(v2);
1659 return (v1.x * v2.x + v1.y * v2.y) / RCL_UNITS_PER_SQUARE;
1663 RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
1664 RCL_Camera camera)
1666 RCL_PixelInfo result;
1668 RCL_Vector2D toPoint;
1670 toPoint.x = worldPosition.x - camera.position.x;
1671 toPoint.y = worldPosition.y - camera.position.y;
1673 RCL_Unit middleColumn = camera.resolution.x / 2;
1675 // rotate the point to camera space (y left/right, x forw/backw)
1677 RCL_Unit cos = RCL_cos(camera.direction);
1678 RCL_Unit sin = RCL_sin(camera.direction);
1680 RCL_Unit tmp = toPoint.x;
1682 toPoint.x = (toPoint.x * cos - toPoint.y * sin) / RCL_UNITS_PER_SQUARE;
1683 toPoint.y = (tmp * sin + toPoint.y * cos) / RCL_UNITS_PER_SQUARE;
1685 result.depth = toPoint.x;
1687 result.position.x = middleColumn -
1688 (RCL_perspectiveScaleHorizontal(toPoint.y,result.depth) * middleColumn) /
1689 RCL_UNITS_PER_SQUARE;
1691 result.position.y =
1692 (RCL_perspectiveScaleVertical(height - camera.height,result.depth)
1693 * camera.resolution.y) / RCL_UNITS_PER_SQUARE;
1695 result.position.y = camera.resolution.y / 2 - result.position.y + camera.shear;
1697 return result;
1700 RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees)
1702 return (degrees * RCL_UNITS_PER_SQUARE) / 360;
1706 Ugly temporary hack to solve mapping to screen. This function computes
1707 (approximately, usin a table) a divisor needed for FOV correction.
1709 RCL_Unit _RCL_fovCorrectionFactor(RCL_Unit fov)
1711 uint16_t table[9] =
1712 {1,208,408,692,1024,1540,2304,5376,30000};
1714 fov = RCL_min(RCL_UNITS_PER_SQUARE / 2 - 1,fov);
1716 uint8_t index = fov / 64;
1717 uint32_t t = ((fov - index * 64) * RCL_UNITS_PER_SQUARE) / 64;
1718 uint32_t v1 = table[index];
1719 uint32_t v2 = table[index + 1];
1721 return v1 + ((v2 - v1) * t) / RCL_UNITS_PER_SQUARE;
1724 RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance)
1726 if (_RCL_fovCorrectionFactors[1] == 0)
1727 _RCL_fovCorrectionFactors[1] = _RCL_fovCorrectionFactor(RCL_VERTICAL_FOV);
1729 return distance != 0 ? ((originalSize * RCL_UNITS_PER_SQUARE) /
1730 RCL_nonZero((_RCL_fovCorrectionFactors[1] * distance) / RCL_UNITS_PER_SQUARE)
1731 ) : 0;
1734 RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize,
1735 RCL_Unit scaledSize)
1737 if (_RCL_fovCorrectionFactors[1] == 0)
1738 _RCL_fovCorrectionFactors[1] = _RCL_fovCorrectionFactor(RCL_VERTICAL_FOV);
1740 return scaledSize != 0 ?
1742 ((originalSize * RCL_UNITS_PER_SQUARE) /
1743 RCL_nonZero((_RCL_fovCorrectionFactors[1] * scaledSize)
1744 / RCL_UNITS_PER_SQUARE)) : RCL_INFINITY;
1747 RCL_Unit
1748 RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance)
1750 if (_RCL_fovCorrectionFactors[0] == 0)
1751 _RCL_fovCorrectionFactors[0] = _RCL_fovCorrectionFactor(RCL_HORIZONTAL_FOV);
1753 return distance != 0 ?
1754 ((originalSize * RCL_UNITS_PER_SQUARE) /
1755 RCL_nonZero((_RCL_fovCorrectionFactors[0] * distance) / RCL_UNITS_PER_SQUARE)
1756 ) : 0;
1759 RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize,
1760 RCL_Unit scaledSize)
1762 // TODO: probably doesn't work
1764 return scaledSize != 0 ?
1765 (originalSize * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2) /
1766 ((RCL_HORIZONTAL_FOV_TAN * 2 * scaledSize) / RCL_UNITS_PER_SQUARE)
1767 : RCL_INFINITY;
1770 RCL_Unit RCL_castRay3D(
1771 RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2,
1772 RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc,
1773 RCL_RayConstraints constraints)
1775 RCL_HitResult hits[constraints.maxHits];
1776 uint16_t numHits;
1778 RCL_Ray ray;
1780 ray.start = pos1;
1782 RCL_Unit distance;
1784 ray.direction.x = pos2.x - pos1.x;
1785 ray.direction.y = pos2.y - pos1.y;
1787 distance = RCL_nonZero(RCL_len(ray.direction));
1789 ray.direction = RCL_normalize(ray.direction);
1791 RCL_Unit heightDiff = height2 - height1;
1793 RCL_castRayMultiHit(ray,floorHeightFunc,0,hits,&numHits,constraints);
1795 RCL_Unit result = RCL_UNITS_PER_SQUARE;
1797 int16_t squareX = RCL_divRoundDown(pos1.x,RCL_UNITS_PER_SQUARE);
1798 int16_t squareY = RCL_divRoundDown(pos1.y,RCL_UNITS_PER_SQUARE);
1800 RCL_Unit startHeight = floorHeightFunc(squareX,squareY);
1802 #define checkHits(comp,res) \
1804 RCL_Unit currentHeight = startHeight; \
1805 for (uint16_t i = 0; i < numHits; ++i) \
1807 if (hits[i].distance > distance) \
1808 break;\
1809 RCL_Unit h = hits[i].arrayValue; \
1810 if ((currentHeight comp h ? currentHeight : h) \
1811 comp (height1 + (hits[i].distance * heightDiff) / distance)) \
1813 res = (hits[i].distance * RCL_UNITS_PER_SQUARE) / distance; \
1814 break; \
1816 currentHeight = h; \
1820 checkHits(>,result)
1822 if (ceilingHeightFunc != 0)
1824 RCL_Unit result2 = RCL_UNITS_PER_SQUARE;
1826 startHeight = ceilingHeightFunc(squareX,squareY);
1828 RCL_castRayMultiHit(ray,ceilingHeightFunc,0,hits,&numHits,constraints);
1830 checkHits(<,result2)
1832 if (result2 < result)
1833 result = result2;
1836 #undef checkHits
1838 return result;
1841 void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
1842 RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
1843 RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force)
1845 int8_t movesInPlane = planeOffset.x != 0 || planeOffset.y != 0;
1847 if (movesInPlane || force)
1849 int16_t xSquareNew, ySquareNew;
1851 RCL_Vector2D corner; // BBox corner in the movement direction
1852 RCL_Vector2D cornerNew;
1854 int16_t xDir = planeOffset.x > 0 ? 1 : -1;
1855 int16_t yDir = planeOffset.y > 0 ? 1 : -1;
1857 corner.x = camera->position.x + xDir * RCL_CAMERA_COLL_RADIUS;
1858 corner.y = camera->position.y + yDir * RCL_CAMERA_COLL_RADIUS;
1860 int16_t xSquare = RCL_divRoundDown(corner.x,RCL_UNITS_PER_SQUARE);
1861 int16_t ySquare = RCL_divRoundDown(corner.y,RCL_UNITS_PER_SQUARE);
1863 cornerNew.x = corner.x + planeOffset.x;
1864 cornerNew.y = corner.y + planeOffset.y;
1866 xSquareNew = RCL_divRoundDown(cornerNew.x,RCL_UNITS_PER_SQUARE);
1867 ySquareNew = RCL_divRoundDown(cornerNew.y,RCL_UNITS_PER_SQUARE);
1869 RCL_Unit bottomLimit = -1 * RCL_INFINITY;
1870 RCL_Unit topLimit = RCL_INFINITY;
1872 RCL_Unit currCeilHeight = RCL_INFINITY;
1874 if (computeHeight)
1876 bottomLimit = camera->height - RCL_CAMERA_COLL_HEIGHT_BELOW +
1877 RCL_CAMERA_COLL_STEP_HEIGHT;
1879 topLimit = camera->height + RCL_CAMERA_COLL_HEIGHT_ABOVE;
1881 if (ceilingHeightFunc != 0)
1882 currCeilHeight = ceilingHeightFunc(xSquare,ySquare);
1885 // checks a single square for collision against the camera
1886 #define collCheck(dir,s1,s2)\
1887 if (computeHeight)\
1889 RCL_Unit height = floorHeightFunc(s1,s2);\
1890 if (height > bottomLimit || \
1891 currCeilHeight - height < \
1892 RCL_CAMERA_COLL_HEIGHT_BELOW + RCL_CAMERA_COLL_HEIGHT_ABOVE)\
1893 dir##Collides = 1;\
1894 else if (ceilingHeightFunc != 0)\
1896 RCL_Unit height2 = ceilingHeightFunc(s1,s2);\
1897 if ((height2 < topLimit) || ((height2 - height) < \
1898 (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))\
1899 dir##Collides = 1;\
1902 else\
1903 dir##Collides = floorHeightFunc(s1,s2) > RCL_CAMERA_COLL_STEP_HEIGHT;
1905 // check collision against non-diagonal square
1906 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1907 if (dir##SquareNew != dir##Square)\
1909 collCheck(dir,s1,s2)\
1911 if (!dir##Collides)\
1912 { /* now also check for coll on the neighbouring square */ \
1913 int16_t dir2##Square2 = RCL_divRoundDown(corner.dir2 - dir2##Dir *\
1914 RCL_CAMERA_COLL_RADIUS * 2,RCL_UNITS_PER_SQUARE);\
1915 if (dir2##Square2 != dir2##Square)\
1917 if (x)\
1918 collCheck(dir,dir##SquareNew,dir2##Square2)\
1919 else\
1920 collCheck(dir,dir2##Square2,dir##SquareNew)\
1924 int8_t xCollides = 0;
1925 collCheckOrtho(x,y,xSquareNew,ySquare,1)
1927 int8_t yCollides = 0;
1928 collCheckOrtho(y,x,xSquare,ySquareNew,0)
1930 if (xCollides || yCollides)
1932 if (movesInPlane)
1934 #define collHandle(dir)\
1935 if (dir##Collides)\
1936 cornerNew.dir = (dir##Square) * RCL_UNITS_PER_SQUARE +\
1937 RCL_UNITS_PER_SQUARE / 2 + dir##Dir * (RCL_UNITS_PER_SQUARE / 2) -\
1938 dir##Dir;\
1940 collHandle(x)
1941 collHandle(y)
1943 #undef collHandle
1945 else
1947 /* Player collides without moving in the plane; this can happen e.g. on
1948 elevators due to vertical only movement. This code can get executed
1949 when force == 1. */
1951 RCL_Vector2D squarePos;
1952 RCL_Vector2D newPos;
1954 squarePos.x = xSquare * RCL_UNITS_PER_SQUARE;
1955 squarePos.y = ySquare * RCL_UNITS_PER_SQUARE;
1957 newPos.x =
1958 RCL_max(squarePos.x + RCL_CAMERA_COLL_RADIUS + 1,
1959 RCL_min(squarePos.x + RCL_UNITS_PER_SQUARE - RCL_CAMERA_COLL_RADIUS - 1,
1960 camera->position.x));
1962 newPos.y =
1963 RCL_max(squarePos.y + RCL_CAMERA_COLL_RADIUS + 1,
1964 RCL_min(squarePos.y + RCL_UNITS_PER_SQUARE - RCL_CAMERA_COLL_RADIUS - 1,
1965 camera->position.y));
1967 cornerNew.x = corner.x + (newPos.x - camera->position.x);
1968 cornerNew.y = corner.y + (newPos.y - camera->position.y);
1971 else
1973 /* If no non-diagonal collision is detected, a diagonal/corner collision
1974 can still happen, check it here. */
1976 if (xSquare != xSquareNew && ySquare != ySquareNew)
1978 int8_t xyCollides = 0;
1979 collCheck(xy,xSquareNew,ySquareNew)
1981 if (xyCollides)
1983 // normally should slide, but let's KISS and simply stop any movement
1984 cornerNew = corner;
1989 #undef collCheck
1991 camera->position.x = cornerNew.x - xDir * RCL_CAMERA_COLL_RADIUS;
1992 camera->position.y = cornerNew.y - yDir * RCL_CAMERA_COLL_RADIUS;
1995 if (computeHeight && (movesInPlane || (heightOffset != 0) || force))
1997 camera->height += heightOffset;
1999 int16_t xSquare1 = RCL_divRoundDown(camera->position.x -
2000 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
2002 int16_t xSquare2 = RCL_divRoundDown(camera->position.x +
2003 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
2005 int16_t ySquare1 = RCL_divRoundDown(camera->position.y -
2006 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
2008 int16_t ySquare2 = RCL_divRoundDown(camera->position.y +
2009 RCL_CAMERA_COLL_RADIUS,RCL_UNITS_PER_SQUARE);
2011 RCL_Unit bottomLimit = floorHeightFunc(xSquare1,ySquare1);
2012 RCL_Unit topLimit = ceilingHeightFunc != 0 ?
2013 ceilingHeightFunc(xSquare1,ySquare1) : RCL_INFINITY;
2015 RCL_Unit height;
2017 #define checkSquares(s1,s2)\
2019 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
2020 bottomLimit = RCL_max(bottomLimit,height);\
2021 height = ceilingHeightFunc != 0 ?\
2022 ceilingHeightFunc(xSquare##s1,ySquare##s2) : RCL_INFINITY;\
2023 topLimit = RCL_min(topLimit,height);\
2026 if (xSquare2 != xSquare1)
2027 checkSquares(2,1)
2029 if (ySquare2 != ySquare1)
2030 checkSquares(1,2)
2032 if (xSquare2 != xSquare1 && ySquare2 != ySquare1)
2033 checkSquares(2,2)
2035 camera->height = RCL_clamp(camera->height,
2036 bottomLimit + RCL_CAMERA_COLL_HEIGHT_BELOW,
2037 topLimit - RCL_CAMERA_COLL_HEIGHT_ABOVE);
2039 #undef checkSquares
2043 void RCL_initCamera(RCL_Camera *camera)
2045 camera->position.x = 0;
2046 camera->position.y = 0;
2047 camera->direction = 0;
2048 camera->resolution.x = 20;
2049 camera->resolution.y = 15;
2050 camera->shear = 0;
2051 camera->height = RCL_UNITS_PER_SQUARE;
2054 void RCL_initRayConstraints(RCL_RayConstraints *constraints)
2056 constraints->maxHits = 1;
2057 constraints->maxSteps = 20;
2060 #endif