5 raycastlib - Small C header-only raycasting library for embedded and low
6 performance computers, such as Arduino. Only uses integer math and stdint
9 author: Miloslav "drummyfish" Ciz
13 - Game field's bottom left corner is at [0,0].
14 - X axis goes right in the ground plane.
15 - Y axis goes up in the ground plane.
16 - Height means the Z (vertical) coordinate.
17 - Each game square is UNITS_PER_SQUARE * UNITS_PER_SQUARE points.
18 - Angles are in Units, 0 means pointing right (x+) and positively rotates
19 clockwise. A full angle has UNITS_PER_SQUARE Units.
25 #define UNITS_PER_SQUARE 1024 ///< N. of Units in a side of a spatial square.
26 typedef int32_t Unit
; /**< Smallest spatial unit, there is UNITS_PER_SQUARE
27 units in a square's length. This effectively
28 serves the purpose of a fixed-point arithmetic. */
29 #define UNIT_INFINITY 5000000;
31 typedef int32_t int_maybe32_t
;
32 typedef uint32_t uint_maybe32_t
;
34 #define UNITS_PER_SQUARE 64
36 #define UNIT_INFINITY 32767;
37 typedef int16_t int_maybe32_t
;
38 typedef uint16_t uint_maybe32_t
;
42 #define USE_COS_LUT 0 /**< type of look up table for cos function:
50 #define VERTICAL_FOV (UNITS_PER_SQUARE / 2)
53 #ifndef HORIZONTAL_FOV
54 #define HORIZONTAL_FOV (UNITS_PER_SQUARE / 4)
57 #define HORIZONTAL_FOV_HALF (HORIZONTAL_FOV / 2)
59 #ifndef CAMERA_COLL_RADIUS
60 #define CAMERA_COLL_RADIUS UNITS_PER_SQUARE / 4
63 #ifndef CAMERA_COLL_HEIGHT_BELOW
64 #define CAMERA_COLL_HEIGHT_BELOW UNITS_PER_SQUARE
67 #ifndef CAMERA_COLL_HEIGHT_ABOVE
68 #define CAMERA_COLL_HEIGHT_ABOVE (UNITS_PER_SQUARE / 3)
71 #ifndef CAMERA_COLL_STEP_HEIGHT
72 #define CAMERA_COLL_STEP_HEIGHT (UNITS_PER_SQUARE / 2)
75 #define logVector2D(v)\
76 printf("[%d,%d]\n",v.x,v.y);
81 logVector2D(r.start);\
83 logVector2D(r.direction);}\
85 #define logHitResult(h){\
88 logVector2D(h.square);\
90 logVector2D(h.position);\
91 printf(" dist: %d\n", h.distance);\
92 printf(" texcoord: %d\n", h.textureCoord);}\
94 #define logPixelInfo(p){\
96 printf(" position: ");\
97 logVector2D(p.position);\
98 printf(" depth: %d\n", p.depth);\
99 printf(" wall: %d\n", p.isWall);\
100 printf(" textCoordY: %d\n", p.textureCoordY);\
102 logHitResult(p.hit);\
105 /// Position in 2D space.
120 Vector2D square
; ///< Collided square coordinates.
121 Vector2D position
; ///< Exact collision position in Units.
122 Unit distance
; /**< Euclidean distance to the hit position, or -1 if
123 no collision happened. */
124 Unit textureCoord
; ///< Normalized (0 to UNITS_PER_SQUARE - 1) tex coord.
125 Unit type
; ///< Integer identifying type of square.
126 uint8_t direction
; ///< Direction of hit.
129 // TODO: things like FOV could be constants to make them precomp. and faster?
136 int16_t shear
; /* Shear offset in pixels (0 => no shear), can simulate
142 Holds an information about a single rendered pixel (for a pixel function
143 that works as a fragment shader.
147 Vector2D position
; ///< On-screen position.
148 int8_t isWall
; ///< Whether the pixel is a wall or a floor/ceiling.
149 int8_t isFloor
; ///< Whether the pixel is floor or ceiling.
150 int8_t isHorizon
; ///< Whether the pixel is floor going towards horizon.
151 Unit depth
; ///< Corrected depth.
152 HitResult hit
; ///< Corresponding ray hit.
153 Unit textureCoordY
; ///< Normalized (0 to UNITS_PER_SQUARE - 1) tex coord.
160 uint8_t computeTextureCoords
; ///< Turns texture coords on/off.
164 Function used to retrieve some information about cells of the rendered scene.
165 It should return a characteristic of given square as an integer (e.g. square
166 height, texture index, ...) - between squares that return different numbers
167 there is considered to be a collision.
169 This function should be as fast as possible as it will typically be called
172 typedef Unit (*ArrayFunction
)(int16_t x
, int16_t y
);
175 Function that renders a single pixel at the display. It is handed an info
176 about the pixel it should draw.
178 This function should be as fast as possible as it will typically be called
181 typedef void (*PixelFunction
)(PixelInfo
*info
);
184 (*ColumnFunction
)(HitResult
*hits
, uint16_t hitCount
, uint16_t x
, Ray ray
);
187 Simple-interface function to cast a single ray.
188 @return The first collision result.
190 HitResult
castRay(Ray ray
, ArrayFunction arrayFunc
);
193 Maps a single point in the world to the screen (2D position + depth).
195 PixelInfo
mapToScreen(Vector2D worldPosition
, Unit height
, Camera camera
);
198 Casts a single ray and returns a list of collisions.
200 void castRayMultiHit(Ray ray
, ArrayFunction arrayFunc
, ArrayFunction typeFunc
,
201 HitResult
*hitResults
, uint16_t *hitResultsLen
, RayConstraints constraints
);
203 Vector2D
angleToDirection(Unit angle
);
208 @param input to cos in Units (UNITS_PER_SQUARE = 2 * pi = 360 degrees)
209 @return normalized output in Units (from -UNITS_PER_SQUARE to UNITS_PER_SQUARE)
211 Unit
cosInt(Unit input
);
213 Unit
sinInt(Unit input
);
215 /// Normalizes given vector to have UNITS_PER_SQUARE length.
216 Vector2D
normalize(Vector2D v
);
218 /// Computes a cos of an angle between two vectors.
219 Unit
vectorsAngleCos(Vector2D v1
, Vector2D v2
);
221 uint16_t sqrtInt(uint_maybe32_t value
);
222 Unit
dist(Vector2D p1
, Vector2D p2
);
223 Unit
len(Vector2D v
);
226 Converts an angle in whole degrees to an angle in Units that this library
229 Unit
degreesToUnitsAngle(int16_t degrees
);
231 ///< Computes the change in size of an object due to perspective.
232 Unit
perspectiveScale(Unit originalSize
, Unit distance
);
235 Casts rays for given camera view and for each hit calls a user provided
238 void castRaysMultiHit(Camera cam
, ArrayFunction arrayFunc
,
239 ArrayFunction typeFunction
, ColumnFunction columnFunc
,
240 RayConstraints constraints
);
243 Using provided functions, renders a complete complex camera view.
245 @param cam camera whose view to render
246 @param floorHeightFunc function that returns floor height (in Units)
247 @param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
248 0 (no ceiling will be rendered)
249 @param typeFunction function that says a type of square (e.g. its texture
250 index), can be 0 (no type in hit result)
251 @param pixelFunc callback function to draw a single pixel on screen
252 @param constraints constraints for each cast ray
254 void render(Camera cam
, ArrayFunction floorHeightFunc
,
255 ArrayFunction ceilingHeightFunc
, ArrayFunction typeFunction
,
256 PixelFunction pixelFunc
, RayConstraints constraints
);
259 Renders given camera view, with help of provided functions. This function is
260 simpler and faster than render(...) and is meant to be rendering scenes
261 with simple 1-intersection raycasting. The render(...) function can give more
262 accurate results than this function, so it's to be considered even for simple
265 void renderSimple(Camera cam
, ArrayFunction floorHeightFunc
,
266 ArrayFunction typeFunc
, PixelFunction pixelFunc
, RayConstraints constraints
);
269 Function that moves given camera and makes it collide with walls and
270 potentially also floor and ceilings. It's meant to help implement player
273 @param camera camera to move
274 @param planeOffset offset to move the camera in
275 @param heightOffset height offset to move the camera in
276 @param floorHeightFunc function used to retrieve the floor height
277 @param ceilingHeightFunc function for retrieving ceiling height, can be 0
278 (camera won't collide with ceiling)
279 @param computeHeight whether to compute height - if false (0), floor and
280 ceiling functions won't be used and the camera will
281 only collide horizontally with walls (good for simpler
283 @param force if true, forces to recompute collision even if position doesn't
286 void moveCameraWithCollision(Camera
*camera
, Vector2D planeOffset
,
287 Unit heightOffset
, ArrayFunction floorHeightFunc
,
288 ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
);
290 //=============================================================================
293 #ifdef RAYCASTLIB_PROFILE
294 // function call counters for profiling
295 uint32_t profile_sqrtInt
= 0;
296 uint32_t profile_clamp
= 0;
297 uint32_t profile_cosInt
= 0;
298 uint32_t profile_angleToDirection
= 0;
299 uint32_t profile_dist
= 0;
300 uint32_t profile_len
= 0;
301 uint32_t profile_pointIsLeftOfRay
= 0;
302 uint32_t profile_castRaySquare
= 0;
303 uint32_t profile_castRayMultiHit
= 0;
304 uint32_t profile_castRay
= 0;
305 uint32_t profile_absVal
= 0;
306 uint32_t profile_normalize
= 0;
307 uint32_t profile_vectorsAngleCos
= 0;
308 uint32_t profile_perspectiveScale
= 0;
309 uint32_t profile_wrap
= 0;
310 uint32_t profile_divRoundDown
= 0;
311 #define profileCall(c) profile_##c += 1
313 #define printProfile() {\
314 printf("profile:\n");\
315 printf(" sqrtInt: %d\n",profile_sqrtInt);\
316 printf(" clamp: %d\n",profile_clamp);\
317 printf(" cosInt: %d\n",profile_cosInt);\
318 printf(" angleToDirection: %d\n",profile_angleToDirection);\
319 printf(" dist: %d\n",profile_dist);\
320 printf(" len: %d\n",profile_len);\
321 printf(" pointIsLeftOfRay: %d\n",profile_pointIsLeftOfRay);\
322 printf(" castRaySquare: %d\n",profile_castRaySquare);\
323 printf(" castRayMultiHit : %d\n",profile_castRayMultiHit);\
324 printf(" castRay: %d\n",profile_castRay);\
325 printf(" normalize: %d\n",profile_normalize);\
326 printf(" vectorsAngleCos: %d\n",profile_vectorsAngleCos);\
327 printf(" absVal: %d\n",profile_absVal);\
328 printf(" perspectiveScale: %d\n",profile_perspectiveScale);\
329 printf(" wrap: %d\n",profile_wrap);\
330 printf(" divRoundDown: %d\n",profile_divRoundDown); }
332 #define profileCall(c)
335 Unit
clamp(Unit value
, Unit valueMin
, Unit valueMax
)
339 if (value
>= valueMin
)
341 if (value
<= valueMax
)
350 Unit
absVal(Unit value
)
354 return value
>= 0 ? value
: -1 * value
;
357 /// Like mod, but behaves differently for negative values.
358 Unit
wrap(Unit value
, Unit mod
)
362 return value
>= 0 ? (value
% mod
) : (mod
+ (value
% mod
) - 1);
365 /// Performs division, rounding down, NOT towards zero.
366 Unit
divRoundDown(Unit value
, Unit divisor
)
368 profileCall(divRoundDown
);
370 return value
/ divisor
- ((value
>= 0) ? 0 : 1);
373 // Bhaskara's cosine approximation formula
374 #define trigHelper(x) (((Unit) UNITS_PER_SQUARE) *\
375 (UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
376 (UNITS_PER_SQUARE / 2 * UNITS_PER_SQUARE / 2 + (x) * (x)))
380 const Unit cosLUT
[64] =
382 1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
383 -199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
384 -1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
385 -199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
387 #elif USE_COS_LUT == 2
388 const Unit cosLUT
[128] =
390 1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
391 687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
392 -199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
393 -822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
394 -1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
395 -758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
396 -100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
397 758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
401 Unit
cosInt(Unit input
)
405 // TODO: could be optimized with LUT
407 input
= wrap(input
,UNITS_PER_SQUARE
);
410 return cosLUT
[input
/ 16];
411 #elif USE_COS_LUT == 2
412 return cosLUT
[input
/ 8];
414 if (input
< UNITS_PER_SQUARE
/ 4)
415 return trigHelper(input
);
416 else if (input
< UNITS_PER_SQUARE
/ 2)
417 return -1 * trigHelper(UNITS_PER_SQUARE
/ 2 - input
);
418 else if (input
< 3 * UNITS_PER_SQUARE
/ 4)
419 return -1 * trigHelper(input
- UNITS_PER_SQUARE
/ 2);
421 return trigHelper(UNITS_PER_SQUARE
- input
);
427 Unit
sinInt(Unit input
)
429 return cosInt(input
- UNITS_PER_SQUARE
/ 4);
432 Vector2D
angleToDirection(Unit angle
)
434 profileCall(angleToDirection
);
438 result
.x
= cosInt(angle
);
439 result
.y
= -1 * sinInt(angle
);
444 uint16_t sqrtInt(uint_maybe32_t value
)
446 profileCall(sqrtInt
);
448 uint_maybe32_t result
= 0;
449 uint_maybe32_t a
= value
;
452 uint_maybe32_t b
= 1u << 14;
454 uint_maybe32_t b
= 1u << 30;
465 result
= result
+ 2 * b
;
475 Unit
dist(Vector2D p1
, Vector2D p2
)
479 int_maybe32_t dx
= p2
.x
- p1
.x
;
480 int_maybe32_t dy
= p2
.y
- p1
.y
;
485 return sqrtInt((uint_maybe32_t
) (dx
+ dy
));
495 return sqrtInt(((uint_maybe32_t
) v
.x
) + ((uint_maybe32_t
) v
.y
));
498 int8_t pointIsLeftOfRay(Vector2D point
, Ray ray
)
500 profileCall(pointIsLeftOfRay
);
502 Unit dX
= point
.x
- ray
.start
.x
;
503 Unit dY
= point
.y
- ray
.start
.y
;
504 return (ray
.direction
.x
* dY
- ray
.direction
.y
* dX
) > 0;
505 // ^ Z component of cross-product
509 Casts a ray within a single square, to collide with the square borders.
511 void castRaySquare(Ray
*localRay
, Vector2D
*nextCellOff
, Vector2D
*collOff
)
513 profileCall(castRaySquare
);
519 criticalLine
.start
= localRay
->start
;
520 criticalLine
.direction
= localRay
->direction
;
522 #define helper(c1,c2,n)\
524 nextCellOff->c1 = n;\
525 collOff->c1 = criticalLine.start.c1 - localRay->start.c1;\
527 (((int_maybe32_t) collOff->c1) * localRay->direction.c2) /\
528 ((localRay->direction.c1 == 0) ? 1 : localRay->direction.c1);\
531 #define helper2(n1,n2,c)\
532 if (pointIsLeftOfRay(localRay->start,criticalLine) == c)\
537 if (localRay
->direction
.x
> 0)
539 criticalLine
.start
.x
= UNITS_PER_SQUARE
- 1;
541 if (localRay
->direction
.y
> 0)
544 criticalLine
.start
.y
= UNITS_PER_SQUARE
- 1;
550 criticalLine
.start
.y
= 0;
556 criticalLine
.start
.x
= 0;
558 if (localRay
->direction
.y
> 0)
561 criticalLine
.start
.y
= UNITS_PER_SQUARE
- 1;
567 criticalLine
.start
.y
= 0;
575 collOff
->x
+= nextCellOff
->x
;
576 collOff
->y
+= nextCellOff
->y
;
579 void castRayMultiHit(Ray ray
, ArrayFunction arrayFunc
, ArrayFunction typeFunc
,
580 HitResult
*hitResults
, uint16_t *hitResultsLen
, RayConstraints constraints
)
582 profileCall(castRayMultiHit
);
584 Vector2D initialPos
= ray
.start
;
585 Vector2D currentPos
= ray
.start
;
587 Vector2D currentSquare
;
589 currentSquare
.x
= divRoundDown(ray
.start
.x
, UNITS_PER_SQUARE
);
590 currentSquare
.y
= divRoundDown(ray
.start
.y
,UNITS_PER_SQUARE
);
594 Unit squareType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
596 Vector2D no
, co
; // next cell offset, collision offset
598 no
.x
= 0; // just to supress a warning
603 for (uint16_t i
= 0; i
< constraints
.maxSteps
; ++i
)
605 Unit currentType
= arrayFunc(currentSquare
.x
,currentSquare
.y
);
607 if (currentType
!= squareType
)
613 h
.position
= currentPos
;
614 h
.square
= currentSquare
;
615 h
.distance
= dist(initialPos
,currentPos
);
618 h
.type
= typeFunc(currentSquare
.x
,currentSquare
.y
);
623 h
.textureCoord
= constraints
.computeTextureCoords
?
624 wrap(currentPos
.x
,UNITS_PER_SQUARE
) : 0;
629 h
.textureCoord
= constraints
.computeTextureCoords
?
630 wrap(UNITS_PER_SQUARE
- currentPos
.y
,UNITS_PER_SQUARE
) : 0;
635 h
.textureCoord
= constraints
.computeTextureCoords
?
636 wrap(UNITS_PER_SQUARE
- currentPos
.x
,UNITS_PER_SQUARE
) : 0;
641 h
.textureCoord
= constraints
.computeTextureCoords
?
642 wrap(currentPos
.y
,UNITS_PER_SQUARE
) : 0;
645 hitResults
[*hitResultsLen
] = h
;
649 squareType
= currentType
;
651 if (*hitResultsLen
>= constraints
.maxHits
)
655 ray
.start
.x
= wrap(currentPos
.x
,UNITS_PER_SQUARE
);
656 ray
.start
.y
= wrap(currentPos
.y
,UNITS_PER_SQUARE
);
658 castRaySquare(&ray
,&no
,&co
);
660 currentSquare
.x
+= no
.x
;
661 currentSquare
.y
+= no
.y
;
663 // offset into the next cell
664 currentPos
.x
+= co
.x
;
665 currentPos
.y
+= co
.y
;
669 HitResult
castRay(Ray ray
, ArrayFunction arrayFunc
)
671 profileCall(castRay
);
680 castRayMultiHit(ray
,arrayFunc
,0,&result
,&len
,c
);
683 result
.distance
= -1;
688 void castRaysMultiHit(Camera cam
, ArrayFunction arrayFunc
,
689 ArrayFunction typeFunction
, ColumnFunction columnFunc
,
690 RayConstraints constraints
)
692 Vector2D dir1
= angleToDirection(cam
.direction
- HORIZONTAL_FOV_HALF
);
693 Vector2D dir2
= angleToDirection(cam
.direction
+ HORIZONTAL_FOV_HALF
);
695 Unit dX
= dir2
.x
- dir1
.x
;
696 Unit dY
= dir2
.y
- dir1
.y
;
698 HitResult hits
[constraints
.maxHits
];
702 r
.start
= cam
.position
;
704 for (uint16_t i
= 0; i
< cam
.resolution
.x
; ++i
)
706 r
.direction
.x
= dir1
.x
+ (dX
* i
) / cam
.resolution
.x
;
707 r
.direction
.y
= dir1
.y
+ (dY
* i
) / cam
.resolution
.x
;
709 castRayMultiHit(r
,arrayFunc
,typeFunction
,hits
,&hitCount
,constraints
);
711 columnFunc(hits
,hitCount
,i
,r
);
715 // global helper variables, for precomputing stuff etc.
716 PixelFunction _pixelFunction
= 0;
718 Unit _floorDepthStep
= 0;
719 Unit _startFloorHeight
= 0;
720 Unit _startCeilHeight
= 0;
721 int_maybe32_t _camResYLimit
= 0;
723 ArrayFunction _floorFunction
= 0;
724 ArrayFunction _ceilFunction
= 0;
725 uint8_t _computeTextureCoords
= 0;
726 Unit _fogStartYBottom
= 0;
727 Unit _fogStartYTop
= 0;
728 int16_t _cameraHeightScreen
= 0;
731 Helper function that determines intersection with both ceiling and floor.
733 Unit
_floorCeilFunction(int16_t x
, int16_t y
)
735 // TODO: adjust also for RAYCAST_TINY
737 Unit f
= _floorFunction(x
,y
);
739 if (_ceilFunction
== 0)
742 Unit c
= _ceilFunction(x
,y
);
744 return ((f
& 0x0000ffff) << 16) | (c
& 0x0000ffff);
747 Unit
adjustDistance(Unit distance
, Camera
*camera
, Ray
*ray
)
749 /* FIXME/TODO: The adjusted (=orthogonal, camera-space) distance could
750 possibly be computed more efficiently by not computing Euclidean
751 distance at all, but rather compute the distance of the collision
752 point from the projection plane (line). */
756 vectorsAngleCos(angleToDirection(camera
->direction
),ray
->direction
)) /
759 return result
== 0 ? 1 : result
;
760 // ^ prevent division by zero
763 void _columnFunction(HitResult
*hits
, uint16_t hitCount
, uint16_t x
, Ray ray
)
765 int_maybe32_t y
= _camResYLimit
; // screen y (for floor), will only go up
766 int_maybe32_t y2
= 0; // screen y (for ceil), will only fo down
768 Unit worldZPrev
= _startFloorHeight
;
769 Unit worldZPrevCeil
= _startCeilHeight
;
774 #define VERTICAL_DEPTH_MULTIPLY 2
776 // we'll be simulatenously drawing the floor and the ceiling now
777 for (uint_maybe32_t j
= 0; j
< hitCount
; ++j
)
779 HitResult hit
= hits
[j
];
781 Unit dist
= adjustDistance(hit
.distance
,&_camera
,&ray
);
783 Unit wallHeight
= _floorFunction(hit
.square
.x
,hit
.square
.y
);
785 Unit worldZ2
= wallHeight
- _camera
.height
;
787 int_maybe32_t z1Screen
= _middleRow
- perspectiveScale(
788 (worldZPrev
* _camera
.resolution
.y
) / UNITS_PER_SQUARE
,dist
);
790 int_maybe32_t z2Screen
= _middleRow
- perspectiveScale(
791 (worldZ2
* _camera
.resolution
.y
) / UNITS_PER_SQUARE
,dist
);
793 int8_t skipFloorWall
= ((z1Screen
< 0 && z2Screen
< 0) ||
794 (z1Screen
> _camResYLimit
&& z2Screen
> _camResYLimit
));
796 int_maybe32_t z1ScreenNoClamp
= z1Screen
;
798 z1Screen
= clamp(z1Screen
,0,_camResYLimit
);
799 z1Screen
= z1Screen
> y2
? z1Screen
: y2
;
801 int_maybe32_t wallScreenHeightNoClamp
= z1ScreenNoClamp
- z2Screen
+ 1;
802 wallScreenHeightNoClamp
= wallScreenHeightNoClamp
== 0 ? 1 :
803 wallScreenHeightNoClamp
;
805 z2Screen
= clamp(z2Screen
,0,_camResYLimit
);
806 z2Screen
= z2Screen
> y2
? z2Screen
: y2
;
808 int_maybe32_t zTop
= z1Screen
< z2Screen
? z1Screen
: z2Screen
;
810 // make the same variables for ceiling
812 Unit wallHeightCeil
= 0;
813 Unit worldZ2Ceil
= 0;
814 int_maybe32_t z1ScreenCeil
= 0;
815 int_maybe32_t z1ScreenCeilNoClamp
= 0;
816 int_maybe32_t z2ScreenCeil
= 0;
817 int_maybe32_t wallScreenHeightCeilNoClamp
= 0;
818 int_maybe32_t zBottomCeil
= y2
;
819 int8_t skipCeilingWall
= 1;
821 if (_ceilFunction
!= 0)
823 wallHeightCeil
= _ceilFunction
!= 0 ?
824 _ceilFunction(hit
.square
.x
,hit
.square
.y
) : 0;
826 worldZ2Ceil
= wallHeightCeil
- _camera
.height
;
828 z1ScreenCeil
= _middleRow
- perspectiveScale(
829 (worldZPrevCeil
* _camera
.resolution
.y
) / UNITS_PER_SQUARE
,dist
);
831 z2ScreenCeil
= _middleRow
- perspectiveScale(
832 (worldZ2Ceil
* _camera
.resolution
.y
) / UNITS_PER_SQUARE
,dist
);
834 skipCeilingWall
= ((z1ScreenCeil
< 0 && z2ScreenCeil
< 0) ||
835 (z1ScreenCeil
> _camResYLimit
&& z2ScreenCeil
> _camResYLimit
));
837 z1ScreenCeilNoClamp
= z1ScreenCeil
;
839 z1ScreenCeil
= clamp(z1ScreenCeil
,0,_camResYLimit
);
840 z1ScreenCeil
= z1ScreenCeil
< y
? z1ScreenCeil
: y
;
842 wallScreenHeightCeilNoClamp
=
843 z2ScreenCeil
- z1ScreenCeilNoClamp
;
845 wallScreenHeightCeilNoClamp
= wallScreenHeightCeilNoClamp
!= 0 ?
846 wallScreenHeightCeilNoClamp
: 1;
848 z2ScreenCeil
= clamp(z2ScreenCeil
,0,_camResYLimit
);
849 z2ScreenCeil
= z2ScreenCeil
< y
? z2ScreenCeil
: y
;
851 zBottomCeil
= z1ScreenCeil
> z2ScreenCeil
?
852 z1ScreenCeil
: z2ScreenCeil
;
855 if (zTop
<= zBottomCeil
)
856 zBottomCeil
= zTop
; // walls on ceiling and floor met
858 // draw floor until wall
864 Unit floorCameraDiff
= absVal(worldZPrev
) * VERTICAL_DEPTH_MULTIPLY
;
866 for (int_maybe32_t i
= y
; i
> z1Screen
; --i
)
869 p
.depth
= (_fogStartYBottom
- i
) * _floorDepthStep
+ floorCameraDiff
;
876 // draw ceiling until wall
880 if (_ceilFunction
!= 0)
882 Unit ceilCameraDiff
= absVal(worldZPrevCeil
) * VERTICAL_DEPTH_MULTIPLY
;
884 for (int_maybe32_t i
= y2
; i
< z1ScreenCeil
; ++i
)
887 p
.depth
= (i
- _fogStartYTop
) * _floorDepthStep
+ ceilCameraDiff
;
892 if (z1ScreenCeil
> y2
)
905 iTo
= y2
< zTop
? zTop
: y2
;
907 for (int_maybe32_t i
= y
; i
>= iTo
; --i
)
912 if (_computeTextureCoords
)
913 p
.textureCoordY
= UNITS_PER_SQUARE
- 1 - ((z1ScreenNoClamp
- i
) *
914 UNITS_PER_SQUARE
) / wallScreenHeightNoClamp
;
924 if (!skipCeilingWall
)
926 iTo
= y
> zBottomCeil
? zBottomCeil
: y
;
928 for (int_maybe32_t i
= y2
; i
< iTo
; ++i
)
933 if (_computeTextureCoords
)
934 p
.textureCoordY
= ((i
- z1ScreenCeilNoClamp
) *
935 UNITS_PER_SQUARE
) / wallScreenHeightCeilNoClamp
;
941 y
= y
> zTop
? zTop
: y
;
942 worldZPrev
= worldZ2
;
944 y2
= y2
< zBottomCeil
? zBottomCeil
: y2
;
945 worldZPrevCeil
= worldZ2Ceil
;
948 break; // walls on ceiling and floor met
951 // draw floor until horizon
957 Unit floorCameraDiff
= absVal(worldZPrev
) * VERTICAL_DEPTH_MULTIPLY
;
958 Unit horizon
= (y2
< _middleRow
|| _ceilFunction
== 0) ? _middleRow
: y2
;
959 horizon
= clamp(horizon
,0,_camera
.resolution
.y
);
961 horizon
+= horizon
> y2
? 0 : 1; // bug workaround
963 for (int_maybe32_t i
= y
; i
>= horizon
; --i
)
966 p
.depth
= (_fogStartYBottom
- i
) * _floorDepthStep
+ floorCameraDiff
;
970 y
= horizon
< y
? horizon
: y
;
972 // draw ceiling until horizon
976 Unit ceilCameraDiff
=
978 absVal(worldZPrevCeil
) * VERTICAL_DEPTH_MULTIPLY
: UNITS_PER_SQUARE
;
980 for (int_maybe32_t i
= y2
; i
< y
; ++i
)
983 p
.depth
= (i
- _fogStartYTop
) * _floorDepthStep
+ ceilCameraDiff
;
987 #undef VERTICAL_DEPTH_MULTIPLY
990 void _columnFunctionSimple(HitResult
*hits
, uint16_t hitCount
, uint16_t x
,
994 int16_t wallScreenHeight
= 0;
995 int16_t coordHelper
= 0;
996 int16_t wallStart
= _middleRow
;
997 int16_t wallEnd
= _middleRow
;
998 int16_t heightOffset
= 0;
1007 HitResult hit
= hits
[0];
1009 dist
= adjustDistance(hit
.distance
,&_camera
,&ray
);
1010 int16_t wallHeightWorld
= _floorFunction(hit
.square
.x
,hit
.square
.y
);
1011 wallScreenHeight
= perspectiveScale((wallHeightWorld
*
1012 _camera
.resolution
.y
) / UNITS_PER_SQUARE
,dist
);
1014 heightOffset
= perspectiveScale(_cameraHeightScreen
,dist
);
1016 wallStart
= _middleRow
- wallScreenHeight
/ 2 + heightOffset
;
1018 coordHelper
= -1 * wallStart
;
1019 coordHelper
= coordHelper
>= 0 ? coordHelper
: 0;
1021 wallStart
= clamp(wallStart
,0,_camResYLimit
);
1022 wallEnd
= clamp(wallStart
+ wallScreenHeight
,0,_camResYLimit
);
1032 while (y
< wallStart
)
1037 p
.depth
+= _floorDepthStep
;
1050 if (_computeTextureCoords
)
1051 p
.textureCoordY
= (coordHelper
* UNITS_PER_SQUARE
) / wallScreenHeight
;
1061 p
.depth
= _middleRow
* _floorDepthStep
;
1063 while (y
< _camera
.resolution
.y
)
1068 p
.depth
-= _floorDepthStep
;
1072 void render(Camera cam
, ArrayFunction floorHeightFunc
,
1073 ArrayFunction ceilingHeightFunc
, ArrayFunction typeFunction
,
1074 PixelFunction pixelFunc
, RayConstraints constraints
)
1076 _pixelFunction
= pixelFunc
;
1077 _floorFunction
= floorHeightFunc
;
1078 _ceilFunction
= ceilingHeightFunc
;
1080 _camResYLimit
= cam
.resolution
.y
- 1;
1082 int16_t halfResY
= cam
.resolution
.y
/ 2;
1084 _middleRow
= halfResY
+ cam
.shear
;
1086 _fogStartYBottom
= _middleRow
+ halfResY
;
1087 _fogStartYTop
= _middleRow
- halfResY
;
1089 _computeTextureCoords
= constraints
.computeTextureCoords
;
1091 _startFloorHeight
= floorHeightFunc(
1092 divRoundDown(cam
.position
.x
,UNITS_PER_SQUARE
),
1093 divRoundDown(cam
.position
.y
,UNITS_PER_SQUARE
)) -1 * cam
.height
;
1096 ceilingHeightFunc
!= 0 ?
1098 divRoundDown(cam
.position
.x
,UNITS_PER_SQUARE
),
1099 divRoundDown(cam
.position
.y
,UNITS_PER_SQUARE
)) -1 * cam
.height
1103 _floorDepthStep
= (12 * UNITS_PER_SQUARE
) / cam
.resolution
.y
;
1105 castRaysMultiHit(cam
,_floorCeilFunction
,typeFunction
,
1106 _columnFunction
,constraints
);
1109 void renderSimple(Camera cam
, ArrayFunction floorHeightFunc
,
1110 ArrayFunction typeFunc
, PixelFunction pixelFunc
, RayConstraints constraints
)
1112 _pixelFunction
= pixelFunc
;
1113 _floorFunction
= floorHeightFunc
;
1115 _camResYLimit
= cam
.resolution
.y
- 1;
1116 _middleRow
= cam
.resolution
.y
/ 2;
1117 _computeTextureCoords
= constraints
.computeTextureCoords
;
1119 _cameraHeightScreen
=
1120 (_camera
.resolution
.y
* (_camera
.height
- UNITS_PER_SQUARE
)) /
1124 _floorDepthStep
= (12 * UNITS_PER_SQUARE
) / cam
.resolution
.y
;
1126 constraints
.maxHits
= 1;
1128 castRaysMultiHit(cam
,_floorFunction
,typeFunc
,_columnFunctionSimple
,
1132 Vector2D
normalize(Vector2D v
)
1134 profileCall(normalize
);
1142 result
.x
= (v
.x
* UNITS_PER_SQUARE
) / l
;
1143 result
.y
= (v
.y
* UNITS_PER_SQUARE
) / l
;
1148 Unit
vectorsAngleCos(Vector2D v1
, Vector2D v2
)
1150 profileCall(vectorsAngleCos
);
1155 return (v1
.x
* v2
.x
+ v1
.y
* v2
.y
) / UNITS_PER_SQUARE
;
1158 PixelInfo
mapToScreen(Vector2D worldPosition
, Unit height
, Camera camera
)
1160 // TODO: precompute some stuff that's constant in the frame
1164 Unit d
= dist(worldPosition
,camera
.position
);
1168 toPoint
.x
= worldPosition
.x
- camera
.position
.x
;
1169 toPoint
.y
= worldPosition
.y
- camera
.position
.y
;
1171 Vector2D cameraDir
= angleToDirection(camera
.direction
);
1173 result
.depth
= // adjusted distance
1174 (d
* vectorsAngleCos(cameraDir
,toPoint
)) / UNITS_PER_SQUARE
;
1176 result
.position
.y
= camera
.resolution
.y
/ 2 -
1177 (camera
.resolution
.y
*
1178 perspectiveScale(height
- camera
.height
,result
.depth
)) / UNITS_PER_SQUARE
1181 Unit middleColumn
= camera
.resolution
.x
/ 2;
1183 Unit a
= sqrtInt(d
* d
- result
.depth
* result
.depth
);
1186 r
.start
= camera
.position
;
1187 r
.direction
= cameraDir
;
1189 if (!pointIsLeftOfRay(worldPosition
,r
))
1192 Unit cos
= cosInt(HORIZONTAL_FOV_HALF
);
1195 (result
.depth
* sinInt(HORIZONTAL_FOV_HALF
)) / (cos
== 0 ? 1 : cos
);
1198 result
.position
.x
= (a
* middleColumn
) / (b
== 0 ? 1 : b
);
1199 result
.position
.x
= middleColumn
- result
.position
.x
;
1204 Unit
degreesToUnitsAngle(int16_t degrees
)
1206 return (degrees
* UNITS_PER_SQUARE
) / 360;
1209 Unit
perspectiveScale(Unit originalSize
, Unit distance
)
1211 profileCall(perspectiveScale
);
1213 return distance
!= 0 ?
1214 (originalSize
* UNITS_PER_SQUARE
) /
1215 ((VERTICAL_FOV
* 2 * distance
) / UNITS_PER_SQUARE
)
1219 void moveCameraWithCollision(Camera
*camera
, Vector2D planeOffset
,
1220 Unit heightOffset
, ArrayFunction floorHeightFunc
,
1221 ArrayFunction ceilingHeightFunc
, int8_t computeHeight
, int8_t force
)
1223 // TODO: have the cam coll parameters precomputed as macros? => faster
1225 int8_t movesInPlane
= planeOffset
.x
!= 0 || planeOffset
.y
!= 0;
1226 int16_t xSquareNew
, ySquareNew
;
1228 if (movesInPlane
|| force
)
1230 Vector2D corner
; // BBox corner in the movement direction
1233 int16_t xDir
= planeOffset
.x
> 0 ? 1 : (planeOffset
.x
< 0 ? -1 : 0);
1234 int16_t yDir
= planeOffset
.y
> 0 ? 1 : (planeOffset
.y
< 0 ? -1 : 0);
1236 corner
.x
= camera
->position
.x
+ xDir
* CAMERA_COLL_RADIUS
;
1237 corner
.y
= camera
->position
.y
+ yDir
* CAMERA_COLL_RADIUS
;
1239 int16_t xSquare
= divRoundDown(corner
.x
,UNITS_PER_SQUARE
);
1240 int16_t ySquare
= divRoundDown(corner
.y
,UNITS_PER_SQUARE
);
1242 cornerNew
.x
= corner
.x
+ planeOffset
.x
;
1243 cornerNew
.y
= corner
.y
+ planeOffset
.y
;
1245 xSquareNew
= divRoundDown(cornerNew
.x
,UNITS_PER_SQUARE
);
1246 ySquareNew
= divRoundDown(cornerNew
.y
,UNITS_PER_SQUARE
);
1248 Unit bottomLimit
= camera
->height
- CAMERA_COLL_HEIGHT_BELOW
+
1249 CAMERA_COLL_STEP_HEIGHT
;
1250 Unit topLimit
= camera
->height
+ CAMERA_COLL_HEIGHT_ABOVE
;
1252 // checks a single square for collision against the camera
1253 #define collCheck(dir,s1,s2)\
1256 Unit height = floorHeightFunc(s1,s2);\
1257 if (height > bottomLimit)\
1259 else if (ceilingHeightFunc != 0)\
1261 height = ceilingHeightFunc(s1,s2);\
1262 if (height < topLimit)\
1267 dir##Collides = floorHeightFunc(s1,s2) > CAMERA_COLL_STEP_HEIGHT;
1269 // check a collision against non-diagonal square
1270 #define collCheckOrtho(dir,dir2,s1,s2,x)\
1271 if (dir##SquareNew != dir##Square)\
1272 collCheck(dir,s1,s2)\
1273 if (!dir##Collides)\
1274 { /* now also check for coll on the neighbouring square */ \
1275 int16_t dir2##Square2 = divRoundDown(corner.dir2 - dir2##Dir *\
1276 CAMERA_COLL_RADIUS * 2,UNITS_PER_SQUARE);\
1277 if (dir2##Square2 != dir2##Square)\
1280 collCheck(dir,dir##SquareNew,dir2##Square2)\
1282 collCheck(dir,dir2##Square2,dir##SquareNew)\
1286 int8_t xCollides
= 0;
1287 collCheckOrtho(x
,y
,xSquareNew
,ySquare
,1)
1289 int8_t yCollides
= 0;
1290 collCheckOrtho(y
,x
,xSquare
,ySquareNew
,0)
1292 #define collHandle(dir)\
1294 cornerNew.dir = (dir##Square) * UNITS_PER_SQUARE + UNITS_PER_SQUARE / 2\
1295 + dir##Dir * (UNITS_PER_SQUARE / 2) - dir##Dir;\
1297 if (!xCollides && !yCollides) /* if non-diagonal collision happend, corner
1298 collision can't happen */
1300 if (xSquare
!= xSquareNew
&& ySquare
!= ySquareNew
) // corner?
1302 int8_t xyCollides
= 0;
1303 collCheck(xy
,xSquareNew
,ySquareNew
)
1307 // normally should slide, but let's KISS
1319 camera
->position
.x
= cornerNew
.x
- xDir
* CAMERA_COLL_RADIUS
;
1320 camera
->position
.y
= cornerNew
.y
- yDir
* CAMERA_COLL_RADIUS
;
1323 if (computeHeight
&& (movesInPlane
|| heightOffset
!= 0 || force
))
1325 camera
->height
+= heightOffset
;
1328 divRoundDown(camera
->position
.x
- CAMERA_COLL_RADIUS
,UNITS_PER_SQUARE
);
1330 divRoundDown(camera
->position
.x
+ CAMERA_COLL_RADIUS
,UNITS_PER_SQUARE
);
1332 divRoundDown(camera
->position
.y
- CAMERA_COLL_RADIUS
,UNITS_PER_SQUARE
);
1334 divRoundDown(camera
->position
.y
+ CAMERA_COLL_RADIUS
,UNITS_PER_SQUARE
);
1336 Unit bottomLimit
= floorHeightFunc(xSquare1
,ySquare1
);
1337 Unit topLimit
= ceilingHeightFunc
!= 0 ?
1338 ceilingHeightFunc(xSquare1
,ySquare1
) : UNIT_INFINITY
;
1342 #define checkSquares(s1,s2)\
1344 height = floorHeightFunc(xSquare##s1,ySquare##s2);\
1345 bottomLimit = bottomLimit < height ? height : bottomLimit;\
1346 height = ceilingHeightFunc != 0 ?\
1347 ceilingHeightFunc(xSquare##s1,ySquare##s2) : UNIT_INFINITY;\
1348 topLimit = topLimit > height ? height : topLimit;\
1351 if (xSquare2
!= xSquare1
)
1354 if (ySquare2
!= ySquare1
)
1357 if (xSquare2
!= xSquare1
&& ySquare2
!= ySquare1
)
1360 camera
->height
= clamp(camera
->height
,
1361 bottomLimit
+ CAMERA_COLL_HEIGHT_BELOW
,
1362 topLimit
- CAMERA_COLL_HEIGHT_ABOVE
);