-
Notifications
You must be signed in to change notification settings - Fork 5
/
raycastlib.h
2061 lines (1650 loc) · 60.5 KB
/
raycastlib.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#ifndef RAYCASTLIB_H
#define RAYCASTLIB_H
/**
raycastlib (RCL) - Small C header-only raycasting library for embedded and
low performance computers, such as Arduino. Only uses integer math and stdint
standard library.
Check the defines below to fine-tune accuracy vs performance! Don't forget
to compile with optimizations.
Before including the library define RCL_PIXEL_FUNCTION to the name of the
function (with RCL_PixelFunction signature) that will render your pixels!
- All public (and most private) library identifiers start with RCL_.
- Game field's bottom left corner is at [0,0].
- X axis goes right in the ground plane.
- Y axis goes up in the ground plane.
- Height means the Z (vertical) coordinate.
- Each game square is RCL_UNITS_PER_SQUARE * RCL_UNITS_PER_SQUARE points.
- Angles are in RCL_Units, 0 means pointing right (x+) and positively rotates
clockwise. A full angle has RCL_UNITS_PER_SQUARE RCL_Units.
- Most things are normalized with RCL_UNITS_PER_SQUARE (sin, cos, vector
unit length, texture coordinates etc.).
- Screen coordinates are normal: [0,0] = top left, x goes right, y goes down.
author: Miloslav "drummyfish" Ciz
license: CC0 1.0
version: 0.909d
Version numbering: major.minor[d], id 'd' is appended, this is a
in-development version based on the previous stable major.minor version. Two
'd' versions with the same version number, .e.g. 1.0d, may be different.
*/
#include <stdint.h>
#ifndef RCL_RAYCAST_TINY /** Turns on super efficient version of this library.
Only use if neccesarry, looks ugly. Also not done
yet. */
#define RCL_UNITS_PER_SQUARE 1024 /**< Number of RCL_Units in a side of a
spatial square, i.e. the fixed point
scaling. */
typedef int32_t RCL_Unit; /**< Smallest spatial unit, there is
RCL_UNITS_PER_SQUARE units in a square's
length. This effectively serves the purpose of
a fixed-point arithmetic. */
#define RCL_INFINITY 2000000000
#else
#define RCL_UNITS_PER_SQUARE 32
typedef int16_t RCL_Unit;
#define RCL_INFINITY 30000
#define RCL_USE_DIST_APPROX 2
#endif
#define RCL_U RCL_UNITS_PER_SQUARE ///< shorthand for RCL_UNITS_PER_SQUARE
#ifndef RCL_COMPUTE_WALL_TEXCOORDS
#define RCL_COMPUTE_WALL_TEXCOORDS 1
#endif
#ifndef RCL_COMPUTE_FLOOR_TEXCOORDS
#define RCL_COMPUTE_FLOOR_TEXCOORDS 0
#endif
#ifndef RCL_FLOOR_TEXCOORDS_HEIGHT
#define RCL_FLOOR_TEXCOORDS_HEIGHT 0 /** If RCL_COMPUTE_FLOOR_TEXCOORDS == 1,
this says for what height level the
texture coords will be computed for
(for simplicity/performance only one
level is allowed). */
#endif
#ifndef RCL_USE_COS_LUT
#define RCL_USE_COS_LUT 0 /**< type of look up table for cos function:
0: none (compute)
1: 64 items
2: 128 items */
#endif
#ifndef RCL_USE_DIST_APPROX
#define RCL_USE_DIST_APPROX 0 /**< What distance approximation to use:
0: none (compute full Euclidean distance)
1: accurate approximation
2: octagonal approximation (LQ) */
#endif
#ifndef RCL_RECTILINEAR
#define RCL_RECTILINEAR 1 /**< Whether to use rectilinear perspective (normally
used), or curvilinear perspective (fish eye). */
#endif
#ifndef RCL_TEXTURE_VERTICAL_STRETCH
#define RCL_TEXTURE_VERTICAL_STRETCH 1 /**< Whether textures should be
stretched to wall height (possibly
slightly slower if on). */
#endif
#ifndef RCL_COMPUTE_FLOOR_DEPTH
#define RCL_COMPUTE_FLOOR_DEPTH 1 /**< Whether depth should be computed for
floor pixels - turns this off if not
needed. */
#endif
#ifndef RCL_COMPUTE_CEILING_DEPTH
#define RCL_COMPUTE_CEILING_DEPTH 1 /**< As RCL_COMPUTE_FLOOR_DEPTH but for
ceiling. */
#endif
#ifndef RCL_ROLL_TEXTURE_COORDS
#define RCL_ROLL_TEXTURE_COORDS 1 /**< Says whether rolling doors should also
roll the texture coordinates along (mostly
desired for doors). */
#endif
#ifndef RCL_VERTICAL_FOV
#define RCL_VERTICAL_FOV (RCL_UNITS_PER_SQUARE / 3)
#endif
#define RCL_VERTICAL_FOV_TAN (RCL_VERTICAL_FOV * 4) ///< tan approximation
#ifndef RCL_HORIZONTAL_FOV
#define RCL_HORIZONTAL_FOV (RCL_UNITS_PER_SQUARE / 4)
#endif
#define RCL_HORIZONTAL_FOV_TAN (RCL_HORIZONTAL_FOV * 4)
#define RCL_HORIZONTAL_FOV_HALF (RCL_HORIZONTAL_FOV / 2)
#ifndef RCL_CAMERA_COLL_RADIUS
#define RCL_CAMERA_COLL_RADIUS RCL_UNITS_PER_SQUARE / 4
#endif
#ifndef RCL_CAMERA_COLL_HEIGHT_BELOW
#define RCL_CAMERA_COLL_HEIGHT_BELOW RCL_UNITS_PER_SQUARE
#endif
#ifndef RCL_CAMERA_COLL_HEIGHT_ABOVE
#define RCL_CAMERA_COLL_HEIGHT_ABOVE (RCL_UNITS_PER_SQUARE / 3)
#endif
#ifndef RCL_CAMERA_COLL_STEP_HEIGHT
#define RCL_CAMERA_COLL_STEP_HEIGHT (RCL_UNITS_PER_SQUARE / 2)
#endif
#ifndef RCL_TEXTURE_INTERPOLATION_SCALE
#define RCL_TEXTURE_INTERPOLATION_SCALE 1024 /**< This says scaling of fixed
poit vertical texture coord
computation. This should be power
of two! Higher number can look more
accurate but may cause overflow. */
#endif
#define RCL_HORIZON_DEPTH (11 * RCL_UNITS_PER_SQUARE) /**< What depth the
horizon has (the floor
depth is only
approximated with the
help of this
constant). */
#ifndef RCL_VERTICAL_DEPTH_MULTIPLY
#define RCL_VERTICAL_DEPTH_MULTIPLY 2 /**< Defines a multiplier of height
difference when approximating floor/ceil
depth. */
#endif
#define RCL_min(a,b) ((a) < (b) ? (a) : (b))
#define RCL_max(a,b) ((a) > (b) ? (a) : (b))
#define RCL_nonZero(v) ((v) + ((v) == 0)) ///< To prevent zero divisions.
#define RCL_zeroClamp(x) ((x) * ((x) >= 0))
#define RCL_likely(cond) __builtin_expect(!!(cond),1)
#define RCL_unlikely(cond) __builtin_expect(!!(cond),0)
#define RCL_logV2D(v)\
printf("[%d,%d]\n",v.x,v.y);
#define RCL_logRay(r){\
printf("ray:\n");\
printf(" start: ");\
RCL_logV2D(r.start);\
printf(" dir: ");\
RCL_logV2D(r.direction);}
#define RCL_logHitResult(h){\
printf("hit:\n");\
printf(" square: ");\
RCL_logV2D(h.square);\
printf(" pos: ");\
RCL_logV2D(h.position);\
printf(" dist: %d\n", h.distance);\
printf(" dir: %d\n", h.direction);\
printf(" texcoord: %d\n", h.textureCoord);}
#define RCL_logPixelInfo(p){\
printf("pixel:\n");\
printf(" position: ");\
RCL_logV2D(p.position);\
printf(" texCoord: ");\
RCL_logV2D(p.texCoords);\
printf(" depth: %d\n", p.depth);\
printf(" height: %d\n", p.height);\
printf(" wall: %d\n", p.isWall);\
printf(" hit: ");\
RCL_logHitResult(p.hit);\
}
#define RCL_logCamera(c){\
printf("camera:\n");\
printf(" position: ");\
RCL_logV2D(c.position);\
printf(" height: %d\n",c.height);\
printf(" direction: %d\n",c.direction);\
printf(" shear: %d\n",c.shear);\
printf(" resolution: %d x %d\n",c.resolution.x,c.resolution.y);\
}
/// Position in 2D space.
typedef struct
{
RCL_Unit x;
RCL_Unit y;
} RCL_Vector2D;
typedef struct
{
RCL_Vector2D start;
RCL_Vector2D direction;
} RCL_Ray;
typedef struct
{
RCL_Unit distance; /**< Distance to the hit position, or -1 if no
collision happened. If RCL_RECTILINEAR != 0, then
the distance is perpendicular to the projection
plane (fish eye correction), otherwise it is
the straight distance to the ray start
position. */
uint8_t direction; /**< Direction of hit. The convention for angle
units is explained above. */
RCL_Unit textureCoord; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
texture coordinate (horizontal). */
RCL_Vector2D square; ///< Collided square coordinates.
RCL_Vector2D position; ///< Exact collision position in RCL_Units.
RCL_Unit arrayValue; /** Value returned by array function (most often
this will be the floor height). */
RCL_Unit type; /**< Integer identifying type of square (number
returned by type function, e.g. texture
index).*/
RCL_Unit doorRoll; ///< Holds value of door roll.
} RCL_HitResult;
typedef struct
{
RCL_Vector2D position;
RCL_Unit direction; // TODO: rename to "angle" to keep consistency
RCL_Vector2D resolution;
int16_t shear; /**< Shear offset in pixels (0 => no shear), can simulate
looking up/down. */
RCL_Unit height;
} RCL_Camera;
/**
Holds an information about a single rendered pixel (for a pixel function
that works as a fragment shader).
*/
typedef struct
{
RCL_Vector2D position; ///< On-screen position.
int8_t isWall; ///< Whether the pixel is a wall or a floor/ceiling.
int8_t isFloor; ///< Whether the pixel is floor or ceiling.
int8_t isHorizon; ///< If the pixel belongs to horizon segment.
RCL_Unit depth; ///< Corrected depth.
RCL_Unit wallHeight;///< Only for wall pixels, says its height.
RCL_Unit height; ///< World height (mostly for floor).
RCL_HitResult hit; ///< Corresponding ray hit.
RCL_Vector2D texCoords; /**< Normalized (0 to RCL_UNITS_PER_SQUARE - 1)
texture coordinates. */
} RCL_PixelInfo;
void RCL_PIXEL_FUNCTION (RCL_PixelInfo *pixel);
typedef struct
{
uint16_t maxHits;
uint16_t maxSteps;
} RCL_RayConstraints;
/**
Function used to retrieve some information about cells of the rendered scene.
It should return a characteristic of given square as an integer (e.g. square
height, texture index, ...) - between squares that return different numbers
there is considered to be a collision.
This function should be as fast as possible as it will typically be called
very often.
*/
typedef RCL_Unit (*RCL_ArrayFunction)(int16_t x, int16_t y);
/*
TODO: maybe array functions should be replaced by defines of funtion names
like with pixelFunc? Could be more efficient than function pointers.
*/
/**
Function that renders a single pixel at the display. It is handed an info
about the pixel it should draw.
This function should be as fast as possible as it will typically be called
very often.
*/
typedef void (*RCL_PixelFunction)(RCL_PixelInfo *info);
typedef void
(*RCL_ColumnFunction)(RCL_HitResult *hits, uint16_t hitCount, uint16_t x,
RCL_Ray ray);
/**
Simple-interface function to cast a single ray.
@return The first collision result.
*/
RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc);
/**
Casts a 3D ray in 3D environment with floor and optional ceiling
(ceilingHeightFunc can be 0). This can be useful for hitscan shooting,
visibility checking etc.
@return normalized ditance (0 to RCL_UNITS_PER_SQUARE) along the ray at which
the environment was hit, RCL_UNITS_PER_SQUARE means nothing was hit
*/
RCL_Unit RCL_castRay3D(
RCL_Vector2D pos1, RCL_Unit height1, RCL_Vector2D pos2, RCL_Unit height2,
RCL_ArrayFunction floorHeightFunc, RCL_ArrayFunction ceilingHeightFunc,
RCL_RayConstraints constraints);
/**
Maps a single point in the world to the screen (2D position + depth).
*/
RCL_PixelInfo RCL_mapToScreen(RCL_Vector2D worldPosition, RCL_Unit height,
RCL_Camera camera);
/**
Casts a single ray and returns a list of collisions.
@param ray ray to be cast, if RCL_RECTILINEAR != 0 then the computed hit
distance is divided by the ray direction vector length (to correct
the fish eye effect)
@param arrayFunc function that will be used to determine collisions (hits)
with the ray (squares for which this function returns different values
are considered to have a collision between them), this will typically
be a function returning floor height
@param typeFunc optional (can be 0) function - if provided, it will be used
to mark the hit result with the number returned by this function
(it can be e.g. a texture index)
@param hitResults array in which the hit results will be stored (has to be
preallocated with at space for at least as many hit results as
maxHits specified with the constraints parameter)
@param hitResultsLen in this variable the number of hit results will be
returned
@param constraints specifies constraints for the ray cast
*/
void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
uint16_t *hitResultsLen, RCL_RayConstraints constraints);
RCL_Vector2D RCL_angleToDirection(RCL_Unit angle);
/**
Cos function.
@param input to cos in RCL_Units (RCL_UNITS_PER_SQUARE = 2 * pi = 360 degrees)
@return RCL_normalized output in RCL_Units (from -RCL_UNITS_PER_SQUARE to
RCL_UNITS_PER_SQUARE)
*/
RCL_Unit RCL_cos(RCL_Unit input);
RCL_Unit RCL_sin(RCL_Unit input);
RCL_Unit RCL_tan(RCL_Unit input);
RCL_Unit RCL_ctg(RCL_Unit input);
/// Normalizes given vector to have RCL_UNITS_PER_SQUARE length.
RCL_Vector2D RCL_normalize(RCL_Vector2D v);
/// Computes a cos of an angle between two vectors.
RCL_Unit RCL_vectorsAngleCos(RCL_Vector2D v1, RCL_Vector2D v2);
uint16_t RCL_sqrt(RCL_Unit value);
RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2);
RCL_Unit RCL_len(RCL_Vector2D v);
/**
Converts an angle in whole degrees to an angle in RCL_Units that this library
uses.
*/
RCL_Unit RCL_degreesToUnitsAngle(int16_t degrees);
///< Computes the change in size of an object due to perspective (vertical FOV).
RCL_Unit RCL_perspectiveScaleVertical(RCL_Unit originalSize, RCL_Unit distance);
RCL_Unit RCL_perspectiveScaleVerticalInverse(RCL_Unit originalSize,
RCL_Unit scaledSize);
RCL_Unit
RCL_perspectiveScaleHorizontal(RCL_Unit originalSize, RCL_Unit distance);
RCL_Unit RCL_perspectiveScaleHorizontalInverse(RCL_Unit originalSize,
RCL_Unit scaledSize);
/**
Casts rays for given camera view and for each hit calls a user provided
function.
*/
void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
RCL_RayConstraints constraints);
/**
Using provided functions, renders a complete complex (multilevel) camera
view.
This function should render each screen pixel exactly once.
function rendering summary:
- performance: slower
- accuracy: higher
- wall textures: yes
- different wall heights: yes
- floor/ceiling textures: no
- floor geometry: yes, multilevel
- ceiling geometry: yes (optional), multilevel
- rolling door: no
- camera shearing: yes
- rendering order: left-to-right, not specifically ordered vertically
@param cam camera whose view to render
@param floorHeightFunc function that returns floor height (in RCL_Units)
@param ceilingHeightFunc same as floorHeightFunc but for ceiling, can also be
0 (no ceiling will be rendered)
@param typeFunction function that says a type of square (e.g. its texture
index), can be 0 (no type in hit result)
@param pixelFunc callback function to draw a single pixel on screen
@param constraints constraints for each cast ray
*/
void RCL_renderComplex(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction ceilingHeightFunc, RCL_ArrayFunction typeFunction,
RCL_RayConstraints constraints);
/**
Renders given camera view, with help of provided functions. This function is
simpler and faster than RCL_renderComplex(...) and is meant to be rendering
flat levels.
function rendering summary:
- performance: faster
- accuracy: lower
- wall textures: yes
- different wall heights: yes
- floor/ceiling textures: yes (only floor, you can mirror it for ceiling)
- floor geometry: no (just flat floor, with depth information)
- ceiling geometry: no (just flat ceiling, with depth information)
- rolling door: yes
- camera shearing: no
- rendering order: left-to-right, top-to-bottom
Additionally this function supports rendering rolling doors.
This function should render each screen pixel exactly once.
@param rollFunc function that for given square says its door roll in
RCL_Units (0 = no roll, RCL_UNITS_PER_SQUARE = full roll right,
-RCL_UNITS_PER_SQUARE = full roll left), can be zero (no rolling door,
rendering should also be faster as fewer intersections will be tested)
*/
void RCL_renderSimple(RCL_Camera cam, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction typeFunc, RCL_ArrayFunction rollFunc,
RCL_RayConstraints constraints);
/**
Function that moves given camera and makes it collide with walls and
potentially also floor and ceilings. It's meant to help implement player
movement.
@param camera camera to move
@param planeOffset offset to move the camera in
@param heightOffset height offset to move the camera in
@param floorHeightFunc function used to retrieve the floor height
@param ceilingHeightFunc function for retrieving ceiling height, can be 0
(camera won't collide with ceiling)
@param computeHeight whether to compute height - if false (0), floor and
ceiling functions won't be used and the camera will
only collide horizontally with walls (good for simpler
game, also faster)
@param force if true, forces to recompute collision even if position doesn't
change
*/
void RCL_moveCameraWithCollision(RCL_Camera *camera, RCL_Vector2D planeOffset,
RCL_Unit heightOffset, RCL_ArrayFunction floorHeightFunc,
RCL_ArrayFunction ceilingHeightFunc, int8_t computeHeight, int8_t force);
void RCL_initCamera(RCL_Camera *camera);
void RCL_initRayConstraints(RCL_RayConstraints *constraints);
//=============================================================================
// privates
#define _RCL_UNUSED(what) (void)(what);
// global helper variables, for precomputing stuff etc.
RCL_Camera _RCL_camera;
RCL_Unit _RCL_horizontalDepthStep = 0;
RCL_Unit _RCL_startFloorHeight = 0;
RCL_Unit _RCL_startCeil_Height = 0;
RCL_Unit _RCL_camResYLimit = 0;
RCL_Unit _RCL_middleRow = 0;
RCL_ArrayFunction _RCL_floorFunction = 0;
RCL_ArrayFunction _RCL_ceilFunction = 0;
RCL_Unit _RCL_fHorizontalDepthStart = 0;
RCL_Unit _RCL_cHorizontalDepthStart = 0;
int16_t _RCL_cameraHeightScreen = 0;
RCL_ArrayFunction _RCL_rollFunction = 0; // says door rolling
RCL_Unit *_RCL_floorPixelDistances = 0;
RCL_Unit _RCL_fovCorrectionFactors[2] = {0,0}; //correction for hor/vert fov
RCL_Unit RCL_clamp(RCL_Unit value, RCL_Unit valueMin, RCL_Unit valueMax)
{
if (value >= valueMin)
{
if (value <= valueMax)
return value;
else
return valueMax;
}
else
return valueMin;
}
static inline RCL_Unit RCL_abs(RCL_Unit value)
{
return value * (((value >= 0) << 1) - 1);
}
/// Like mod, but behaves differently for negative values.
static inline RCL_Unit RCL_wrap(RCL_Unit value, RCL_Unit mod)
{
RCL_Unit cmp = value < 0;
return cmp * mod + (value % mod) - cmp;
}
/// Performs division, rounding down, NOT towards zero.
static inline RCL_Unit RCL_divRoundDown(RCL_Unit value, RCL_Unit divisor)
{
return value / divisor - ((value >= 0) ? 0 : 1);
}
// Bhaskara's cosine approximation formula
#define trigHelper(x) (((RCL_Unit) RCL_UNITS_PER_SQUARE) *\
(RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 - 4 * (x) * (x)) /\
(RCL_UNITS_PER_SQUARE / 2 * RCL_UNITS_PER_SQUARE / 2 + (x) * (x)))
#if RCL_USE_COS_LUT == 1
#ifdef RCL_RAYCAST_TINY
const RCL_Unit cosLUT[64] =
{
16,14,11,6,0,-6,-11,-14,-15,-14,-11,-6,0,6,11,14
};
#else
const RCL_Unit cosLUT[64] =
{
1024,1019,1004,979,946,903,851,791,724,649,568,482,391,297,199,100,0,-100,
-199,-297,-391,-482,-568,-649,-724,-791,-851,-903,-946,-979,-1004,-1019,
-1023,-1019,-1004,-979,-946,-903,-851,-791,-724,-649,-568,-482,-391,-297,
-199,-100,0,100,199,297,391,482,568,649,724,791,851,903,946,979,1004,1019
};
#endif
#elif RCL_USE_COS_LUT == 2
const RCL_Unit cosLUT[128] =
{
1024,1022,1019,1012,1004,993,979,964,946,925,903,878,851,822,791,758,724,
687,649,609,568,526,482,437,391,344,297,248,199,150,100,50,0,-50,-100,-150,
-199,-248,-297,-344,-391,-437,-482,-526,-568,-609,-649,-687,-724,-758,-791,
-822,-851,-878,-903,-925,-946,-964,-979,-993,-1004,-1012,-1019,-1022,-1023,
-1022,-1019,-1012,-1004,-993,-979,-964,-946,-925,-903,-878,-851,-822,-791,
-758,-724,-687,-649,-609,-568,-526,-482,-437,-391,-344,-297,-248,-199,-150,
-100,-50,0,50,100,150,199,248,297,344,391,437,482,526,568,609,649,687,724,
758,791,822,851,878,903,925,946,964,979,993,1004,1012,1019,1022
};
#endif
RCL_Unit RCL_cos(RCL_Unit input)
{
input = RCL_wrap(input,RCL_UNITS_PER_SQUARE);
#if RCL_USE_COS_LUT == 1
#ifdef RCL_RAYCAST_TINY
return cosLUT[input];
#else
return cosLUT[input / 16];
#endif
#elif RCL_USE_COS_LUT == 2
return cosLUT[input / 8];
#else
if (input < RCL_UNITS_PER_SQUARE / 4)
return trigHelper(input);
else if (input < RCL_UNITS_PER_SQUARE / 2)
return -1 * trigHelper(RCL_UNITS_PER_SQUARE / 2 - input);
else if (input < 3 * RCL_UNITS_PER_SQUARE / 4)
return -1 * trigHelper(input - RCL_UNITS_PER_SQUARE / 2);
else
return trigHelper(RCL_UNITS_PER_SQUARE - input);
#endif
}
#undef trigHelper
RCL_Unit RCL_sin(RCL_Unit input)
{
return RCL_cos(input - RCL_UNITS_PER_SQUARE / 4);
}
RCL_Unit RCL_tan(RCL_Unit input)
{
return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_nonZero(RCL_cos(input)
);
return (RCL_sin(input) * RCL_UNITS_PER_SQUARE) / RCL_nonZero(RCL_cos(input));
}
RCL_Unit RCL_ctg(RCL_Unit input)
{
return (RCL_cos(input) * RCL_UNITS_PER_SQUARE) / RCL_sin(input);
}
RCL_Vector2D RCL_angleToDirection(RCL_Unit angle)
{
RCL_Vector2D result;
result.x = RCL_cos(angle);
result.y = -1 * RCL_sin(angle);
return result;
}
uint16_t RCL_sqrt(RCL_Unit value)
{
#ifdef RCL_RAYCAST_TINY
uint16_t result = 0;
uint16_t a = value;
uint16_t b = 1u << 14;
#else
uint32_t result = 0;
uint32_t a = value;
uint32_t b = 1u << 30;
#endif
while (b > a)
b >>= 2;
while (b != 0)
{
if (a >= result + b)
{
a -= result + b;
result = result + 2 * b;
}
b >>= 2;
result >>= 1;
}
return result;
}
RCL_Unit RCL_dist(RCL_Vector2D p1, RCL_Vector2D p2)
{
RCL_Unit dx = p2.x - p1.x;
RCL_Unit dy = p2.y - p1.y;
#if RCL_USE_DIST_APPROX == 2
// octagonal approximation
dx = RCL_abs(dx);
dy = RCL_abs(dy);
return dy > dx ? dx / 2 + dy : dy / 2 + dx;
#elif RCL_USE_DIST_APPROX == 1
// more accurate approximation
RCL_Unit a, b, result;
dx = ((dx < 0) * 2 - 1) * dx;
dy = ((dy < 0) * 2 - 1) * dy;
if (dx < dy)
{
a = dy;
b = dx;
}
else
{
a = dx;
b = dy;
}
result = a + (44 * b) / 102;
if (a < (b << 4))
result -= (5 * a) / 128;
return result;
#else
dx = dx * dx;
dy = dy * dy;
return RCL_sqrt((RCL_Unit) (dx + dy));
#endif
}
RCL_Unit RCL_len(RCL_Vector2D v)
{
RCL_Vector2D zero;
zero.x = 0;
zero.y = 0;
return RCL_dist(zero,v);
}
static inline int8_t RCL_pointIsLeftOfRay(RCL_Vector2D point, RCL_Ray ray)
{
RCL_Unit dX = point.x - ray.start.x;
RCL_Unit dY = point.y - ray.start.y;
return (ray.direction.x * dY - ray.direction.y * dX) > 0;
// ^ Z component of cross-product
}
void RCL_castRayMultiHit(RCL_Ray ray, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunc, RCL_HitResult *hitResults,
uint16_t *hitResultsLen, RCL_RayConstraints constraints)
{
RCL_Vector2D currentPos = ray.start;
RCL_Vector2D currentSquare;
currentSquare.x = RCL_divRoundDown(ray.start.x,RCL_UNITS_PER_SQUARE);
currentSquare.y = RCL_divRoundDown(ray.start.y,RCL_UNITS_PER_SQUARE);
*hitResultsLen = 0;
RCL_Unit squareType = arrayFunc(currentSquare.x,currentSquare.y);
// DDA variables
RCL_Vector2D nextSideDist; // dist. from start to the next side in given axis
RCL_Vector2D delta;
RCL_Vector2D step; // -1 or 1 for each axis
int8_t stepHorizontal = 0; // whether the last step was hor. or vert.
nextSideDist.x = 0;
nextSideDist.y = 0;
RCL_Unit dirVecLengthNorm = RCL_len(ray.direction) * RCL_UNITS_PER_SQUARE;
delta.x = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.x));
delta.y = RCL_abs(dirVecLengthNorm / RCL_nonZero(ray.direction.y));
// init DDA
if (ray.direction.x < 0)
{
step.x = -1;
nextSideDist.x = (RCL_wrap(ray.start.x,RCL_UNITS_PER_SQUARE) * delta.x) /
RCL_UNITS_PER_SQUARE;
}
else
{
step.x = 1;
nextSideDist.x =
((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.x,RCL_UNITS_PER_SQUARE)) *
delta.x) / RCL_UNITS_PER_SQUARE;
}
if (ray.direction.y < 0)
{
step.y = -1;
nextSideDist.y = (RCL_wrap(ray.start.y,RCL_UNITS_PER_SQUARE) * delta.y) /
RCL_UNITS_PER_SQUARE;
}
else
{
step.y = 1;
nextSideDist.y =
((RCL_wrap(RCL_UNITS_PER_SQUARE - ray.start.y,RCL_UNITS_PER_SQUARE)) *
delta.y) / RCL_UNITS_PER_SQUARE;
}
// DDA loop
#define RECIP_SCALE 65536
RCL_Unit rayDirXRecip = RECIP_SCALE / RCL_nonZero(ray.direction.x);
RCL_Unit rayDirYRecip = RECIP_SCALE / RCL_nonZero(ray.direction.y);
// ^ we precompute reciprocals to avoid divisions in the loop
for (uint16_t i = 0; i < constraints.maxSteps; ++i)
{
RCL_Unit currentType = arrayFunc(currentSquare.x,currentSquare.y);
if (RCL_unlikely(currentType != squareType))
{
// collision
RCL_HitResult h;
h.arrayValue = currentType;
h.doorRoll = 0;
h.position = currentPos;
h.square = currentSquare;
if (stepHorizontal)
{
h.position.x = currentSquare.x * RCL_UNITS_PER_SQUARE;
h.direction = 3;
if (step.x == -1)
{
h.direction = 1;
h.position.x += RCL_UNITS_PER_SQUARE;
}
RCL_Unit diff = h.position.x - ray.start.x;
h.position.y = // avoid division by multiplying with reciprocal
ray.start.y + (ray.direction.y * diff * rayDirXRecip) / RECIP_SCALE;
#if RCL_RECTILINEAR
/* Here we compute the fish eye corrected distance (perpendicular to
the projection plane) as the Euclidean distance (of hit from camera
position) divided by the length of the ray direction vector. This can
be computed without actually computing Euclidean distances as a
hypothenuse A (distance) divided by hypothenuse B (length) is equal to
leg A (distance along principal axis) divided by leg B (length along
the same principal axis). */
#define CORRECT(dir1,dir2)\
RCL_Unit tmp = diff / 4; /* 4 to prevent overflow */ \
h.distance = ((tmp / 8) != 0) ? /* prevent a bug with small dists */ \
((tmp * RCL_UNITS_PER_SQUARE * rayDir ## dir1 ## Recip) / (RECIP_SCALE / 4)):\
RCL_abs(h.position.dir2 - ray.start.dir2);
CORRECT(X,y)
#endif // RCL_RECTILINEAR
}
else
{
h.position.y = currentSquare.y * RCL_UNITS_PER_SQUARE;
h.direction = 2;
if (step.y == -1)
{
h.direction = 0;
h.position.y += RCL_UNITS_PER_SQUARE;
}
RCL_Unit diff = h.position.y - ray.start.y;
h.position.x =
ray.start.x + (ray.direction.x * diff * rayDirYRecip) / RECIP_SCALE;
#if RCL_RECTILINEAR
CORRECT(Y,x) // same as above but for different axis
#undef CORRECT
#endif // RCL_RECTILINEAR
}
#if !RCL_RECTILINEAR
h.distance = RCL_dist(h.position,ray.start);
#endif
if (typeFunc != 0)
h.type = typeFunc(currentSquare.x,currentSquare.y);
#if RCL_COMPUTE_WALL_TEXCOORDS == 1
switch (h.direction)
{
case 0: h.textureCoord =
RCL_wrap(-1 * h.position.x,RCL_UNITS_PER_SQUARE); break;
case 1: h.textureCoord =
RCL_wrap(h.position.y,RCL_UNITS_PER_SQUARE); break;
case 2: h.textureCoord =
RCL_wrap(h.position.x,RCL_UNITS_PER_SQUARE); break;
case 3: h.textureCoord =
RCL_wrap(-1 * h.position.y,RCL_UNITS_PER_SQUARE); break;
default: h.textureCoord = 0; break;
}
if (_RCL_rollFunction != 0)
{
h.doorRoll = _RCL_rollFunction(currentSquare.x,currentSquare.y);
if (h.direction == 0 || h.direction == 1)
h.doorRoll *= -1;
}
#else
h.textureCoord = 0;
#endif
hitResults[*hitResultsLen] = h;
*hitResultsLen += 1;
squareType = currentType;
if (*hitResultsLen >= constraints.maxHits)
break;
}
// DDA step
if (nextSideDist.x < nextSideDist.y)
{
nextSideDist.x += delta.x;
currentSquare.x += step.x;
stepHorizontal = 1;
}
else
{
nextSideDist.y += delta.y;
currentSquare.y += step.y;
stepHorizontal = 0;
}
}
}
RCL_HitResult RCL_castRay(RCL_Ray ray, RCL_ArrayFunction arrayFunc)
{
RCL_HitResult result;
uint16_t len;
RCL_RayConstraints c;
c.maxSteps = 1000;
c.maxHits = 1;
RCL_castRayMultiHit(ray,arrayFunc,0,&result,&len,c);
if (len == 0)
result.distance = -1;
return result;
}
void RCL_castRaysMultiHit(RCL_Camera cam, RCL_ArrayFunction arrayFunc,
RCL_ArrayFunction typeFunction, RCL_ColumnFunction columnFunc,
RCL_RayConstraints constraints)
{
RCL_Vector2D dir1 =
RCL_angleToDirection(cam.direction - RCL_HORIZONTAL_FOV_HALF);
RCL_Vector2D dir2 =
RCL_angleToDirection(cam.direction + RCL_HORIZONTAL_FOV_HALF);
/* We scale the side distances so that the middle one is
RCL_UNITS_PER_SQUARE, which has to be this way. */
RCL_Unit cos = RCL_nonZero(RCL_cos(RCL_HORIZONTAL_FOV_HALF));
dir1.x = (dir1.x * RCL_UNITS_PER_SQUARE) / cos;
dir1.y = (dir1.y * RCL_UNITS_PER_SQUARE) / cos;
dir2.x = (dir2.x * RCL_UNITS_PER_SQUARE) / cos;
dir2.y = (dir2.y * RCL_UNITS_PER_SQUARE) / cos;
RCL_Unit dX = dir2.x - dir1.x;
RCL_Unit dY = dir2.y - dir1.y;
RCL_HitResult hits[constraints.maxHits];
uint16_t hitCount;
RCL_Ray r;
r.start = cam.position;
RCL_Unit currentDX = 0;
RCL_Unit currentDY = 0;
for (int16_t i = 0; i < cam.resolution.x; ++i)
{
/* Here by linearly interpolating the direction vector its length changes,
which in result achieves correcting the fish eye effect (computing
perpendicular distance). */
r.direction.x = dir1.x + currentDX / cam.resolution.x;