-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathtinyphysicsengine.h
More file actions
3303 lines (2561 loc) · 91.7 KB
/
tinyphysicsengine.h
File metadata and controls
3303 lines (2561 loc) · 91.7 KB
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 _TINYPHYSICSENGINE_H
#define _TINYPHYSICSENGINE_H
/**
tinyphysicsengine (TPE)
Simple/suckless header-only hybrid 3D physics engine with no floating point,
only 32 bit int arithmetic, similar to e.g. small3dlib.
Conventions and formats are the same or similar to those of small3dlib so as
to make them easily integrate with each other.
The library works with bodies made of spheres connected by elastic springs,
i.e. soft bodies which however behave as "stiff" bodies by default and can
be used to fake rigid body physics as well. Bodies are placed in environemnts
specified by a distance function that allows to implement any mathematical
shape.
Orientations/rotations are in extrinsic Euler angles in the ZXY order (by Z,
then by X, then by Y), if not mentioned otherwise. Angles are in TPE_Units,
TPE_FRACTIONS_PER_UNIT is full angle (2 PI). Sometimes rotations can also be
specified in the "about axis" format: here the object is rotated CW by given
axis by an angle that's specified by the magnitude of the vector.
Where it matters (e.g. rotations about axes) we consider a left-handed coord.
system (x right, y up, z forward).
------------------------------------------------------------------------------
by drummyfish, 2022
version 0.8d
This work's goal is to never be encumbered by any exclusive intellectual
property rights. The work is therefore provided under CC0 1.0
(https://creativecommons.org/publicdomain/zero/1.0/) + additional WAIVER OF
ALL INTELLECTUAL PROPERTY RIGHTS that waives the rest of intellectual property
rights not already waived by CC0 1.0. The WAIVER OF ALL INTELLECTUAL PROPERTY
RGHTS is as follows:
Each contributor to this work agrees that they waive any exclusive rights,
including but not limited to copyright, patents, trademark, trade dress,
industrial design, plant varieties and trade secrets, to any and all ideas,
concepts, processes, discoveries, improvements and inventions conceived,
discovered, made, designed, researched or developed by the contributor either
solely or jointly with others, which relate to this work or result from this
work. Should any waiver of such right be judged legally invalid or
ineffective under applicable law, the contributor hereby grants to each
affected person a royalty-free, non transferable, non sublicensable, non
exclusive, irrevocable and unconditional license to this right.
*/
#include <stdint.h>
typedef int32_t TPE_Unit; ///< Basic fixed point unit type.
typedef int16_t TPE_UnitReduced; ///< Like TPE_Unit but saving space
#define TPE_FRACTIONS_PER_UNIT 512 ///< one fixed point unit, don't change
#define TPE_F TPE_FRACTIONS_PER_UNIT ///< short for TPE_FRACTIONS_PER_UNIT
#define TPE_JOINT_SIZE_MULTIPLIER 32 ///< joint size is scaled (size saving)
#define TPE_INFINITY 2147483647
#define TPE_JOINT_SIZE(joint) ((joint).sizeDivided * TPE_JOINT_SIZE_MULTIPLIER)
#ifndef TPE_APPROXIMATE_LENGTH
#define TPE_APPROXIMATE_LENGTH 0 /**< whether or not use length/distance
approximation rather than exact
calculation (1 is faster but less
accurate), beware of possible lower
stability */
#endif
#if !TPE_APPROXIMATE_LENGTH
#define TPE_DISTANCE TPE_dist
#define TPE_LENGTH TPE_vec3Len
#else
#define TPE_DISTANCE TPE_distApprox
#define TPE_LENGTH TPE_vec3LenApprox
#endif
#ifndef TPE_LOG
#define TPE_LOG(s) ; // redefine to some print function to show debug logs
#endif
#ifndef TPE_LOW_SPEED
/** Speed, in TPE_Units per ticks, that is considered low (used e.g. for auto
deactivation of bodies). */
#define TPE_LOW_SPEED 30
#endif
#ifndef TPE_RESHAPE_TENSION_LIMIT
/** Tension limit, in TPE_Units, after which a non-soft body will be reshaped.
Smaller number will keep more stable shapes but will cost more performance. */
#define TPE_RESHAPE_TENSION_LIMIT 20
#endif
#ifndef TPE_RESHAPE_ITERATIONS
/** How many iterations of reshaping will be performed by the step function if
the body's shape needs to be reshaped. Greater number will keep shapes more
stable but will cost some performance. */
#define TPE_RESHAPE_ITERATIONS 3
#endif
#ifndef TPE_DEACTIVATE_AFTER
/** After how many ticks of low speed should a body be disabled. This mustn't
be greater than 255. */
#define TPE_DEACTIVATE_AFTER 128
#endif
#ifndef TPE_LIGHT_DEACTIVATION
/** When a body is activated by a collision, its deactivation counter will be
set to this value, i.e. after a collision the body will be prone to deactivate
sooner than normally. This is to handle situations with many bodies touching
each other that would normally keep activating each other, never coming to
rest. */
#define TPE_LIGHT_DEACTIVATION \
(TPE_DEACTIVATE_AFTER - TPE_DEACTIVATE_AFTER / 10)
#endif
#ifndef TPE_TENSION_ACCELERATION_DIVIDER
/** Number by which the base acceleration (TPE_FRACTIONS_PER_UNIT per tick
squared) caused by the connection tension will be divided. This should be
power of 2. */
#define TPE_TENSION_ACCELERATION_DIVIDER 32
#endif
#ifndef TPE_TENSION_ACCELERATION_THRESHOLD
/** Limit within which acceleration caused by connection tension won't be
applied. */
#define TPE_TENSION_ACCELERATION_THRESHOLD 5
#endif
#ifndef TPE_TENSION_GREATER_ACCELERATION_THRESHOLD
/** Connection tension threshold after which twice as much acceleration will
be applied. This helps prevent diverting joints that are "impaled" by
environment.*/
#define TPE_TENSION_GREATER_ACCELERATION_THRESHOLD \
(TPE_TENSION_ACCELERATION_THRESHOLD * 3)
#endif
#ifndef TPE_COLLISION_RESOLUTION_ITERATIONS
/** Maximum number of iterations to try to uncollide two colliding bodies. */
#define TPE_COLLISION_RESOLUTION_ITERATIONS 16
#endif
#ifndef TPE_COLLISION_RESOLUTION_MARGIN
/** Margin, in TPE_Units, by which a body will be shifted back to get out of
collision. */
#define TPE_COLLISION_RESOLUTION_MARGIN (TPE_F / 64)
#endif
#ifndef TPE_NONROTATING_COLLISION_RESOLVE_ATTEMPTS
/** Number of times a collision of nonrotating bodies with environment will be
attempted to resolve. This probably won't have great performance implications
as complex collisions of this kind should be relatively rare. */
#define TPE_NONROTATING_COLLISION_RESOLVE_ATTEMPTS 8
#endif
#ifndef TPE_APPROXIMATE_NET_SPEED
/** Whether to use a fast approximation for calculating net speed of bodies
which increases performance a bit. */
#define TPE_APPROXIMATE_NET_SPEED 1
#endif
#define TPE_PRINTF_VEC3(v) printf("[%d %d %d]",(v).x,(v).y,(v).z);
typedef struct
{
TPE_Unit x;
TPE_Unit y;
TPE_Unit z;
} TPE_Vec3;
/** Keeps given point within specified axis-aligned box. This can be used e.g.
to smooth rendered movement of jittering physics bodies. */
TPE_Vec3 TPE_vec3KeepWithinBox(TPE_Vec3 point, TPE_Vec3 boxCenter,
TPE_Vec3 boxMaxVect);
TPE_Vec3 TPE_vec3KeepWithinDistanceBand(TPE_Vec3 point, TPE_Vec3 center,
TPE_Unit minDistance, TPE_Unit maxDistance);
TPE_Vec3 TPE_vec3(TPE_Unit x, TPE_Unit y, TPE_Unit z);
TPE_Vec3 TPE_vec3Minus(TPE_Vec3 v1, TPE_Vec3 v2);
TPE_Vec3 TPE_vec3Plus(TPE_Vec3 v1, TPE_Vec3 v2);
TPE_Vec3 TPE_vec3Cross(TPE_Vec3 v1, TPE_Vec3 v2);
TPE_Vec3 TPE_vec3Project(TPE_Vec3 v, TPE_Vec3 base);
TPE_Vec3 TPE_vec3ProjectNormalized(TPE_Vec3 v, TPE_Vec3 baseNormalized);
TPE_Vec3 TPE_vec3Times(TPE_Vec3 v, TPE_Unit units);
TPE_Vec3 TPE_vec3TimesPlain(TPE_Vec3 v, TPE_Unit q);
TPE_Vec3 TPE_vec3Normalized(TPE_Vec3 v);
TPE_Unit TPE_vec3Dot(TPE_Vec3 v1, TPE_Vec3 v2);
TPE_Unit TPE_vec3Len(TPE_Vec3 v);
TPE_Unit TPE_vec3LenApprox(TPE_Vec3 v);
/** Returns an angle in TPE_Units (see angle conventions) of a 2D vector with
the X axis, CCW. */
TPE_Unit TPE_vec2Angle(TPE_Unit x, TPE_Unit y);
/** Keeps given value within specified range. This can be used e.g. for movement
smoothing. */
TPE_Unit TPE_keepInRange(TPE_Unit x, TPE_Unit xMin, TPE_Unit xMax);
static inline TPE_Unit TPE_abs(TPE_Unit x);
static inline TPE_Unit TPE_max(TPE_Unit a, TPE_Unit b);
static inline TPE_Unit TPE_min(TPE_Unit a, TPE_Unit b);
static inline TPE_Unit TPE_nonZero(TPE_Unit x);
static inline TPE_Unit TPE_dist(TPE_Vec3 p1, TPE_Vec3 p2);
static inline TPE_Unit TPE_distApprox(TPE_Vec3 p1, TPE_Vec3 p2);
TPE_Unit TPE_sqrt(TPE_Unit x);
/** Compute sine, TPE_FRACTIONS_PER_UNIT as argument corresponds to 2 * PI
radians. Returns a number from -TPE_FRACTIONS_PER_UNIT to
TPE_FRACTIONS_PER_UNIT. */
TPE_Unit TPE_sin(TPE_Unit x);
TPE_Unit TPE_cos(TPE_Unit x);
TPE_Unit TPE_atan(TPE_Unit x);
typedef struct
{
TPE_Vec3 position;
TPE_UnitReduced velocity[3]; ///< not TPE_Vec3 to save size
uint8_t sizeDivided; /**< size (radius, ...), for saving space divided by
TPE_JOINT_SIZE_MULTIPLIER */
} TPE_Joint;
typedef struct
{
uint8_t joint1;
uint8_t joint2;
uint16_t length; ///< connection's preferred length, uint16_t saves space
} TPE_Connection;
#define TPE_BODY_FLAG_DEACTIVATED 1 /**< Not being updated due to low energy,
"sleeping", will be woken by
collisions etc. */
#define TPE_BODY_FLAG_NONROTATING 2 /**< When set, the body won't rotate,
will only move linearly. Here the
velocity of the body's first joint
is the velocity of the whole
body. */
#define TPE_BODY_FLAG_DISABLED 4 /**< Disabled, not taking part in
simulation. */
#define TPE_BODY_FLAG_SOFT 8 /**< Soft connections, effort won't be
made to keep the body's shape. */
#define TPE_BODY_FLAG_SIMPLE_CONN 16 /**< Simple connections, don't zero out
antagonist forces or apply
connection friction, can increase
performance. */
#define TPE_BODY_FLAG_ALWAYS_ACTIVE 32 /**< Will never deactivate due to low
energy. */
/** Function used for defining static environment, working similarly to an SDF
(signed distance function). The parameters are: 3D point P, max distance D.
The function should behave like this: if P is inside the solid environment
volume, P will be returned; otherwise closest point (by Euclidean distance) to
the solid environment volume from P will be returned, except for a case when
this closest point would be further away than D, in which case any arbitrary
point further away than D may be returned (this allows for optimizations). */
typedef TPE_Vec3 (*TPE_ClosestPointFunction)(TPE_Vec3, TPE_Unit);
/** Function that can be used as a joint-joint or joint-environment collision
callback, parameters are following: body1 index, joint1 index, body2 index,
joint2 index, collision world position. If body1 index is the same as body1
index, then collision type is body-environment, otherwise it is body-body
type. The function has to return either 1 if the collision is to be allowed
or 0 if it is to be discarded. This can besides others be used to disable
collisions between some bodies. */
typedef uint8_t (*TPE_CollisionCallback)(uint16_t, uint16_t, uint16_t, uint16_t,
TPE_Vec3);
/** Function used by the debug drawing functions to draw individual pixels to
the screen. The parameters are following: pixel x, pixel y, pixel color. */
typedef void (*TPE_DebugDrawFunction)(uint16_t, uint16_t, uint8_t);
/** Physics body made of spheres (each of same weight but possibly different
radia) connected by elastic springs. */
typedef struct
{
TPE_Joint *joints;
uint8_t jointCount;
TPE_Connection *connections;
uint8_t connectionCount;
TPE_UnitReduced jointMass; ///< mass of a single joint
TPE_UnitReduced friction; ///< friction of each joint
TPE_UnitReduced elasticity; ///< elasticity of each joint
uint8_t flags;
uint8_t deactivateCount;
} TPE_Body;
typedef struct
{
TPE_Body *bodies;
uint16_t bodyCount;
TPE_ClosestPointFunction environmentFunction;
TPE_CollisionCallback collisionCallback;
} TPE_World;
/** Tests the mathematical validity of given closest point function (function
representing the physics environment), i.e. whether for example approaching
some closest point in a straight line keeps approximately the same closest
point. Note that this function may take a long time to complete, especially
with higher gridResolution values and more complex environment functions. You
should use this function to test your environment function, especially if you
create functions for your own shapes etc. The cornerFrom and cornerTo points
are corners of an axis-aligned box within which testing will take place,
gridResolution defines numbers of points (i.e. step length) along each
dimension to test (recommended e.g. 64), allowedError says error within which
points will be considered the same (recommended range approx. 10 to 200). If
testing is successful, 1 is returned, otherwise 0 is returned and the point
around which error was detected is returned in errorPoint (unless the pointer
is 0 in which case it is ignored). */
uint8_t TPE_testClosestPointFunction(TPE_ClosestPointFunction f,
TPE_Vec3 cornerFrom, TPE_Vec3 cornerTo, uint8_t gridResolution,
TPE_UnitReduced allowedError, TPE_Vec3 *errorPoint);
void TPE_bodyInit(TPE_Body *body,
TPE_Joint *joints, uint8_t jointCount,
TPE_Connection *connections, uint8_t connectionCount,
TPE_Unit mass);
void TPE_worldInit(TPE_World *world,
TPE_Body *bodies, uint16_t bodyCount,
TPE_ClosestPointFunction environmentFunction);
/** Gets orientation (rotation) of a body from a position of three of its
joints. The vector from joint1 to joint2 is considered the body's forward
direction, the vector from joint1 to joint3 its right direction. The returned
rotation is in Euler angles (see rotation conventions). */
TPE_Vec3 TPE_bodyGetRotation(const TPE_Body *body, uint16_t joint1,
uint16_t joint2, uint16_t joint3);
void TPE_vec3Normalize(TPE_Vec3 *v);
/** Rotates a 3D point by given Euler angle rotation (see rotation
conventions). */
TPE_Vec3 TPE_pointRotate(TPE_Vec3 point, TPE_Vec3 rotation);
/** Returns an inverse rotation to given rotation, in Euler angles (see rotation
conventions). */
TPE_Vec3 TPE_rotationInverse(TPE_Vec3 rotation);
/** Returns a connection tension, i.e. a signed percentage difference against
desired length (TPE_FRACTIONS_PER_UNIT means 100%). */
static inline TPE_Unit TPE_connectionTension(TPE_Unit length,
TPE_Unit desiredLength);
/** Rotates a rotation specified in Euler angles by given axis + angle (see
rotation conventions). Returns a rotation in Eurler angles. */
TPE_Vec3 TPE_rotationRotateByAxis(TPE_Vec3 rotation, TPE_Vec3 rotationByAxis);
/** Computes the formula of a 1D collision of rigid bodies. */
void TPE_getVelocitiesAfterCollision(TPE_Unit *v1, TPE_Unit *v2, TPE_Unit m1,
TPE_Unit m2, TPE_Unit elasticity);
/** Computes orientation/rotation (see docs for orientation format) from two
vectors (which should be at least close to being perpendicular and do NOT
need to be normalized). This can be used to determine orientation of a body
from a relative position of its joints. */
TPE_Vec3 TPE_rotationFromVecs(TPE_Vec3 forward, TPE_Vec3 right);
TPE_Joint TPE_joint(TPE_Vec3 position, TPE_Unit size);
/** Mostly for internal use, resolves a potential collision of two joints in a
way that keeps the joints outside provided environment (if the function
pointer is not 0). Returns 1 if joints collided or 0 otherwise. */
uint8_t TPE_jointsResolveCollision(TPE_Joint *j1, TPE_Joint *j2,
TPE_Unit mass1, TPE_Unit mass2, TPE_Unit elasticity, TPE_Unit friction,
TPE_ClosestPointFunction env);
/** Mostly for internal use, tests and potentially resolves a collision between
a joint and environment, returns 0 if no collision happened, 1 if it happened
and was resolved normally and 2 if it couldn't be resolved normally. */
uint8_t TPE_jointEnvironmentResolveCollision(TPE_Joint *joint, TPE_Unit
elasticity, TPE_Unit friction, TPE_ClosestPointFunction env);
/** Tests whether a body is currently colliding with the environment. */
uint8_t TPE_bodyEnvironmentCollide(const TPE_Body *body,
TPE_ClosestPointFunction env);
/** Mostly for internal use, tests and potentially resolves a collision of a
body with the environment, returns 1 if collision happened or 0 otherwise. */
uint8_t TPE_bodyEnvironmentResolveCollision(TPE_Body *body,
TPE_ClosestPointFunction env);
TPE_Vec3 TPE_bodyGetLinearVelocity(const TPE_Body *body);
/** Computes the minimum bounding box of given body. */
void TPE_bodyGetAABB(const TPE_Body *body, TPE_Vec3 *vMin, TPE_Vec3 *vMax);
/** Computes a bounding sphere of a body which is not minimal but faster to
compute than the minimum bounding sphere. */
void TPE_bodyGetFastBSphere(const TPE_Body *body, TPE_Vec3 *center,
TPE_Unit *radius);
/** Computes the minimum bounding sphere of a body (there is another function
for a faster approximate bounding sphere). */
void TPE_bodyGetBSphere(const TPE_Body *body, TPE_Vec3 *center,
TPE_Unit *radius);
uint8_t TPE_checkOverlapAABB(TPE_Vec3 v1Min, TPE_Vec3 v1Max, TPE_Vec3 v2Min,
TPE_Vec3 v2Max);
/** Mostly for internal use, checks and potentiall resolves collision of two
bodies so as to keep them outside given environment. Returns 1 if collision
happened or 0 otherwise. */
uint8_t TPE_bodiesResolveCollision(TPE_Body *b1, TPE_Body *b2,
TPE_ClosestPointFunction env);
/** Pins a joint of a body to specified location in space (sets its location
and zeros its velocity). */
void TPE_jointPin(TPE_Joint *joint, TPE_Vec3 position);
/** "Fakes" a rotation of a moving sphere by rotating it in the direction of
its movement; this can create the illusion of the sphere actually rotating
due to friction even if the physics sphere object (a body with a single joint)
isn't rotating at all. Returns a rotation in the "about axis" format (see
library conventions). */
TPE_Vec3 TPE_fakeSphereRotation(TPE_Vec3 position1, TPE_Vec3 position2,
TPE_Unit radius);
/** Casts a ray against environment and returns the closest hit of a surface. If
no surface was hit, a vector with all elements equal to TPE_INFINITY will be
returned. The function internally works differently for outside rays (rays
cast from the outside of the environment) and inside rays. Outside rays can
be traced with raymarching and will be processed very quickly and precisely;
in this case if any intersection is found, the function will try to return a
point outside (not guaranteed) the environment that's just in front of the hit
surface. Inside rays are difficult and slow to trace because environment
function won't provide distance, so the results aren't guaranteed to be
precise (the ray may miss some intersections); here rays will be traced by
given step (insideStepSize) and eventually iterated a bit towards the
intersection -- if any intersection is found, the function will try to return
a point inside (not guaranteed) the environment just before the hit
surface. */
TPE_Vec3 TPE_castEnvironmentRay(TPE_Vec3 rayPos, TPE_Vec3 rayDir,
TPE_ClosestPointFunction environment, TPE_Unit insideStepSize,
TPE_Unit rayMarchMaxStep, uint32_t maxSteps);
/** Casts a ray against bodies in a world (ignoring the environment), returns
the position of the closest hit as well as the hit body's index in bodyIndex
(unless the bodyIndex pointer is 0 in which case it is ignored). Similarly
with jointIndex. If no hit is found a vector with all elements equal to
TPE_INFINITY will be returned and bodyIndex will be -1. A specific body can be
excluded with excludeBody (negative value will just make this parameter
ignored). */
TPE_Vec3 TPE_castBodyRay(TPE_Vec3 rayPos, TPE_Vec3 rayDir, int16_t excludeBody,
const TPE_World *world, int16_t *bodyIndex, int16_t *jointIndex);
/** Performs one step (tick, frame, ...) of the physics world simulation
including updating positions and velocities of bodies, collision detection and
resolution, possible reshaping or deactivation of inactive bodies etc. The
time length of the step is relative to all other units but it's ideal if it is
1/30th of a second. */
void TPE_worldStep(TPE_World *world);
void TPE_worldDeactivateAll(TPE_World *world);
void TPE_worldActivateAll(TPE_World *world);
TPE_Unit TPE_worldGetNetSpeed(const TPE_World *world);
TPE_Unit TPE_bodyGetNetSpeed(const TPE_Body *body);
TPE_Unit TPE_bodyGetAverageSpeed(const TPE_Body *body);
void TPE_bodyMultiplyNetSpeed(TPE_Body *body, TPE_Unit factor);
void TPE_bodyLimitAverageSpeed(TPE_Body *body, TPE_Unit speedMin,
TPE_Unit speedMax);
/** Deactivates a body (puts it to sleep until another collision or force wake
up). */
void TPE_bodyDeactivate(TPE_Body *body);
static inline uint8_t TPE_bodyIsActive(const TPE_Body *body);
/** Attempts to shift the joints of a soft body so that the tension of all
springs becomes zero while keeping the joints near their current position.
This function performs one iteration of the equalizing algorithm and doesn't
guarantee a perfect solution, it may help to run multiple iterations (call
this function multiple times). */
void TPE_bodyReshape(TPE_Body *body, TPE_ClosestPointFunction
environmentFunction);
/** Mostly for internal use, performs some "magic" on body connections, mainly
cancelling out of velocities going against each other and also applying
connection friction in soft bodies. The strong parameter indicates if the
body is soft or not. */
void TPE_bodyCancelOutVelocities(TPE_Body *body, uint8_t strong);
/** Moves a body by certain offset. */
void TPE_bodyMoveBy(TPE_Body *body, TPE_Vec3 offset);
/** Moves a body (its center of mass) to given position. */
void TPE_bodyMoveTo(TPE_Body *body, TPE_Vec3 position);
/** Zeros velocities of all soft body joints. */
void TPE_bodyStop(TPE_Body *body);
void TPE_bodyActivate(TPE_Body *body);
/** Adds velocity to a soft body. */
void TPE_bodyAccelerate(TPE_Body *body, TPE_Vec3 velocity);
void TPE_bodyApplyGravity(TPE_Body *body, TPE_Unit downwardsAccel);
/** Adds angular velocity to a soft body. The rotation vector specifies the axis
of rotation by its direction and angular velocity by its magnitude (magnitude
of TPE_FRACTIONS_PER_UNIT will add linear velocity of TPE_FRACTIONS_PER_UNIT
per tick to a point in the distance of TPE_FRACTIONS_PER_UNIT from the
rotation axis). */
void TPE_bodySpin(TPE_Body *body, TPE_Vec3 rotation);
/** Same as TPE_bodySpin but additionally allows to specify the center of
the spin. */
void TPE_bodySpinWithCenter(TPE_Body *body, TPE_Vec3 rotation, TPE_Vec3 center);
/** Instantly rotates a body about an axis (see library conventions for
the rotation format). */
void TPE_bodyRotateByAxis(TPE_Body *body, TPE_Vec3 rotation);
/** Computes the center of mass of a body. This averages the position of all
joints; note that if you need, you may estimate the center of the body faster,
e.g. by taking a position of a single "center joint", or averaging just 2
extreme points. */
TPE_Vec3 TPE_bodyGetCenterOfMass(const TPE_Body *body);
/** Draws a debug view of a 3D physics world using a provided pixel drawing
function. This can be used to overlay a simple visualization of the physics
objects to your main render, to spot exact borders of objects etc. The
function draws simple dotted lines and circles with different "colors" for
different types of objects (joints, connections, environemnt). camPos, camRot
and camView should match the camera settings of your main renderer. CamView.x
is horizontal resolution in pixels, camView.y is the vertical resolution,
CamView.z says the camera focal length (~FOV) in TPE_Units (0 means
orthographic projection). envGridRes is the resolution of an environment probe
grid (the function will probe points in space and draw borders of the physics
environemnt), envGridSize is the size (int TPE_Units) of the grid cell. Note
the function may be slow (reducing envGridRes can help, workable value can be
e.g. 16). */
void TPE_worldDebugDraw(TPE_World *world, TPE_DebugDrawFunction drawFunc,
TPE_Vec3 camPos, TPE_Vec3 camRot, TPE_Vec3 camView, uint16_t envGridRes,
TPE_Unit envGridSize);
#define TPE_DEBUG_COLOR_CONNECTION 0
#define TPE_DEBUG_COLOR_JOINT 1
#define TPE_DEBUG_COLOR_ENVIRONMENT 2
#define TPE_DEBUG_COLOR_INACTIVE 3
uint32_t TPE_jointHash(const TPE_Joint *joint);
uint32_t TPE_connectionHash(const TPE_Connection *connection);
uint32_t TPE_bodyHash(const TPE_Body *body);
/** Computes 32 bit hash of the world, useful for checking if two states of the
world differ. The function takes into account most of the relevant state but
possibly not all of it, for details check the code. */
uint32_t TPE_worldHash(const TPE_World *world);
// FUNCTIONS FOR GENERATING BODIES
void TPE_makeBox(TPE_Joint joints[8], TPE_Connection connections[16],
TPE_Unit width, TPE_Unit depth, TPE_Unit height, TPE_Unit jointSize);
void TPE_makeCenterBox(TPE_Joint joints[9], TPE_Connection connections[18],
TPE_Unit width, TPE_Unit depth, TPE_Unit height, TPE_Unit jointSize);
void TPE_makeRect(TPE_Joint joints[4], TPE_Connection connections[6],
TPE_Unit width, TPE_Unit depth, TPE_Unit jointSize);
void TPE_makeTriangle(TPE_Joint joints[3], TPE_Connection connections[3],
TPE_Unit sideLength, TPE_Unit jointSize);
void TPE_makeCenterRect(TPE_Joint joints[5], TPE_Connection connections[8],
TPE_Unit width, TPE_Unit depth, TPE_Unit jointSize);
void TPE_makeCenterRectFull(TPE_Joint joints[5], TPE_Connection connections[10],
TPE_Unit width, TPE_Unit depth, TPE_Unit jointSize);
void TPE_make2Line(TPE_Joint joints[2], TPE_Connection connections[1],
TPE_Unit length, TPE_Unit jointSize);
// FUNCTIONS FOR BUILDING ENVIRONMENT
TPE_Vec3 TPE_envAABoxInside(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3 size);
TPE_Vec3 TPE_envAABox(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3 maxCornerVec);
TPE_Vec3 TPE_envBox(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3 maxCornerVec,
TPE_Vec3 rotation);
TPE_Vec3 TPE_envSphere(TPE_Vec3 point, TPE_Vec3 center, TPE_Unit radius);
TPE_Vec3 TPE_envSphereInside(TPE_Vec3 point, TPE_Vec3 center, TPE_Unit radius);
TPE_Vec3 TPE_envHalfPlane(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3 normal);
TPE_Vec3 TPE_envGround(TPE_Vec3 point, TPE_Unit height);
TPE_Vec3 TPE_envInfiniteCylinder(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3
direction, TPE_Unit radius);
TPE_Vec3 TPE_envCylinder(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3 direction,
TPE_Unit radius);
TPE_Vec3 TPE_envCone(TPE_Vec3 point, TPE_Vec3 center, TPE_Vec3 direction,
TPE_Unit radius);
TPE_Vec3 TPE_envLineSegment(TPE_Vec3 point, TPE_Vec3 a, TPE_Vec3 b);
TPE_Vec3 TPE_envHeightmap(TPE_Vec3 point, TPE_Vec3 center, TPE_Unit gridSize,
TPE_Unit (*heightFunction)(int32_t x, int32_t y), TPE_Unit maxDist);
/** Environment function for triagnular prism, e.g. for ramps. The sides array
contains three 2D coordinates of points of the triangle in given plane with
respect to the center. WARNING: the points must be specified in counter
clowckwise direction! The direction var specified axis direction (0, 1 or
2).*/
TPE_Vec3 TPE_envAATriPrism(TPE_Vec3 point, TPE_Vec3 center,
const TPE_Unit sides[6], TPE_Unit depth, uint8_t direction);
/* The following are helper macros for creating a union of shapes inside an
environment function and accelerating them with bounding volumes. */
#define TPE_ENV_START(test,point) TPE_Vec3 _pBest = test, _pTest; \
TPE_Unit _dBest = TPE_DISTANCE(_pBest,point), _dTest; \
(void)(_pBest); (void)(_dBest); (void)(_dTest); (void)(_pTest); // supress war
#define TPE_ENV_NEXT(test,point) \
{ if (_pBest.x == point.x && _pBest.y == point.y && _pBest.z == point.z) \
return _pBest; \
_pTest = test; _dTest = TPE_DISTANCE(_pTest,point); \
if (_dTest < _dBest) { _pBest = _pTest; _dBest = _dTest; } }
#define TPE_ENV_END return _pBest;
#define TPE_ENV_BCUBE_TEST(bodyBCubeC,bodyBCubeR,envBCubeC,envBCubeR) ( \
(TPE_abs(envBCubeC.x - bodyBCubeC.x) <= ((bodyBCubeR) + (envBCubeR))) && \
(TPE_abs(envBCubeC.y - bodyBCubeC.y) <= ((bodyBCubeR) + (envBCubeR))) && \
(TPE_abs(envBCubeC.z - bodyBCubeC.z) <= ((bodyBCubeR) + (envBCubeR))))
#define TPE_ENV_BSPHERE_TEST(bodyBSphereC,bodyBSphereR,envBSphereC,envBSphereR)\
(TPE_DISTANCE(bodyBSphereC,envBSphereC) <= ((bodyBSphereR) + (envBSphereR)))
//------------------------------------------------------------------------------
// privates:
uint16_t _TPE_body1Index, _TPE_body2Index, _TPE_joint1Index, _TPE_joint2Index;
TPE_CollisionCallback _TPE_collisionCallback;
static inline TPE_Unit TPE_nonZero(TPE_Unit x)
{
return x != 0 ? x : 1;
}
static inline TPE_Unit TPE_connectionTension(TPE_Unit length,
TPE_Unit desiredLength)
{
return (length * TPE_F) / desiredLength
- TPE_F;
}
TPE_Joint TPE_joint(TPE_Vec3 position, TPE_Unit size)
{
TPE_Joint result;
result.velocity[0] = 0;
result.velocity[1] = 0;
result.velocity[2] = 0;
result.position = position;
size /= TPE_JOINT_SIZE_MULTIPLIER;
if (size > 0xff)
{
TPE_LOG("WARNING: joint size too big in TPE_joint");
}
result.sizeDivided = size;
return result;
}
TPE_Vec3 TPE_vec3(TPE_Unit x, TPE_Unit y, TPE_Unit z)
{
TPE_Vec3 r;
r.x = x;
r.y = y;
r.z = z;
return r;
}
TPE_Unit TPE_sqrt(TPE_Unit x)
{
int8_t sign = 1;
if (x < 0)
{
sign = -1;
x *= -1;
}
uint32_t result = 0;
uint32_t a = x;
uint32_t b = 1u << 30;
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 * sign;
}
TPE_Unit TPE_vec3Len(TPE_Vec3 v)
{
#define ANTI_OVERFLOW 25000
if (v.x < ANTI_OVERFLOW && v.x > -1 * ANTI_OVERFLOW &&
v.y < ANTI_OVERFLOW && v.y > -1 * ANTI_OVERFLOW &&
v.z < ANTI_OVERFLOW && v.z > -1 * ANTI_OVERFLOW)
{
return TPE_sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
else
{
v.x /= 32; v.y /= 32; v.z /= 32;
return TPE_sqrt(v.x * v.x + v.y * v.y + v.z * v.z) * 32;
}
#undef ANTI_OVERFLOW
}
TPE_Unit TPE_vec3LenApprox(TPE_Vec3 v)
{
// 48 sided polyhedron approximation
if (v.x < 0) v.x *= -1;
if (v.y < 0) v.y *= -1;
if (v.z < 0) v.z *= -1;
if (v.x < v.y) // order the coordinates
{
if (v.x < v.z)
{
if (v.y < v.z)
{ // v.x < v.y < v.z
int32_t t = v.x; v.x = v.z; v.z = t;
}
else
{ // v.x < v.z < v.y
int32_t t = v.x; v.x = v.y; v.y = t;
t = v.z; v.z = v.y; v.y = t;
}
}
else
{ // v.z < v.x < v.y
int32_t t = v.x; v.x = v.y; v.y = t;
}
}
else
{
if (v.y < v.z)
{
if (v.x < v.z)
{ // v.y < v.x < v.z
int32_t t = v.y; v.y = v.z; v.z = t;
t = v.x; v.x = v.y; v.y = t;
}
else
{ // v.y < v.z < v.x
int32_t t = v.y; v.y = v.z; v.z = t;
}
}
}
return (893 * v.x + 446 * v.y + 223 * v.z) / 1024;
}
TPE_Unit TPE_dist(TPE_Vec3 p1, TPE_Vec3 p2)
{
p1 = TPE_vec3Minus(p1,p2);
return TPE_vec3Len(p1);
}
TPE_Unit TPE_distApprox(TPE_Vec3 p1, TPE_Vec3 p2)
{
p1 = TPE_vec3Minus(p1,p2);
return TPE_vec3LenApprox(p1);
}
void TPE_bodyInit(TPE_Body *body,
TPE_Joint *joints, uint8_t jointCount,
TPE_Connection *connections, uint8_t connectionCount,
TPE_Unit mass)
{
body->joints = joints;
body->jointCount = jointCount;
body->connections = connections;
body->connectionCount = connectionCount;
body->deactivateCount = 0;
body->friction = TPE_F / 2;
body->elasticity = TPE_F / 2;
body->flags = 0;
body->jointMass = TPE_nonZero(mass / jointCount);
for (uint32_t i = 0; i < connectionCount; ++i)
{
TPE_Unit d = TPE_DISTANCE(
joints[connections[i].joint1].position,
joints[connections[i].joint2].position);
if (d > 0xffff)
{
TPE_LOG("WARNING: joint distance too long in TPE_bodyInit");
}
connections[i].length = d != 0 ? d : 1; // prevent later division by zero
}
}
void TPE_worldInit(TPE_World *world, TPE_Body *bodies, uint16_t bodyCount,
TPE_ClosestPointFunction environmentFunction)
{
world->bodies = bodies;
world->bodyCount = bodyCount;
world->environmentFunction = environmentFunction;
world->collisionCallback = 0;
}
#define C(n,a,b) connections[n].joint1 = a; connections[n].joint2 = b;
void TPE_make2Line(TPE_Joint joints[2], TPE_Connection connections[1],
TPE_Unit length, TPE_Unit jointSize)
{
joints[0] = TPE_joint(TPE_vec3(length / 2,0,0),jointSize);
joints[1] = TPE_joint(TPE_vec3(length / -2,0,0),jointSize);
C(0, 0,1)
}
void TPE_makeRect(TPE_Joint joints[4], TPE_Connection connections[6],
TPE_Unit width, TPE_Unit depth, TPE_Unit jointSize)
{
width /= 2;
depth /= 2;
for (uint8_t i = 0; i < 4; ++i)
joints[i] = TPE_joint(TPE_vec3((i % 2) ? -1 * width : width,
0,(i / 2) ? - 1 * depth : depth),jointSize);
C(0, 0,1) C(1, 0,2) C (2, 3,1) C(3, 3,2)
C(4, 0,3) C(5, 1,2)
}
void TPE_makeCenterRect(TPE_Joint joints[5], TPE_Connection connections[8],
TPE_Unit width, TPE_Unit depth, TPE_Unit jointSize)
{
TPE_makeRect(joints,connections,width,depth,jointSize);
joints[4] = TPE_joint(TPE_vec3(0,0,0),jointSize);
C(6, 0,4) C(7, 3,4)
}
void TPE_makeCenterRectFull(TPE_Joint joints[5], TPE_Connection connections[10],
TPE_Unit width, TPE_Unit depth, TPE_Unit jointSize)
{
TPE_makeCenterRect(joints,connections,width,depth,jointSize);
C(8, 1,4) C(9, 2,4)
}
void TPE_makeTriangle(TPE_Joint joints[3], TPE_Connection connections[3],
TPE_Unit sideLength, TPE_Unit jointSize)
{
joints[0] = TPE_joint(TPE_vec3(sideLength / 2,0,
TPE_sqrt((sideLength * sideLength) / 2) / 2),jointSize);
joints[1] = joints[0];
joints[1].position.x *= -1;
joints[2] = TPE_joint(TPE_vec3(0,0,-1 * joints[0].position.z),jointSize);
C(0, 0,1) C(1, 1,2) C(2, 2,0)
}
void TPE_makeBox(TPE_Joint joints[8], TPE_Connection connections[16],
TPE_Unit width, TPE_Unit depth, TPE_Unit height, TPE_Unit jointSize)
{
width /= 2;
depth /= 2;
height /= 2;
for (uint8_t i = 0; i < 8; ++i)
joints[i] = TPE_joint(
TPE_vec3(
(i % 2) ? width : (-1 * width),
((i >> 2) % 2) ? height : (-1 * height),
((i >> 1) % 2) ? depth : (-1 * depth)),
jointSize);
C(0, 0,1) C(1, 1,3) C(2, 3,2) C(3, 2,0) // top
C(4, 4,5) C(5, 5,7) C(6, 7,6) C(7, 6,4) // bottom
C(8, 0,4) C(9, 1,5) C(10,3,7) C(11,2,6) // middle
C(12,0,7) C(13,1,6) C(14,2,5) C(15,3,4) // diagonal
}
void TPE_makeCenterBox(TPE_Joint joints[9], TPE_Connection connections[18],
TPE_Unit width, TPE_Unit depth, TPE_Unit height, TPE_Unit jointSize)
{
TPE_makeBox(joints,connections,width,depth,height,jointSize);
joints[8] = TPE_joint(TPE_vec3(0,0,0),jointSize);
C(16, 0,8) C(17, 7,8)
}
#undef C
void TPE_bodyDeactivate(TPE_Body *body)
{
body->flags |= TPE_BODY_FLAG_DEACTIVATED;
}
void TPE_worldStep(TPE_World *world)
{
_TPE_collisionCallback = world->collisionCallback;
for (uint16_t i = 0; i < world->bodyCount; ++i)
{
TPE_Body *body = world->bodies + i;
if (body->flags & (TPE_BODY_FLAG_DEACTIVATED | TPE_BODY_FLAG_DISABLED))
continue;
TPE_Joint *joint = body->joints, *joint2;
TPE_Vec3 origPos = body->joints[0].position;
for (uint16_t j = 0; j < body->jointCount; ++j) // apply velocities
{
// non-rotating bodies will copy the 1st joint's velocity
if (body->flags & TPE_BODY_FLAG_NONROTATING)
for (uint8_t k = 0; k < 3; ++k)
joint->velocity[k] = body->joints[0].velocity[k];
joint->position.x += joint->velocity[0];
joint->position.y += joint->velocity[1];
joint->position.z += joint->velocity[2];
joint++;
}
TPE_Connection *connection = body->connections;
TPE_Vec3 aabbMin, aabbMax;
TPE_bodyGetAABB(body,&aabbMin,&aabbMax);
_TPE_body1Index = i;
_TPE_body2Index = _TPE_body1Index;
uint8_t collided =
TPE_bodyEnvironmentResolveCollision(body,world->environmentFunction);
if (body->flags & TPE_BODY_FLAG_NONROTATING)
{
/* Non-rotating bodies may end up still colliding after environment coll
resolvement (unlike rotating bodies where each joint is ensured separately
to not collide). So if still in collision, we try a few more times. If not
successful, we simply undo any shifts we've done. This should absolutely
prevent any body escaping out of environment bounds. */
for (uint8_t i = 0; i < TPE_NONROTATING_COLLISION_RESOLVE_ATTEMPTS; ++i)
{
if (!collided)
break;
collided =
TPE_bodyEnvironmentResolveCollision(body,world->environmentFunction);
}
if (collided &&
TPE_bodyEnvironmentCollide(body,world->environmentFunction))
TPE_bodyMoveBy(body,TPE_vec3Minus(origPos,body->joints[0].position));
}
else // normal, rotating bodies
{
TPE_Unit bodyTension = 0;
for (uint16_t j = 0; j < body->connectionCount; ++j) // joint tension
{
joint = &(body->joints[connection->joint1]);
joint2 = &(body->joints[connection->joint2]);
TPE_Vec3 dir = TPE_vec3Minus(joint2->position,joint->position);
TPE_Unit tension = TPE_connectionTension(TPE_LENGTH(dir),
connection->length);
bodyTension += tension > 0 ? tension : -tension;
if (tension > TPE_TENSION_ACCELERATION_THRESHOLD ||
tension < -1 * TPE_TENSION_ACCELERATION_THRESHOLD)
{
TPE_vec3Normalize(&dir);
if (tension > TPE_TENSION_GREATER_ACCELERATION_THRESHOLD ||
tension < -1 * TPE_TENSION_GREATER_ACCELERATION_THRESHOLD)
{