From 6d3b46c9dedce728aa3a6fbf047b416a49bbdca4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 9 Jul 2026 11:31:38 +0100 Subject: [PATCH] Add box3d src to project; Add simple build script for Windows --- vendor/box3d/lib/box3d.lib | Bin 2244240 -> 3262634 bytes vendor/box3d/src/build.bat | 8 + vendor/box3d/src/src/CMakeLists.txt | 256 ++ vendor/box3d/src/src/aabb.c | 176 + vendor/box3d/src/src/aabb.h | 80 + vendor/box3d/src/src/algorithm.h | 19 + vendor/box3d/src/src/arena_allocator.c | 178 + vendor/box3d/src/src/arena_allocator.h | 114 + vendor/box3d/src/src/bitset.c | 66 + vendor/box3d/src/src/bitset.h | 67 + vendor/box3d/src/src/block_allocator.c | 90 + vendor/box3d/src/src/block_allocator.h | 35 + vendor/box3d/src/src/body.c | 2454 +++++++++++++ vendor/box3d/src/src/body.h | 256 ++ vendor/box3d/src/src/box3d.natvis | 147 + vendor/box3d/src/src/broad_phase.c | 535 +++ vendor/box3d/src/src/broad_phase.h | 79 + vendor/box3d/src/src/capsule.c | 385 ++ vendor/box3d/src/src/compound.c | 1196 +++++++ vendor/box3d/src/src/compound.h | 14 + vendor/box3d/src/src/constraint_graph.c | 328 ++ vendor/box3d/src/src/constraint_graph.h | 77 + vendor/box3d/src/src/contact.c | 873 +++++ vendor/box3d/src/src/contact.h | 183 + vendor/box3d/src/src/contact_solver.c | 2472 +++++++++++++ vendor/box3d/src/src/contact_solver.h | 73 + vendor/box3d/src/src/container.h | 184 + vendor/box3d/src/src/convex_manifold.c | 1600 +++++++++ vendor/box3d/src/src/core.c | 255 ++ vendor/box3d/src/src/core.h | 161 + vendor/box3d/src/src/ctz.h | 134 + vendor/box3d/src/src/distance.c | 1904 ++++++++++ vendor/box3d/src/src/distance_joint.c | 579 +++ vendor/box3d/src/src/dynamic_tree.c | 2186 ++++++++++++ vendor/box3d/src/src/height_field.c | 1583 +++++++++ vendor/box3d/src/src/hull.c | 2791 +++++++++++++++ vendor/box3d/src/src/hull_map.h | 24 + vendor/box3d/src/src/id_pool.c | 73 + vendor/box3d/src/src/id_pool.h | 34 + vendor/box3d/src/src/island.c | 735 ++++ vendor/box3d/src/src/island.h | 101 + vendor/box3d/src/src/joint.c | 1748 +++++++++ vendor/box3d/src/src/joint.h | 415 +++ vendor/box3d/src/src/manifold.c | 246 ++ vendor/box3d/src/src/manifold.h | 57 + vendor/box3d/src/src/math_functions.c | 616 ++++ vendor/box3d/src/src/math_internal.h | 480 +++ vendor/box3d/src/src/mesh.c | 2411 +++++++++++++ vendor/box3d/src/src/mesh_contact.c | 1186 +++++++ vendor/box3d/src/src/motor_joint.c | 439 +++ vendor/box3d/src/src/mover.c | 70 + vendor/box3d/src/src/name_cache.c | 155 + vendor/box3d/src/src/name_cache.h | 42 + vendor/box3d/src/src/parallel_for.c | 135 + vendor/box3d/src/src/parallel_for.h | 23 + vendor/box3d/src/src/parallel_joint.c | 251 ++ vendor/box3d/src/src/physics_world.c | 4099 ++++++++++++++++++++++ vendor/box3d/src/src/physics_world.h | 350 ++ vendor/box3d/src/src/platform.h | 104 + vendor/box3d/src/src/prismatic_joint.c | 725 ++++ vendor/box3d/src/src/qsort.h | 186 + vendor/box3d/src/src/recording.c | 1279 +++++++ vendor/box3d/src/src/recording.h | 386 ++ vendor/box3d/src/src/recording_ops.inl | 232 ++ vendor/box3d/src/src/recording_replay.c | 3634 +++++++++++++++++++ vendor/box3d/src/src/recording_replay.h | 258 ++ vendor/box3d/src/src/revolute_joint.c | 673 ++++ vendor/box3d/src/src/scheduler.c | 198 ++ vendor/box3d/src/src/scheduler.h | 15 + vendor/box3d/src/src/sensor.c | 452 +++ vendor/box3d/src/src/sensor.h | 41 + vendor/box3d/src/src/shape.c | 2393 +++++++++++++ vendor/box3d/src/src/shape.h | 155 + vendor/box3d/src/src/simd.c | 202 ++ vendor/box3d/src/src/simd.h | 357 ++ vendor/box3d/src/src/solver.c | 2328 ++++++++++++ vendor/box3d/src/src/solver.h | 306 ++ vendor/box3d/src/src/solver_set.c | 633 ++++ vendor/box3d/src/src/solver_set.h | 67 + vendor/box3d/src/src/sphere.c | 247 ++ vendor/box3d/src/src/spherical_joint.c | 745 ++++ vendor/box3d/src/src/table.c | 248 ++ vendor/box3d/src/src/table.h | 53 + vendor/box3d/src/src/timer.c | 700 ++++ vendor/box3d/src/src/triangle_manifold.c | 1274 +++++++ vendor/box3d/src/src/types.c | 155 + vendor/box3d/src/src/verstable.h | 2069 +++++++++++ vendor/box3d/src/src/weld_joint.c | 314 ++ vendor/box3d/src/src/wheel_joint.c | 1105 ++++++ vendor/box3d/src/src/world_snapshot.c | 1335 +++++++ vendor/box3d/src/src/world_snapshot.h | 21 + 91 files changed, 58118 insertions(+) create mode 100644 vendor/box3d/src/build.bat create mode 100644 vendor/box3d/src/src/CMakeLists.txt create mode 100644 vendor/box3d/src/src/aabb.c create mode 100644 vendor/box3d/src/src/aabb.h create mode 100644 vendor/box3d/src/src/algorithm.h create mode 100644 vendor/box3d/src/src/arena_allocator.c create mode 100644 vendor/box3d/src/src/arena_allocator.h create mode 100644 vendor/box3d/src/src/bitset.c create mode 100644 vendor/box3d/src/src/bitset.h create mode 100644 vendor/box3d/src/src/block_allocator.c create mode 100644 vendor/box3d/src/src/block_allocator.h create mode 100644 vendor/box3d/src/src/body.c create mode 100644 vendor/box3d/src/src/body.h create mode 100644 vendor/box3d/src/src/box3d.natvis create mode 100644 vendor/box3d/src/src/broad_phase.c create mode 100644 vendor/box3d/src/src/broad_phase.h create mode 100644 vendor/box3d/src/src/capsule.c create mode 100644 vendor/box3d/src/src/compound.c create mode 100644 vendor/box3d/src/src/compound.h create mode 100644 vendor/box3d/src/src/constraint_graph.c create mode 100644 vendor/box3d/src/src/constraint_graph.h create mode 100644 vendor/box3d/src/src/contact.c create mode 100644 vendor/box3d/src/src/contact.h create mode 100644 vendor/box3d/src/src/contact_solver.c create mode 100644 vendor/box3d/src/src/contact_solver.h create mode 100644 vendor/box3d/src/src/container.h create mode 100644 vendor/box3d/src/src/convex_manifold.c create mode 100644 vendor/box3d/src/src/core.c create mode 100644 vendor/box3d/src/src/core.h create mode 100644 vendor/box3d/src/src/ctz.h create mode 100644 vendor/box3d/src/src/distance.c create mode 100644 vendor/box3d/src/src/distance_joint.c create mode 100644 vendor/box3d/src/src/dynamic_tree.c create mode 100644 vendor/box3d/src/src/height_field.c create mode 100644 vendor/box3d/src/src/hull.c create mode 100644 vendor/box3d/src/src/hull_map.h create mode 100644 vendor/box3d/src/src/id_pool.c create mode 100644 vendor/box3d/src/src/id_pool.h create mode 100644 vendor/box3d/src/src/island.c create mode 100644 vendor/box3d/src/src/island.h create mode 100644 vendor/box3d/src/src/joint.c create mode 100644 vendor/box3d/src/src/joint.h create mode 100644 vendor/box3d/src/src/manifold.c create mode 100644 vendor/box3d/src/src/manifold.h create mode 100644 vendor/box3d/src/src/math_functions.c create mode 100644 vendor/box3d/src/src/math_internal.h create mode 100644 vendor/box3d/src/src/mesh.c create mode 100644 vendor/box3d/src/src/mesh_contact.c create mode 100644 vendor/box3d/src/src/motor_joint.c create mode 100644 vendor/box3d/src/src/mover.c create mode 100644 vendor/box3d/src/src/name_cache.c create mode 100644 vendor/box3d/src/src/name_cache.h create mode 100644 vendor/box3d/src/src/parallel_for.c create mode 100644 vendor/box3d/src/src/parallel_for.h create mode 100644 vendor/box3d/src/src/parallel_joint.c create mode 100644 vendor/box3d/src/src/physics_world.c create mode 100644 vendor/box3d/src/src/physics_world.h create mode 100644 vendor/box3d/src/src/platform.h create mode 100644 vendor/box3d/src/src/prismatic_joint.c create mode 100644 vendor/box3d/src/src/qsort.h create mode 100644 vendor/box3d/src/src/recording.c create mode 100644 vendor/box3d/src/src/recording.h create mode 100644 vendor/box3d/src/src/recording_ops.inl create mode 100644 vendor/box3d/src/src/recording_replay.c create mode 100644 vendor/box3d/src/src/recording_replay.h create mode 100644 vendor/box3d/src/src/revolute_joint.c create mode 100644 vendor/box3d/src/src/scheduler.c create mode 100644 vendor/box3d/src/src/scheduler.h create mode 100644 vendor/box3d/src/src/sensor.c create mode 100644 vendor/box3d/src/src/sensor.h create mode 100644 vendor/box3d/src/src/shape.c create mode 100644 vendor/box3d/src/src/shape.h create mode 100644 vendor/box3d/src/src/simd.c create mode 100644 vendor/box3d/src/src/simd.h create mode 100644 vendor/box3d/src/src/solver.c create mode 100644 vendor/box3d/src/src/solver.h create mode 100644 vendor/box3d/src/src/solver_set.c create mode 100644 vendor/box3d/src/src/solver_set.h create mode 100644 vendor/box3d/src/src/sphere.c create mode 100644 vendor/box3d/src/src/spherical_joint.c create mode 100644 vendor/box3d/src/src/table.c create mode 100644 vendor/box3d/src/src/table.h create mode 100644 vendor/box3d/src/src/timer.c create mode 100644 vendor/box3d/src/src/triangle_manifold.c create mode 100644 vendor/box3d/src/src/types.c create mode 100644 vendor/box3d/src/src/verstable.h create mode 100644 vendor/box3d/src/src/weld_joint.c create mode 100644 vendor/box3d/src/src/wheel_joint.c create mode 100644 vendor/box3d/src/src/world_snapshot.c create mode 100644 vendor/box3d/src/src/world_snapshot.h diff --git a/vendor/box3d/lib/box3d.lib b/vendor/box3d/lib/box3d.lib index 0448b70ecfc235c3eed95854714e0086c86cc973..38b5dad3636609168a26b0040a865ae90bc45fe7 100644 GIT binary patch literal 3262634 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

xmMHH2&h7VD1Hw*h!EW%Lc~ROhyM<4)C|UXJKIQ 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

%dSKZV46r2x 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

yGu9&R~Va>t{a&!cmW3aFjp(YZ&ZfSG}zw~86m zfdVxk9Qn7oa4?~Kea@r%23U(n_c<4S4M>1M*bt9E*au*0_%$F- z0lNwj0iZ%0)Z}sjjckU1crGdtpZNuARAN5!3zn!PfZ7HsDWCyW5B`>XPjL8yeBc2# z_+_joW(*)%0cJuhhJ=AG!~}kg8~hsA;V~|b#T1D8zaC&4`C+m1)&tW%h|0@oDi7mS zxe}@pmWpN~sf3u-4p9N|Vil4!Ec_wL5+KUp$uYo#fq_5#fbjv3?sFcUu1i4O7ne@g zH6S_!M5KU-5`IC~7Jfn3DPZG0y03v^*Q2)=)RqDZfeja60IBuh7w`}OMNI_1fP(@^ zB!geT!vG{w!7t$8z%S_9!7tzuz%S@K;}gGt1E@Xkx`Myum^&hvy8Q&e5$4nV!K3?! z=fQUhU|T%8Z@BPpKkotV9p8iWj+H5@t zlHEZAI(`uzy98X>s}?8O!Z zkP-ab{RDivFL-o^Nu)XcKk0e!l>sO~(m*|Fmo%5o5EVC%)=MQ}KFLlI#wT4{57c>k zbVmjFcHeO6KIqXK%x3BItCTm*#nS6vDO;M0rBI3N|AU_Y4|p^m;_$UTT=T%A+XtF^ zUaofo1q-C~NMMfSk$TnaKr~9wC_wp82q3GLLRhMz2u` z0cR&r)^bsa0f!GL48XZ7fxkt}je!9)i4f9#xHCq@fq%Q3fKRXMfwcc8JPy7#aOBrG zk=E&=;^N3JauO7m25Fr!DsEu6gR*RhibWcKz3TyRK>``UdwIkap!*isrTiGkwVN5um~ zxv2QKfafg)Ye2mx!4j1aaNvO>6;i~5nTYb&g@4-#{+5-_pv2~*;(;8|p$8oKH7p;{F=UyyznyC87+KK{fQ-=LHq;J4q+pQ1UxH& zd9OqQHcNv#;C-As1OyxBo-U6_nO2BRe#V@$a4hjQs zmmL%^{4M=F#MXn4?gn?x_;o-425|3rK24p4T5as zZ_x*tzuiv&+`s`94=)U$wVw;LItM%NB|DN%Q0$;oqOWX0wV#^+zs3nraRYT1nsXrL z?6*a@0K=R`#G6x&&zvaY&C$naju6-!KLP%21X~BsZSeT(lnp3+h%#qBK69GD<`5Om z;Yj8{V%im~nQ%1Qs-l9%W3#elS30hvT zKiNPfp#Te0uaiKh8&4X4eJf~q1?-+~Uk0CEFt-yF;k}_8;PyuMk2Fwu@?HU4o_KWM z@aXmh4F-eD7f4S5tlgvggbV-n(;f$38dx7M<#e<@$=@>564aAB4(<0LO<;oNFTuJ% z?t58eiPF0T2VH0D90mpkQUlN@*^S2|*^h%C)X8<_fb?-m3_#Cb2boX&f}j9Ah#Y`t zEIiK#OErC%{%mP#Cr^ z=l}cvAJP8@M-soFD=2tC2}OZl(A9un&=u5a58xMc1to?8aPVSFW8LQ{2_10AfXxOa ztL`72dqMu?Z&_y!3LRLl@CIl|;NWWokQ)A$N#+a;*83b-7#NDgyRSp%$iZfSqrcdk zfuS3e5qRo-y4x&RKx;xmIY2cTs7y)wf5Nxh;EDLohvwTk(#W)nFbWvrJ!QvDp_xAj0NwnW}%0u6dZ7{j{4D8UO3Sa90#=|1cM8oh$# zsh7TB%OD(3wuTJr`EHA``DqOD0K`n6?hiiQ4?u%>AX)yFyT%L**7Km5_A*AM-3QjX50YDDqJ6qu z?hAnYZsL*bb04yH$S2w5K8puv!A!_~4v$Wk`vPEpb%%gNeV{zBMo>QY?7p7%{{+A0 zR?uiAC{A{O))+7_{QvKx{KMnmLxt``p4{g>xKHqF?!5piYSL%_rO6mkQqTOSmld>*}_C%};htH(iWDLEW_Jp?-actB0gwEri;u>&p&`2}4e zJ^PnFSnLbq0Zo@C!>sg@L9e`h$K?>1~Y$vg9b8= z0UwvZHO!APHp$--sfQ!=!pa7Ik@JrH8n-%JR5Tp2NB{+NSO@sw*Uh;s|&V*3TaT4#4p$jDz^E-lT6?u z8`6#G1(n*6nWk1y!3~;g;uq`%mEEB348LG2sQBg=>_sZP;UN!d6(Ng*_$IJI9^@&I zfCVE11AhxIl8gBTTETAF?kC{U{exei7gU3QyEd&SKy54maMc3p-#UP1u|UgP0@D7U z5y(efi(W&TO9nFt{eC@eHWzh*M+EffXoYn1S*z}m4gnbaSQ8M zyM+Zm*cn0tbbby=o zf{qFN0uBZIf{q3J0u2*D;Um}yk!b|;S~h7TCx%c4kM18(Tfspkzz7LiaF7dl3h)au z@e4RA@C$kg@C$ev@C!OA@C!IP@C$kw@C$eb@C!OQ@C!I5@C$kc@C$er@C!O6@C!6T zZ1gJN7w81p3SL>>1QG;Q;{1YLAVL1^VFGFYPj$L-cyzl8q;-b!q;-ahc=U!cq;>l8 zfu<|LWsa*DIAC8c`iB&3G)u&g1Przj6d(L8&02`y;unBqknLg6ln4qWNN|B;O%pup zae`j}oMS+K1I_Gf`ltjz(waaoBt^b70434{egSYEO6v?!@d2g70)7E-P68z~zivMP zeofZ~egSZf0wqO|x)5kG2c>D(1^k-66A(%7Wdmsa3T%M|q{RvLh)1t411MNQJxTB& z1r<`2M|Z;ljKt;9*$5JZ&5k3{NE1MynIq63tSe*;7BaT$S^;q>c$U<)2F5|vwiT3s zKxG7|{{vG3;)A+e$b3-O3z-i}ZODAk(kf&=XgC0w4;sus=7T&1=7W+1$igKcueW4q zASDe@nt+5kC_{DM@aT1AfMh6!wEw3d#VZNn?g7fe3H$;LAV2d9dKT~tbb!184|ix5 zgJdsAxOanck}pRmxLEY)2A7(k{Fc_)dk(aR!cPE_jHaGpU|>k=oO%V(Fi-1j1#Pnc zd-A2lZ{(bYSdaipL&&v-D+8o-@`aX8uF%rS7g{>GLQ5y#07%{7itvwb0ly%4U<7O< zXe13Z5CmFEfxA(kucmO`~_e0a63Bg1Qah0tZy8f=Xx`P=VBPQ~@bg zhEhM`<>t>wVL!N%dVxGrpbnFy?t;=OLz`HvmxFY*iI|cOkwb(Hc;FW_JJ$^vYyc%q z{uU#UVUY1P&=jCRw}V8d>kZI22ENfX&>)>tdazW;ACu&5^3CSEt2pB*$g9c*2)0LnpA|DkOP*(vJYfH(E}R( zynqyh;87PBkg2dWlHl^;Az(e!bRJc(D7gc28Gsl;BbCmFz!%Dn4x1{m-Yp*mc zhzR;?53rWEX^LK3<= z+$e&fE`!gU|B@hIf}BLqoGX%8eF<^TcCa}_g;o!Cb1*_H1#Be9X~g6`Gwh~9Tqg-O ziYV8;lfdK4ixNbK)-rtNbP{h)9GW?xk_~yP1De3Vp$8dahIm;`0(mJbWVI-GsW+_f zLSJPKp2LL=55Evc4FzIt-5^ehtwm_IqEv37;zWnN7Cv)$i8tqw7-}e>xaX)C(e9ar z&zxG~%?ZM1jw$ixaN#rOttip{IwXoGe3lY#P8B|LVu?4$2%kBkU~`Bn173;X@z+@q zqQhqqK66@$Hzx|8Ikv=`BZSYKufiaI5fwhCgt3MXxC}y`&|U;KlGqZw1iPt_;w&6& z6j83z!DkLX*c_0P2-bH`gs}P&;-2F|pwJ@9oH_W+sRx@wl)pmonPUz%hbZ^(;4|mF zAkqFhB8Vq^mJ@GI4L)Wz zusI+n5loeS_{=dR-W)c3=Dg%5+F$$l@r2JpusKBes|=qxkzjL(3THj+=3t}(Hn5Q( zrxBA1Zt&spz@#ZMuGY53$6liFOh)o{1xbgUF zFE`QtnuO1sQsT|=!e@>y*c_tD3MPE!Jmn(VU%Nm@x4`EP(8hh{5^qisK666B<`CsC zE$rriQ#&N#LsB~v*ho;4B&H0!0y_K!moK-2PJbaTZ}#9bCm(DM$VmjtKo@-Gs1t9_ zAMkNu@TEks&NU>6?sI@r0a2m81)n+7!R8R*2S&hq zpdcr_f=*3n1)Wm$iC+*rs|~KfK)wMjc`E?z$AB;M11*FEOM{%{(dl}EU%>SMzvfoZ znjPr6W&V~DHc&WUL*57UGJy@eBgUiq9B7LUXiX5<7?19A5Qo8dSdRs`_L*OBFUW-; z=YQrG+zOgH2TfpsCg~wML5rP0Yj+^C{tz+f1*pKtTgi0$N@G(E{=sNaO}=?+pb}a}%@|1vFEJSh4c* zC<`dT@P{Ah?0o}CGqi~!NOYh@5awPHaLNQlIA{qYC`6txfRFlW1%(C(Gmz$g(5gk) znva&7;B$*mljc#-af3rWVnL@Df#=eYBK8er2Qw_p2xc_MYG*&t$$H@21`2#o$_4Eo z10_IEszqITBmg}a7qqTj!1uyyX^(C;(BfwX@SZZzVq_oab}~1f&R&p1Kye320-(|c z6vZG>aFpUn3ZQfeat)}M0lDN0Iaxt)D+g%4Td);$5Eg&SInbfIXjx$wBPc7t^8w6h zgCpcXJt8`y<={^iK2W&{U8;ACB>l^&--N*j_?`4tibmIY^ zBL_N$3w(|o z1-yIsTvho_gfkFTG9KwwaCNN8AiL}XNSOl(|yLSj;K zN@`kqMrKxaPHtX)L19sGNoiSmMP*fWO>JF$Lt|5OOKV$uM`u@ePj6rUgo%?TPnkMx z`iz;gX3v?sVBUPtG*}rJASaLOvNAA0P9BeDWnjn#pNLt`%E0gtY|2|!1_mpz-k+=t44_LM zSs1w3U}EBIFm>u|uuwCGv+Ut4Z#XLq&Ps%{s^P2~Yzz#xVB4O;P5uIx{SRkxvBPW& zU}s>k1M7%ohq)z#9hTasvokPQgJqY%W!JMaFuVr)?LIpLgAG{r4P5p&++;Qmm>q&} zRyYR(11Qa~Fr;w6Y%77wHo#dua29Ap0g?w7!Nu0WSy$jJ&isePMFCoTnr43V3P&8U?wYa!A#bHv&`WvXD*oD zZY~A}C$QcbaJ?(w>Ndk!`{1loaJ@p@3=Ga-z4F{Jd-b_t_FBPNu5eZ$oD~aaWx`pY z(MU+ib)B1m!3Avc6S&D=;OaoP20=oXiw9<#7!S-xaXbtRu3){{JTO;P!_~FHSrg%` zxp2Ktc^DYnzvh@NC4E|uT9=O;}0R{%p>HjPY$KYbu1Q-}zf%CbzAOk}nSci%r%np4)nEOC; zmqB3J9Jp*bToyD#8Vr`*0+-znmj%tkg6azv21Oy5$tFS!4DZ19ZWMx*z#oJd7(&6O z2njPVKyFHq6^50-YQnG*I7t|0N~ti+6kZXSDLx_$3}IkXK&uiU?n7W4|3=E*! zj)mbjT!(@v0|Otp&7dj@vm-~8fgv1h%1%+3ZD&MbrihBcOi>ks#exBxWesPgiosmf zCC0!Y2)3hN3}(j%F$MNAkDy#1XgzzuI>h0mP3YtAsH+yDg!e`K?bHbK!$-K z1uPp2m(767&X8eXNCnF-h0AV$%Yqi>rh#QY!({_x85q*RV#%^F+iuG;Fo2G_W?^^% z7yBU#3rS`<7>f_il7h3;;4IL>YDk%ACkJ!=R5=C)Q0t9_VUZlnwzYDw7QkIO28LL$ zTVBb*+FW1c7#Q|~!!Jsnf#DcfHc1}lf+9Gp3C@}$4@+U53JeUzU>(^C3=EK4S1J@> zCbuXsFo2HUW?}fQ0MoHZk%6HctYf_*Ox;dJ28KOg7ld`M z4re*4z+BIx%D~VBcDMUyA7*lt zJ_AD@ST-FlJ4K&?As;NZ5H7YxALjGzaMmk*1_sd4_$&?T84Xgo4xV5kJkzK6^Hg6m~9g4tnV#K2GmR_AI2 zvm*d5+hD}NPz{#th0D%>%bqY|V5kAhUWLm(FoL;2z?gxd7Az}k3^Q577-p|AoRwzG zz)%NPR}5EIXACoCoiPJLJy>=(T=p1T?>A!x2GE^HEDX#hFgy56V0PGAY8{qxa=J`>p7gIWy-+N0@h(}3Uh&zDa-{$rVI?Ao<0jh zJzTa6F1yQ=fuRko_c&bk5?q$ajDevYEX!{OGg-%9+`eFfM170%MIWMG&AR%dDnbAf{;%#;F428O9%*;=@4hb7E?J1iL( zrh#RT!euYOWf`m(7^Z_|K_@OkaSQzHQ zWmmvuL5ES!2J3wam;C^j1s!=g2P~^=4Kvxo8fGu(V9mK;*;2S{gEh?FZa8b7H3P#u zu)5Q5b=Tmgu-Py$%m>Q~+raFQvw@l7XT!j-04y5~mraAqPP1WPSO}I~43}MJ19RU! z8wQ5OVA7;YYPJjvOTeIpZVHbb1H*E#tfU>x4i!6?DIsm{6}W6!{_3ar=C9%iqLJ=_u=fOUL_>tJ$# z=`eC&U|0*5wReEo<^`9na$sNpjX1I}w8Ld5!DSCQFfeQY>pc&by#<%$a%5oG2$mIh zgqf`52(vfHk%3_oST-Imo8<`esW!Avf4Vqn+~maT`&b~(XZH5JY};l#kO1FY^UT-^h>DFV(63_HQHvd%C& zG@N0kL^v}r>;lWC!etBKvJ0FU7(llSvoNfM%kFT7x$lKD0|RJemxbXgT$aHFX0m|` z1H)dh$+j*qdp+Q?6)p@6`@piTaM=lP*#j;N4Ew>dXW_CpTwwNcxH2#t0LzNH!c10h zh1shOXT`ZPFdPJ{%Z95fgUc>+Wnef2mfh_NYYCopWnj1uZo|EEWnef0W-+?K+JwAt zmW>+&Ln(M{IK&O6H^~j=f?PPO0?wM_#=!6sY~BVpn8}CX=ADPL{<$$Qlz~kac88g) z=ngYk2hMVVvqIpkcsQ#Z&gyiB**3===8qL{)^&FVhH|iN-{H2gdcbTGfU~6GEPD@_ zKLX)mF>qEooYm<8x7P!1FPwGVgMpy}Z0{Qnm@j|9^|E@xTp$Z)fo{lw^kyyKVgYbg zk|)fzQcsv|jd0cmPX>lcux)!iVYZ!y>%9hNJ%qDjykIU!hqH>{tSMeF+m?I5Y}*WH zJ@R5;r~=#e-3w+Lt2fL%0XRz<&YIy3GjFvw%)ISz)-!JghH9{RpW$MHJ}{FdePBAY zd>BBJU<@n_Mm`J-=fP>s(T9OS7Cgq|=>r=xiuQq}@lGEG1_iKeA6!<(mx18{cn(d$ zmx19Cn5E*&zyO**VP??qWng#$7Sr)%V0a2<8Tc|VJOi^#d>I&?gIN~73=A*8EE``2 zhL>QLgD(TaD=^E&mx19mnC0Qi!0-ml^6_P0cnf9)_%blO1G7SW85rJ!SrNVr3?IO( z7+(g4k6>1UF9X9TFe}BEf#EZlmEp_4@CD4u@nvB63T74fGBA7tvr2p!7`}s96}}7% zKftUSUj~MsU{-@K0|RJoh?$|qmx19oSggaBf#DCB)#J;+@E6RQ;LE`956qh4%fRp- z%$niLz`(!?3jR613=E85)&gG!1|~3Ti7x{KGnlo)mw|x=%v$5iz`zP-ZSZAaU<0$Z z_%blCgIPO#85lUgtUbOA44h!r0bd3NE->qeF9QQNn03OJfq@6iI^)a0zzb$w@MU1& z1GBF9GBEIiSvPzc7(jQiGBe!qWnd5li#_mVU=RYcp7=5_2!mNKd>I%-z^pgE3=E=R z)(2k(1~D+}i!TF%IGFXrmw`b7%=+WYz#s``G59esfM(Q~8Cd)n7^J~s9DWQ8GGG>u z9|MCdm?hxH!0-?pY9f9N402#G2|oq~c`!@HkAXn}%u?`UU{D0JRQwnil)x+vKL!S6 zFiXdefk6e#GVo(yPzAG0{1_P2z$^k3eGz-Yg5aP$cpa&L<@MB=m2eV@Q7#Iw|tOP#>2176_#gBo( z2+YdxV_+}_vvT|x7)-#d0zU=@Q!uNYWx@&EWoS=KL!R%FssFn zfx!yQ>hNP=um-bw{1_N)z^n;=3=Fnl))YSm20Jioh93iiJ(xAekAcAf%v#{bz~BgG zE%9Sua00Vd_%SdzgIR0*7#Li@tPOq)46b0-7C#0CH!y359|MCsn6<}`fdO=9H#5Tl zKL!R*u-Fkl1_m!M>x3TzgEyFU#*cvkbj>z1!v#ME24Aq)6+Z?BKQQZt9|MCwn03dG zfdO=nIWxlpKL&xCZ!Lok^2#*cv^1kC#2$G{K@W_|HvUL20<$#y85okmEFFIa2GD)#%nSzp3=FAY zF%y3VhBPqC!k>X59n7-vXJ7!`_sz`U;LpI22^MqlXJE(zvpoD67_z}EAAbgh955@u zpMfD4%nI>mV8{crBK#Q`^1-Yae+CB7UG>Zi3H}TWg)tHPgw0W_=4%uwUcz)%4eYw%}as06cG{23Ukz^o2`28L=d ztH+;#p$5#F;LpHN3uaC6XJDuUvu5}+Fw}!tbNm??8o(@%<~J7IjspDKg9JYD3pjG5 z{XgZxzs*sk6Li&hHzR0w3+MrZ)qmvo%6MXKRFk&S-_G zgx=}We8l5$Bm-;MQY4S6F)%Pd zW<*KT1DPi!O%G&d7^>$pzknymaiEBRy9?}6L@cN?z$U7pu>j5x&T|lRvf=ayt&|#;bLt{1IF@ThjN1Y0pfgax zC!<0yIc+|YaTwIjge+z7Xnv#7?FhPQ!hnCfBRKz=fISW^?K)jlERf8#0hwzDGS>nY zpBjg)8DL8Rz&cSAyhk_A1P@sz1l2=q+(1??K*~P_P^|z89Z03%sqqOEMxXfw0#!in zh8E(WWCcFq6<$3+ifnKIBHga&0V=lx7+}kTAXW%~>IegV0eFQ2vI1TifT|48smPF= zKF6LFFF5O4*Tt!-<ra#TD#x=U07JerRf#2=Ppfb}g+7+`DWf4NMQ70|Kapxd##Z^1n4(ftB^tIS)4hHB9L&U%at4BdSouUU74uc;93z6fiX&$amlu@RQ)*?kxaH>ki;^Q{6N=T~rJ}MNR;}fS&~@Uw|YN z_yycNKnF0JfTRle1^gmF1&J9bDh&8FTU0<^;1_Vq09~XF3Z75=0)7=B3D9YfpZEoP zR6yrJg8k&t4RwAu==Kr=(4{xlM+rpSQLxTq&Y+0vfrKmUitz3^Dxggs9*svp5#SgX z7kgNkfq?tF5!%@G!VxK zOYB*Uu*6=+2)i;Jlo}*L!@@lp-+;Ecpe6>8>rit$zko9bXzP=KNB0YU0dEOVp6?A& zG2jF%^q+D0hvT;u!22>6s(}| z^k_T+@;$C#-NFD{8*>>RtheC7`V5{OelZ{?WDZb)4X&gVP|Gl%?gO3&UkEtzZ~x)K zzx{yc!3P4K+&>`q4n0@!;Qj$A+Gc~!)}96`7py0Pi#7%1f(??DV^j=4^^Ai@^AUyk z!yetRaxMr|&VjEXB0>|JM{gJhs1yS=8Q@JF7Zp2DRM>z@C`ePsr~5egE~3-WgpF#s zBs>*@dVY{acc5ATQqK1=z!u$2h6mS02H2vz`wXx}cc2mgT*aeQcON~FYk(i1>iyt* zXbk{50=pYInD~%`>2(gsV;4Cftx$3IZMa2)~ zU&!tAkgR(aTrnOeP`(}qhY2iF9B@~R?hLTClb~T{NZ^1wF6F$wpz6f~ zDTqN~>(O`w6cV_C7$gT-^$98-A*((?bqyq8e}b0^Y}kSr9Hl62X>fA_BZUX+qKkxbX`L3~&((DOc#!o&tp;WO*lO$0{TWK*0@(0wH)R zR~wB29~BMg4X@A|2GU`K_B+UL(t@G@veK25C>WwWX2@m^!aZhaJqGT>qGWP#v4WAw z(fdf<&7f-)X;Y60bVKS$ESDM3xmE**1yYd<>W+Fe9sva!t|Ip}s22;a)wmd8{U8^eT>)`=pt9MM`eI(HORVgGHNwY+@S_BIFd1fcr?L-PFx@| z!9!N(6P@6j-)MMrGrEAr+2C5e@L97&HlZCZF)=WH}9y>jw2;UktD2Y1$y`<4My4Sq)E`9>`7rs2*ac9Uz+v5Sjvc(*B0ItmtI*D>3WOoZ>`Uf<@2AUiLEeHV3{78T@ zK4|Qlz|4;V`n-}G18g%74Gf0t10vCzkZn3(OXVRk?4tsn2Sl2l2F=4mwhn>S z2MK^ve%k+2pcsT`a#2A~{Ge%L$j%B9H9FCdmFYs3uVICZd3X?6x6I4`go* zX?h^LiJ*E&DhMI_d=NT`DhMH4cu3R)*%d^hCdgJN5;Z|~Pm!nzvTX|777Gn`40Q~1 z1WmVrx+*@&K|CJ4Q6e76P8>eHMLZtezkIr1`t~}p_*fsPV?M^r&|u9_%3}QXwSh-B zmm~i+7cM48{%tv2%m<%xaK8WxFv6HDTo6{T6HBKPk5BKe3>F54<1Q)+2@DJjKD|u^ zETGGa&w6%W_vk(k9@*jFCfss~za;^50$=AA6;Q{&^(}u3s5|M?xkLrj(l`F*(*2ZQ z-UZaKf@t#SK9Sbhq5_)k@;LZXp|eK?WETH+e*y3X;yo&$WAHq?`8~Q@z%m}4EPS5H zVjjuv93H)1j2_k}5FUfv3Ca$=6V$Pj&9j@mqgB?C>F&@pw5i}r*}Wdf4<3lAd@|NJy}2|a~xx4c-g_ozyR`B19&2u-?!HlyoxS> zUvmq%9|j6GPEa_;a55i!%)xyFEWik3vT%0$3xGKuoi!>6j=hd7ojEE6j-70vL3;M}7fc5XZx@+m#12dhgNeqLKg-G4SXG54nOS)<6wp&{2b~ zIv^Phk6y?GGgl=7$X*B$7b0}fj2 z!zC=o+(j8Zy8S^%(($*nF)%Q=8lQAEKH%6J$O?)V28dg`FYpUG2!LJ6FX*8FvF{VV zU;}8guVc3_PiF_H5c23g1yV1-FW3NDO$Jit3u;z$fKD!O>~_`YYy$1>2Z_7teBu{u z0yR-VVbIwGY6y1D0#$opYxy-GVeQckNmroW5$Hri=)9j}ykneWY<%qDeg*~x(9xDG z44_j;AxB7jXJlXiB~}&&J0=DO9&puC%*4Qu1KvsiItru&%)*_cs*rNjEtDL^&d^{_ zlN<$^qHC~c;BP5pW?%s2qhzFfW&916qhP6!Uvm$5%p07)K&gYlqZ?uft{mo#Cx;>Q zo<IQE-ERo z!U~jx!73qZ{w#A;Qc75kIfF_kZ&2w}66@Q|?$UjB7ZdDQRIvLz5Ji&#fuafQ0?<*L z4bWmo0#fXN3mn1)4Ct(4NC5*{*KrNJ%Sal2>K$l80V3uM7XzKj2N8>ei=AYItt{ha zW?=XQHU)I5N+GyN0_`#81Q%GK-Gz_>i<5nAMxmnknrfNPzd$tT@msB|9_9h zH=y(T_%#b&@M~7waN*Z7?skni!7orC z-~zh3+l5~sz<^(~b^*U;=?4Cmb|wafZg&Y#DsbQza98LK2Cpd$;1}>W@aXk@0Gf~o zMRvj`egSuZZg(C?#DRD$@EQy(7T^M^h!Q{t25S1K6o8TiB)_KdAHB}(%CB+4mH*g@ zRr~@D2K)jX`~m?2`~n#Y{M(%c(*B=F<1fC!16pvwzx|NM!3P39+y^|mT^Dqp@I3fH z!H4@`8h_ovG)MmYLtuqzj{J3p9Ia0AgU?WIImzDwI#W5#gTL;i^$Gq~&_WQyP$Pe<+%QPSUx&t28 zCs1+-e+$@K;Rk#=T^|@90O|H+;1_gdaWy{a)9dJDEU@N4w?Cpa07?G&~`x;q^124(b8sPFurXeSIX+r{rI&RyshoZzb%D}+40~L4j1kRpvsJk z5y4^Mg0UejZjatopyC;=#qHXC#-sZxti^31$KP_5nSsHz^$C9qsD0qmxkg2Rk%7Ud z`!lFv0O}Ng8U~>00@v<0{PGSAFs;y50UxMQ3u$+Q)(|4KyLmt|-JrXwTfoZ;Jv&+W z5Sq^;ng!=!%>s{ZSCH|a9r*=o*f>B`2^$ZHa$$ot4RC1#sj6WU0Z}Dv5+KTjO~yBw zAGtBk)FEdfSIW|T8Pqu72L&Iv)eWl?!2*yv(GjgqWQV%Uu``4XQrviS`!aZTy6|aG z*A9>c2Glpwg!)ECc!Ik_ZyEVpUqRakphV))TMlj?fD(>J?|M)I^y!8rsV$H-x6s}m zDD8pjF;LioYX(Pt0XKtge*xcKH&EBW&jD1rXMmaq0gm0^Rka?D-EM@tG;RtYjT)F; z8aG&Tp++Uar87mPpfg6L1XQ1ZS~igC1yWd8rl^#lb!FCrI<4KV46fa0K%4v-zzV@G zhL&B9`~rRs;KB)n+t9lkYa0x0|NttW4E7#^$C9e6P;b4 z%|4FZZZe%spv@jW$p>3c*6F0B@yml&V=*vzcDo69^hWc1;ummX{sMBflufq_^Pf%^ zDHqS?7mTI%;47set4qLro0#~x*yzJE;k_F~;{!Ci329Wwz#A3naFz$06%1#AR`5b} zfR6BmG+{u8(?S{_pyS{mjStWf5}-y23&Ra&SZ@imAs9l_%ctd7T6R2V!up{tZT*MO4|sH6h*;9QO0`gCpq4-WWr?g95h zK_wSxu?wi=0u{C{pbjr6MS>d-pk(U8uQ>(M+u_%o1729_YN4aT-vSzA^XTSsHU8$( zxkUwZ^*gv!U~&aDpqU*HKHzZSJ^&VCL~vL*VQf&>+=<7fvqi;$5mcxi;BPSmb-H?2 zf%=cI!p|q!pT`5da$BLZM+FqZ@KUoy1=Pm#NOtE)>+DhSVPs$c_iei8fR%wp6d-}& z*kGdq>NbM=IV2Qio$frY#{XeeLhB`;?xP;v=Uwq>{gFn&jgRo7{&!qzDs8f z#}_EOBZT9RNAp|8(#wwjk9qX22PFbm=J&9q2(tT6*iz!&I43D8bC@#3s9#CTxi02O0YIHQo-lo(wUD1kzFRQ7HhGAu5o4n=Y0)Dj_H(UrCw|#G|g=XLqr|x=Nsu8d~mwTmmlk_yyeH zr5?P{^XNXsFW?3rz~LA01C@Z_@f%Q)4j#S%d6U6~Ujvd6!Iutz3S@AT4U$tmKJM^0_#)-m zjIf~|(3#|r0__^Skb4AYy@RuU!C9>ElfDJuEYO%#5xCa{8p`DbHw{6j#Xx#p+=yNm za)Z#L@eOFDn@_SAk4Gyt3gFqv$e_^Vj zjlO%JJ*kjJALxD^&;o1dU@PeUB+qU>&|oV_1~S+xqT-PZ+NS{i11Oh(qrP`D$ZIfnfqen0qWJ~f6g<1xd_cz- zgYF>oGk^{_fcFX(fO1O!D4~M#4miIUfa-t9Sb>0_2q?n@fQAzcI&)MUj=QM1fN}|V zA`?7<3TgaV=BT)!^pr}>eY@FQy3ZP40wqTVkRv?0Pk@Fv`33yI!w4Wd`2`(7w{!T3 zeBu}MfQ(9jQY>hg+oSOaC>c1$fzoJf^x-aeMGab~1*u{K7-9YUbVde-d~izJ$H>6I z4o+$J7-3Z`2NS%1kCf6v!GruDkD_)xvOrxA59C39Ds(-t5B9rupFtYz2Q~lBg45j= z6-bPh3xK;Jphk*M_h-lMH;BpwJj4%L(+wTs2X(&Sm1d6$=#CxGfL(Ws3P=Xj)9_3V zWdN1;urYpU;~$dd5F`Aq46q@7N6;8Qs8I_pv0Ff82&k+9HE5w@`#!zp0x%~v*fH?8 zfEI#62e%UV1zZ&zyZL;2T?P0Be8D4I(6Sjk2oFwcSj%QtM5ELJ)F=(;j8O>zZHz%J zms8NnW&Re>vRU75c2I+q5m7F?D!|KSUvSC;b-oehGHhfW-01A~MQ(F;yP`BXRnjJS zbc1Fph-qqqN?=eVdNdvZB{9c%$5?1e2E``&fI4V17^JZ{6P}Wffi{SNQ!=Q$g_N0~ zI~$;l#coEA&L+@ZI)^>Fog_S350nV<3ot@+lqYCNz{BA%bRLYz8B54Hc%z7LkLOK1Uc7`L`{$b4I!&WAV*+; zc0GUQ7l@RAEI|qcorM8fpaR|;4%#x&>7o+Gzs*qrv>FM#^BJ@e*+s?RGrxeNj7PVR zN+;W)I{}8r(mivqp$sZUi}x928Yh8xecc9YN=eK=$v$V}+Rg=%6|n)T@L$h}f(EIsF{! z4&+$DvmqaHYBy95u@-@vesI^JScE%>AxD*aG{1=eg%HR>$k_%7pf(67q#UuHYLEij zDUk+BzzLvA0+OmBjR}wDBN2x^7+@#2gN)pcdJqAgMUIeDH%Ze2IgyeyJ&?0CNz(&4 z)f1|R*f@b46iT8d$eEbbTf780fs;f>K#mY4Q4?sy8*)SpNTYxw%J~eSjp>jjYCbA1 zpakRwN3Gg3lxY9Zdi^f)r%LcJK-};+H}~j#LG;ojE{96exf*7x(Mrt%1@A!dJ&q4cOz)dj^uEJ96#&PeTaWsAWOFgq%hzH z&B{W3j3EKJjt+|s$jxV1Bp{cld2}BFHJp&`fZX&3D}q=UV0Wv55&^tq;$(nb;Rb5A z@_;%5s6`j($PLI1aG?4JbiNdePxqm2M;_3i1!&aFMFq6tTEns116rnn4w6!4fZb+? zVH@OHJPcvTO>r2)kSq2)KuZEL6rgjJ&2JJsn=1?$T)Q9oc7O2be&y5slfMNtlJ3!2 zA>om{>jEN z8$oVx?LPRKUoiH%N3ZYo&-?TfTxu zP`W!Ix{rV67XVd&3=jhZW3MA=yzmmVR0K3N0vbze{=vlGdKz>`v?IU329R694*S60 zwg)u!*X^j`+j@Y%rH>KpNiC2k!$LeD&g9oP1oBuM$gfBFHBNov7Yu#Qf9zzaN3ZYm zPyB+p&-pd3f!q!1Ub$Z9*EkCG{0Ri}#%F%eDXQQ*s$En-9`wBpGSn~TGk=5^Xi-?~ z>(BgxzV|^k`+0okk2nkBJ_oA-g{T*YM{g*|f4=V>|6h3d6qMC_KpVHH;S;|AQ@0Zb==dM-Kv8dn2WXNA6nrkA)(~jS#T69xwYNXJ z@C&$JcL6oB6F_Wmz;?Rc|I9B?dLK3!_laMC9pvlJ`~v`32lLKJ!N$1Nq>!BfrK;knsEe!4m(Ec{CsAakSnBN?tXaT=+G7R1#eH zH9!ZUXzT&cOM#C10XY?vutDQdps{aI1cDMcbi@TTMh;5Tv4=rtY~+B~DI8{CU}yxh z@<0QFV3rIM14A8{1u9@%x=+CdhCO;Gf(BE4dK*DA6yWw4xIqXywdyE1Uwj5F91-Yt zQ4s+5m^mz6R5+MqZ-5d;{yQ7$IdnD(mBsf~%aOC&7;gNjWgMa;5s1L<_x_@}IUMf*`ZT(iq z@7sOMqx*yp|9Z&&YL8@bk8VB>{`FU(s&iBvd^+uXx@%NCI^{tdxIdJtxf=iX1x=J*_vzdXK1Qtj zv`go1(1|=?*KA;X83dZCwf@WB&ddawp+D=|eau7qyvL5SXCT1i`$La|ub6zS5AnA@ z0IiGZT%!UCVeej@GLM5#nL%l}vqUAqr!z$*0F*Kez$SyHXF!5p$Kla@Ov2In9e+~>Xgy&!WL6J!n2Q1^5IZ+RLh!mv z=Vnj{!dd|v7=5}ye(x?(NdQ&WpwI#3_XwYEQ0#S=s5rply!$G0S^%5e`nIIPqc@JF z`!BydsQ6{@fR-qTA_i89JoV@f0UdP%xvSmi!0UGG=4p^=-b)jZw;}US;BYfK@Y4AI z|NpMW|2HuDbnXOs-=%XW*zeFpY|6mEpbRdUt>9;RfTm~Fz+#}olt4Lwg#lC#fbQXC zVNha(9lK`E$iT1|oCW8=#nv;zj$%6i7vp7OV1Vd#WMW{@2b=811UrB&mkCyAw!m2v znHU&!!0M*K)q(D>gA}BoIVlJWbbFyDSRH8j8^opH#T~(*q7OXn1Uhiuv-<|PiRaP% z$#F*q3j+gmvdkm7M#V?_o=5Uw$L)+h$)7=sIV@Ni7(9CYL0c&J`@z@Acdk*n!NS1M z>(1C=4} zbXE=Mj$?3^^yz*87T>7=n}7jri0*7r0iA1Z{MG|>9uWhB$8iTx=?HE)363 zCka>3x_nT*?6~{?e+CA4Gub2gx<{`Qho|+iI(Ap1|Hijp7kDHe=ie4%!0vJIF$b9K zW5DLYebXcPI7ozln~wo2DwhS7%Z$opLgg~@Z(}h634u)KzR`S~1IY>8u15b|jSqki zi*jxGU&`ugeBgC`uRA+9F4$Z^;q7_w0W9c!xDWKYv!V;IpbIdg3oxMzFro^8oCp%} z;XcrOoWm#iyl1Z?lc)7D{{Bm#eI_mc`CCqc&S`UJ1SJfRqxiRp820)zr5$|4k;Z+Y z(?`X}_&}PY_2o2v*Fzr37x}mU@aSa`2O9y}_~FsrqXLRsm+t4?M}4wCM0hfv^XQcH z>2&01{_jx6z8|zw>osdy>;DpQ59Vtg%?Aa1x*_L&`E*YKM>@<1&t4Y0?iv*zm+rGZ zprZ7WBlDkQE*t_3pgaJXZ~)DjffD-vqcF{oMBr%smcQxu-~azX3+qA70o941gZzCu z7kIFMmS!$d0j0>!6#*=uZBUS;>e9I)1SHuxAp%5oL-Gsg3?~)_22krTJ}&w&XgeyT zumG)IhO_}dCtg6rKx_3OVxZ%nKtpgW;48l&4IBk#28K>>LBqe@QNR;4FAZw){7?IT z0<_3u`vFho6CMX2D0pxm>^|keeef`Kl{o}^G`;~%fg>+Ohpe*#wd6p3MMzczRZsi^ z46r3%`~n^VpecTE+gZRt0VED;H}ea4fT#XJv+w)@4h|r3&>9kc0gnKXdeA`=`~nUM zAaT&*5q<%W0+2W;X!r#hK$VO~cQd#p-q`|PGV9R|J~0whKuCb9`0i$KL#DGu1*E6j zS;zVy$S}|vAAX;M9=(wBJUd&!8`QvV2PGcR!ZFzTWRJ!pARk8`=7Fz2R$*XZ*amKT zf*j5aZk&Uh4O?p*1345IHu~BLiYBnx-QgTQz2K8sJi7UPx*@BuS`L&7b~J-}e4rL0 zsDf39Kiusu(%B4J=5-7#!^pqQU8bXXFX$XOcY#i4nQnK9PG_IAKJ<8JQ&&w~$OW`T~{1rHd5n&b@N z&;Xq`zyxN2P9%k-XHY0WDl1TGhotAu5EX%7&^U)j^P2*nf6J#lQWc<4(|+P?`(>(PPX|#h~^vGpy1uKJfAyGpO{>KL*-)zyaD)XTwn< z!@unURJkKKK*14deBk9uB*mN{#VrRgTmjmboW@^#lLNHA{{$#eg41g2fl_q$Ml&-o zfGRw2YVFN10BQIEiq#B{)&rmpN`PngH;@6KeV88nt{;56|9Le3HQ;aJ1(iS6@A%tU zSQr>Qt-tZN{|1{?qmtm+eGb&@jsQCu+*i2_>aKLZ01NB{-ID-j8DDbT0lFjtbZlX- zhXh!R|Jb+xpZNvLMSME-T{_D-I`ert%OQO*Gh1!%up2=F9NwfAr|gS8?e~ z*GcQl_c-qE^Z)<<|Hs=w27+o@kK^s2izT2;&|Mh}3=9fo9Q+!PQ;c1@{druCp15?E z3v_?=>1KEA4P*j8`JyxbHWwx)XZ~$IOw8{5+hmvyKIGs&0TyKhGkayY z9Ify1_k&7P$A;ex{H@bLW#Tc{mM8qJpyf!-KY93@mVt(ldL6kOw}2`puy*c)4>+9o zw}~)vUvTVoP%;2jO`!DT*?rZgyGI2iI z*x3%sg=xpzLB$OtgGc9nP?4PG!GEm=dOUdd>6QbfT;L?|5@a}-1seANaeRAA4Z5%S zK&%I)E_e~f-?|nw>WI|hhAwUEo(^fl^@D0lsA2t}rYLBrLBJ!q9dsX&NA?9sBK7F* z2i-dak`sd|1_|tfx9vQ;uX}bwdgHr52^h8($oK%Lj}igutt5at+yS7nD+APt0c9zE z4N%dcQKMqu(R`!;w8AU;FlZ$#ER42;+KQdqK`jBWwZW*hqE2zFK~CP+r^lr^#H#=r0?dD>|@O0)BU0KWSxLVcZe~EPxnC&XuqyI z#F)jS+sBv#Y)Q9|F^g~axo#0-pY8*lJ}L^H(6#y2M@u9fcd&DTn&HT2fP;1YKMER> zv4*&+rv%j9=|2AQ2PnZHMS}5xm+vuz5J3Pse%}tyir63%jivq1f}MzFfsEDQ|NVDqlAz4}+q&Z+&(9J)-U@_2*K9G z76aXr0}%s{^ZjOIV1N#v-2~V4-H7Hz%YhQ^?ru;H2Pcv4$)F_b(cKM7qF@n^?ru=` z6_mY1Jd&G1O)!sMkp@Kd-`x#r@?cQDy2akh~m|*%r1I93U32+Y)EDzaN?$OlSsN^X$l4y=-UT3O0gq(o8js8iprI?6wcw^+>&X(3v%y1P5NAQG zHGXTk8q{2<3kTJh7NGGahwi@B*q64(}4RQ zl&OvzSc46#IRGk496-g0g-3Tc#H1V*1CTb5D|`^1hYVeMba#Ua3y?g-SsvZq0U#b) z6RX=h0VbLOx7i~ZbX@{B*ma-z1)L$3Ftmh&Y~_UvdxA#qSiy-8)W%`~v+NjP$r^Oh zGo+1G2OmqE2WNo>-yyP~Ne4)x2dx!`h=F<@5EkfMatO7!FLt`9n0R#ma_RI@G4Sbr2^!!i0MFm_^D;2_c2=KoHU95nSy{o~SH{c0 z;Mo~1;n52&IXydzA9!@1@a;^#;kfheg1Zd9oy8YijlVhaYgVstHGcA$UjTF&41+7b z=4Olk|Nn#L8bAX=Ac5T)Pyw(CM}E!X9j^SE&NE#3HN87r`86k3{Qv*om0xoi@$}`~uCOLl}Iz<2gW^H$f|wn?d`VK^zC)*4rh;pZNt`4Lo{78$R<# zxH^317w|Rl==BZw%r7t*bZCRC^{>zT5xy>;`6DKSPGRV728lZEVEF%^0W|T^;L7iE z5MfO}D1U(r4**S%w18%;Iyzk=9RFW-0S(ka!ptXmF=)20yBjpu=g54<#rh$CFX%Kz z|6Y?6Pv|5gY*NOzdo9RJkM3{@?_Lu<(3rX2C-9vOy}k+EZUU~>zaXvw*_r@yO{Z&y zPdC^hj{F+u{$KLx4(IR%P0n>fCi&t(lT8yoIr9sI=!tmr`WAG%3Gizia=t9h9h4fJRg;T&;icx0-T+QoA$gJb3;V4N$e-T<^fZ-y+KeN)S^)rK9mT zaB^nk0M+?EDv%>SJi0wN_yq(!tUUzyTb{8qFn~G-7rH$-JhBf)crv$wi`?!44xe5R z0q_|9A$A4^#|BFV{+1o=AcJ~AecxT6aT5>Ffb}eP(9tj8Lr6g7C8TZnas^1VJD$V0 z^)`PCXj}9reu2rLMCj2Qy5JMPz-&pc|B4Kl4X)gHkL= za0M(GLag=ZoeFA;!V<5m^{>uW(D=IJ&I#`(Fo2``6Te_9NR11>%L&kMA+$l|le`)f zXRri(*2VfIe{U@t1A||$NiHb!`7(cnrDM=QdjTl*`gAV?Ezo4w3etg;s6p+>Py7PlM7;r=sMSO~dV4{QbV#~} zngrTve(YqZ#sknc{16oj z7sycuCqtncL8>2u{nti{;uWU#5g$S6 z6P#f9BW@tlCnQ3BTMv|E!2Ijc8~OnhW&DDnEHEE=^!hS5?(Ere2~_=-et<+CzhLYa zaNO|=`u@NYb^L;H7SFv_5rPm=?3Ki z&u(zx3Ta6QxUhMA<`>9e3*gsy01Fm0Z$d-oGsKhN)Cy{F6n^3t2w}7E=nVx`x}aq+ z3ZUZ0qt{mfT-q}%{m;O!aSF6_Mg?32@Vgv@7Ga(4n!sY-n2~+@yeCCfh3X+p>{C^o%(;kD=w4lXBNMYb&{eizn9Mr<Wp?Ir|4_ zq{Q6-v?;}yU%=S_G{hg^14+4`_yxQjT&;iYGCFI-09s)Z;K=U+%JM!c3DAHA`C;dO zhX0WI0~9CVYgR79#u~sjzcl>;PH*70nMdzb(C9CuP6BHOnGULXK*IY#t&UFcp(QT- zF6WRcAxNzOwhJWldOFx>Na1z$`~Uwwkcp|+Vm{sHplnFh-3zY9!99d-W>BI9MQ=B_ zmTWyx8p zj|3NQmp}7I+yj+!XFy>uayX5@?j$r@c=S#MwaYqtLAzxgcl`hVpTVQI7c`FmN<<*~ zf4BoUadd;aJ&=wJqyX^zf6OEKglFRsP!}28#c=$89Nf!rwXUt;@0sxV|9=7HJS!yEoNX~s0On@15uDwRw9fH3|GMyaH}&jFm!>3XquQA7{b8nK<9~` z0S~i)mQ~FM%j&W*FhE$6>1H`r(9tMU4u-J4S28JbIz4Lh(7>dARmw6Z%O2I5+UYIZ6 z@WQUG`oqh>a0~1s20jLc+h7(89|HquxQUs8gO7pXI#`ScE+)XozyO+>XJ!!LV_>)m zmIVzmuyi{{95x0o5i1dGy;LD+9T-u|IQlC<8NwrQ1;f%)iw$13-7Wd2~8L`s&v~^Te$O_&Y#bNnNa+D)@Ur$NYIFAMnUL0ZPyw%w7fj0*(UR zjQj$g3O=A)L7BT7LB)wrr*i_opfd-*0LW}_3D8N%4*Y`7p!+r(L1oe>enDr4Py7On zpcZSVGia?OGidRoptDb>b4Yh1sPES447w!A+X7T&`+0Ov0`zAtA@#UQ zv=P0nixD2o(;>YrCk~J1LjoS1&KbrBESNm_T`qWZ1{ZjA9|q0g6@V7F#vcYXXduZE z8l)AVa07>yPxnP=P<1+jR-l#?xwamt$OVN)ZMtjg$%-U!a2$XHhf{%zbqC1lwcgzg zpqk&2`Jl%P*l`FR%w6CnUpFIYxiW`mCsVhhz$Zt3K_?E7fQVzKlR&ql3|Iujk?D3+ zaO`wa@a#Tx*!Tdb6of>YN2jAgr=tcaDD=CX6!-<43_2ORope6&3wSwnI`4|`&Kr@Pv6v@lKtr0Z+*?Iu9yhaIyu{XL?QZcw=+*CcwW}{K&P`z zTBox|r*lOo`1Tyou%ktHbw=88$doO^acA&UShsVG^$*arY>A2i>a^@}e%~J+$>-6g zWj}yCg*Il`Sqz@fFzBp~@kn;g0F6w7=4D$C)Jb}D2PYuS$%01-!Mkw4tizyrH&C(7 z!T=gCgbb2{wgW?kd}0_0DMl)qi$yz>lggJ*Fe)Mt(W*)L92&R#<20njZ4R7&{Wn-P?r}TH{H&NNO0*a zmVrh@noDP~hf8O1g-hpV(6B^zb&U1#I=j~YrR@8mQ|14yfAISdA1Bc0=yZ-~Jy2(W z78BsM#A_*!-f9_;-z7i{ODBOke%NX|kO03tgQY%y?;OwsOGmM%i$$>qe=BGOaW^=X zIT(FIaI-u!#2j((zYKN7#K=LTr7)yYWZ9&HiNQN32Qfdr?U^lR%>U0nwiI(Js2HY zzVWxlfajLLi+(}td%JT$W3MGD9+33a&2HVC!OFnE-v?T9H}m^$PzDD@q$*e&Wa#>( z#Q*>QLBn{Eln6T60+JFz*IhtTBIt%PNJ<2?H6c?esf-K^pve{%2GGXzT5uie&&0qm z3(NwiQ&0j%OovPa4f^>Ji z@(GlA9bApSp`4+!8H!e*I5@e0SdgFqEq{hYJm|tD z2n*y2XIQj0|6;6V2d_kb&9wtG4{Uvb-~U2qpbl!(@^uI6AQc=C!x7os0b_J054@$h(2t502D|DosJa_M*lk-!G%O4C}~(b3e@I6=X@J>gGwBR zk{D=Kb7=Wr!sC&A!K0h8!G`H$X*f8ZTMtxNgYupkB=2$jcb0M3&iGmelKDiNt9=-1 z`5ShF@*YD8Teq_eO75HOz`yM{Cpt&Qc z&-q)xbN%pw2egC&R2FeI{QAh>3R=V5Q0>W3%J0zdpRts);a3BH%YIPtj#@J1fI=s| z`G-KM3P?FJ&UK3QFh4oIRo83@Xwf;k=3gHp9CSlrzCi z`)v#i43HUK8%9{61s?ZlImz#Oq5IHb(B2Gi6B=}sD`<6-0ca7QW{rx1NB1qT*RdbN z{Fz_S9&&VZISXhh4rCnxPPO$s9^g$d<)BFncM(tvH2}0A!hl}`WEK2!Psr&@nhXpK z5Ef{MFNEa<7Yl{6KpQ(Cvf%A%5dt2a1rne$>_H=RprdO&x_^RBuLZZN!AID_Jmr(@ z&BHGMK2#07Fi?;kbXqOHpuYe=Xg#2yy8?JEprF42cpadiJE%qPkN`Tqh+ohjG<)No z06K+@1$56V=+N6z7Gv-kY$TjD-RaB&DwoCahjKj&G?+nqq1<6*;%OIt4am|x96|2F zFX%1+(OC~VD!UwXQntGc0lO3+I?7c*QK~^eu?|$R0Z6e4OfiT9yZ04zvbVbpvJ7Nn zA4H?O3rxnN)7=Bgz6@Fq2^w&P@RNgie3HF6x;gl_JFs;6N2GPS$9VKQF?w1btOJL= z3;#A}5!aRjr7T7VUh{xYkz!%`f2cE2F*=^_BOCE_#_AN_;jE2NDk!i z>~&)Fus&4Ba?DYLp`lWQp_IeuB%`b{(y`N#2h>Rfow4PU>;%#% z<7s`Mj^&t>2tz}a2t%nbtl#U==_t_YC;{%jb~AyN;|YM5;t2>ib-M^i@C!120$p9- z=_ujY>m<|Z!~<3d8uvZO6ISFzcD07y;3<8-^fy}8v;(%wA!2Hkr zg6*Jfd}P_y4r)$;je!(F?Vxo$U_QuLkhdVldUJpZ84p+?L&y{DQ(!B2p~iw5|6pTb z*$-qqsDtI%9W4OPW5p8SdOlvLQj`lN^c2!pbGR zQUPt*c*-3*L^ZfmEEpBLTWLAAU-2_j!*_$a;3j8KwT9?WY``y$&ppgy=59&`>YJP%3)d z89br_9q9u%&EpSybn|;MpMV?(83=JS_&7+=br+!HAVIWauLDb`JC8^A;m%+IPy|5r ziogR|9?pU~v+)f$SHO??r@U~1CWSfRD{nlyk9#y80a+1$SdxK(VGlUcL8os)@&anv zixzRrpi$*+K~LsO{M)=3UHG>-G4pS8Vgen)>&SiJ-~$fsgZ$gLz_eqp7mH)3C(mK3 z8;FQ;&=r^`JczGXKp`vu+V~2}dmi0Cp#cNA&B&+w0O)+4FwcW86u_&5T~q>mx(|3B zd?AqL(&?fS;?eB_+9B!;Yj~dkD+e8m=+S+>(?=x$TFIq#`ly6}vpGx$EN5N^i-NYu z2s(p?u{}Tosoomh4icTtkZq?P-RB|FCZ9kB!Y6(~Zx^t%56rEf`30R}D}>I2I3dU~ zum#m%nHaDPNKFDPeS7vgFruby><93A90zTYU;s7DoKryQHo^nkV-SR=R#4i3)YhPL zuOO@{1_p-P;PeDqeh6V9S~I8M#RO3-Sw+Vp{6h82p-?JOUWnpyu zf6%qlgQo>FeB8n4nS2t|E(Ph}Gr9!QV|2i`H$ddIC{!OWNZe+qxFr#7|sqCWY3C6H>mRk+3yKq z!%n*fiNj922C*TFh9GJPb=L9)KuraZ&p^v$(nUbsWfy+IcnJ_UTn0q?D}YFMP~WT` zbfQMN4yeX705vJVZFxjP-j!d&`vCaX3RixO=moC)8p#t}`8Bc|z-9Dfz+2C-5|CzYz;J+11lG?9WVZwU$B}7 zqNx}(AmI#YpMcL}PX<@J5}v(|Xw@#~ac5AogyFTZM>jvHJwV(=N}cXJoxuvAR!#!A z!iH>v2Q_#CK${@q4}%Qd0WPMnMGR=uHzaV=;6YjlXVt)2li@`nJaB10q66yRLq~L= zafa>2E>P1@0+gQl12T`y9gVBgPLte=rS4zOAg-o z(Sx(>;4IK33lMdny#){!=$sq~3!0M}-+=r@-Tt*l0l%Ps0W9obG$@YYf*=m8b38`{ zG`R@LL+%wGjYmK(g0z@HCo!-vfJ$vh0D_jRK%9@r6rlKrh(W#7{DxYE1IU%|K}qZ> zu^!ap1@#L-m?13C6b^(1wgJ=-q|vwwBwaxFmGcXNhdn;=3v_^bNT2ux`$06s zOJohUU`<@0QrsO0|UbmaQJ}6dLca~(AiZGmP0VegC6|h2S8n)41PgIP@*pYRh@#40-zb(3Xkqv zpfy&Yjo}Xbf{u`RqY@PZ&}b5*i3aV%rg?xa)_b7<>KcQ3qbZ;kaVO|1+HMd*=CBfu zvPJ|_HK9#+cyzi$os{g&;n7>n18RqX8)pWf1|KN-Yj|282X*0?8C?0d z2^(K>ZMg)xeeKX|bB}IrNVCrk`4S+o5bP2l(AW_C5}@XTEH0hiJfO+x3Xkp_l?<0o zcOBp4gP;)|PwV5L9l{QEJdPd0u11$YZhCF%+H$GHz@r;Hhr|KCuapBak^{PFhyybG z1e$f=fDAu@E#LzWxptSR6m*6gcqU(jjlN14ope0L%m5yo?r^t(#Jfj)^kI;PM4+SA zC=%ew6CNf|j|@CQ3+iqoYXl9zK~e`|uno~iK-4j_;W-Ml;T@86K{JvN7HAd$VhZSF zK8P4NVS^IbfS0N?8P5hKt6@~C?gy6|h*A~N(59qR1(i6BZ$QmDP~8o^$O}}$MPMJ9 z1|8voXSxwTm1)bL-=$`NixxwWDEp$NBz#hro9H7h(8HjQgVE~QxgHC$_t%>&O z+ylOpgueyU^n_+j@NI!5oXiKGad3YCWlc^-7?Xt)!UA0Y=FH=h{EWW`v~sxh8-E{Y zMU_u)HRvL31r2bU__|N8vj+GcM#wBm%Yl+C(5Y9TBWHZOzqEdx8@ zR0IiTh$lhEK)R?{fPCT!p8InaVc_2uECQ++T#ZkHD+W+1-~inY=flD5c<>>I3-<}I z5F>)a!U1DLeZeo_0y=*Va%G)o@(IwEV$d=J9~ID~TmWcr5R_;WK$dbDT>?+^9(WBO z<@4xfcKm+;GCJtY({jK!`2@Jc&hYH@Wc0K?Rfo_c4jmcfhv)%SA&j7KL}uprM}EU>0bo2U1)9hu;JRnoEBHmhEI@V1Tez!&%KtuzR2( z4s!(E8P)tI0yLQoYIb&JaDW?z6#^c;SsWh8t_+|n%RGC7SbVyFf?HLBj2_k(>e!F5 zGkEl7bGRD6b+ORrZ(#?YhQ$pkTS5I8*Y2mFJi-NT=x{-@Jy-ym+4;Aza6uY6pnGhc zd0hB?A2~Mvaj0W+?M-!f%?Y|Bst?k}#bPfDUe!;TJ~-G4#NRRhEdHf#e}p51I7z4f43%^LPEplj#Ze}GQEW&Z;% zCJ%$!kOn>wdqLxT4q)}gJf5BGVBdG9bAWCv1GNJKKxHJz@1Q{QN#+Bc4Fejd@$B^k zopD&l?9!p{VxbQ@>lf5zDiw0=%;)j!J`TFk19X>C9O%dxaPtsc2NArnwv)}X`?zCg zIH;o$0W|#xKwT8ZhA(><3XE-3{Q;6v%Nz z5C_ntHx26N5V1_F<}<$_c(m>_zaYv`9i+!hYDXO2V+Qq%A!!Y?E`!vLH>ghy2^&a% z9=@ajWGral58^sfjRi#jvaz7iK^Px=f1 zbqMPwTvh>o1*8F-WesP64q1ih0CjgHz-K}GBsUs>&Q9yT>60AE;gjsifSL}kBSDov|27{+MsWR~cJLub8utmuUPmUE?h~EPJUc)KkMeJSnPz>W zbRB3j6?oGH^kBWX!>%m{O2k_Kmk4=u3w!kX>3A|qc;zzn`;0X z;Bz?UVy?pgU*{Tk7&Hw8Sy2!2J$S`w0JsPS4Jm*Q(}T?7xsbi?l$ZsP&_$^ApgEs% z#Asv!xYPz0UVj%A# zO&vRidn6a~`1DrtfNCuPpX5Rgk6t$x&;hUDMP;tWms~6YMM_y6yMKXZni%=F)vz)1 zZwp}q9a8PW23qF8!`6I^#pB>}4iD~MorOFgmM8ZQ2=&t^`5dHSd<<0K2-LB;bOegL zwgwqmV(QV&=*qt>gb~yz31I}aS6vuEB0P*BHynJ#;lq8&v6J2N;3E!S?h6p=A}Bs3 zKy$JRo(CT(_;6nbMXQEq_aVo4wB-~`s0(W#>oXV~d%eIddjUpJOI`qS2>2THRcOrVwm3lpeKaM7{X3*2JBW)9Y6A|T6s1RxFp>%8d1zs(2gCYX^dAO6X)#VKBl za3&X&<=E@R0SOz>l0#1(&*VT3P*(ut09HrPx3c}aXY9yh)yh^1(V2{z){FKB`4C7~2eKC`lEJU>+Nb-LZ*riBt97724L_)&2|sYmK@_yu!M9pz+f*{<;SsHfWUx=t3aS z5i?#QY5a8uz!%eGeBu}6Q2`(LAz8JwOBa=hQqFEC6H6DBgc5eoZbnd$ zfHZ;@Bx)S?N%j(PvGx+E;qvHa0!bMiIPBlc;^}$t0V`-)#fRC6qti*m(VC-_8yrrK z)*fsn>`=vy{M#I24*PT;@<O#5TESK~{r)*l?Zf4TDe z9P>=R;nDn?g}=uRbTmhA^dHZIubDi0!vsA4gRU0=Ef`|}@5XlQb`$~a=>Ooze8^)4 z=+GU|?WMjuV5bRqGJArL4X^-3Ea+^$6V|QkE(}Dj;L;bi2NRYY_q&!qM#rI)@N! zEZn;wNArMO@5p=zY!qnc^MBBpGQK-}pz-X>?8pI1nhq$g=WhiK^MI^E@j)KQDiOzS z2ZR@*K|&Ia-3|ypc!13VdBRHuhc8q?Mx*#b7-Wu$W48l#Z)l)1=AnQAR{S=4oVX|KA5k0Xx|6<6_f^tQpVu~6-$o)4|y~nlJNk0 z&_iIQ$N%3`wy@%s3GZM&0WvCfe~ab_^^QZ!_5bN zfQL>%je83h>l2Q>g&d$V&%u%3|0C#HzvOeC{Oe&$cg4Y16kIBi^8sB*cEXo`J>7gZHsW^-zEjw2LakC0cQ1@Fj;@)?*~n5f)A_*O;bAlKkN!RZUN+w z<{v!#O`wo<>~-LB+y*+a9%K~gzK&lx)$?y-f$*Wn)x-Jxj-7#!Y?(Tff}!sAV<~zfE-oNFTi5q2wH0LiC@6S0(`!Vr$cWTi%a)0 z>#O{I?BLTLVxa5CT{|NcK#`#UN?00>afcBcm(EB9$GF1|Vd0J;9?fqIJi32&BTle3 z{sx)=4CpM609{m&yug5;fx)-+34b4`Jp)QQ3El5NfeyY-3|w7mfT~M{2_D^z8vG0l zmL(cF{H>sa%e$e8Be_1pqx(j;dyH#uIirj9hq_3VGgut0kGb&se(*%O93woyr~3uy z%nnu8*8iYe-j0J;(ZDXeNDhzj==KkQpWW=!nIGWNSsvq&>>uIL?H=IKDRbh#dj#lq z=K#>~&|f zK3W&n`nHsPFKCYKH52n&>lge!FFcaZBAstTM#y&N;|N&K=6BG5js6chYqv%P5wHeG z0o(1L06v2kR4inGPL>e$NDfCjdHc9KXe&E|N3wfFr+Y^0fjUi4NWrd3@ks{7GSVR)()`WjDe0gfy5Z-qz*_C13Kdd5@S~Iqp>65 zC*gxuyFt`B1ViTh41BtGf=(1SzU|Td(sCx~Fn9hwPyzrIp8?&UK~2U8Q0yguZ+Au9 zTh+Z6bmBYsQUVX=f8FjV*AnD`s(mCt7V0ZSEgX5$GbdOw(N(3nEK}+rp9OGl74}%gUByd1OwGdB( z&MSu;n-5x#3^_I*bd&_-*!*=23=EKC^Ebl}RdDHc;@AONw(nwnz^D793%~yXm(E7e zC7#_*Je`f8>r{P{FM!TM(J(&AFVEo7&E(PR$phLK$oK_x8kP&EO}7il4SbV-c7Jl|hFq=a*?j{v#R)!}tNW!#^T7hd1&hh96~>o* zGCzPM9e04v?DAnQE%5C1gRFUV>@@S}o!SGQL}}#!pCAuDP}#H7jl=kVH~6$g1QJ$sRcfi3X~rVz?WMc1|4RT4L+O)G%gS6{emWc zAYvZffgHvMKnGk;J>bE7=rH6&63```;5&Z~lvsImdkT1TU+_5i2-GGM^tkxSgZY9- zuSh5tY>zMKgcfFxPG`{eOaTwbQJEf{&Jv)CJ^HW#{HS|S?;Et5n1unPgVCeYkpr~X z+XJ@Q8?uEPwBM<-L`5RpF~kva;V|d|Rq(x}AAPzX`E;N1Wq#tZ(>%uL%j`&XMaRAW z!G~nWarpF>N%(cMJn-y3;BoMUxTo;}pKdn^5B~KB!OnlB?_qqwQ~9z__a#UE?K}aV z%4fmD$RPQPFFcqJc`6_Fv_4p(3tM0WQsv;&eF2BU()%C_o%y%31bFxAaHcsLvuJqq zMlq&2Dj(esI`b^e*^nim`@B!L8;4J~pMOVa>4x0N=+PYpI>N$(`J_)T3&ReE4-5>T?eUNJ1zApc zbcadsZ|BiUQ$FFuznz5%^H0SqN3j=Mk+GQp95J7e1a18I((jA@S6he}lVx1aD~zUkS^BkZB<&EeU} z;nD3Zk=AVE!BBb`;t~$r~A}yka3Ru+eL!XlrN__T3;x)^y!Y| z02i;Yr~;i@0NQK_iAzuBgPzKVL0X)kTFUN$at73VPtd`5pbKb0^{N8sDn^g)A`Vch z>8_FhoojILg*YO8d4gvC?txN~XZH)A?oU45H#`r%*7q^Kv4eqwfx%Pxc3LY-e5tbW z0nbiG&rU}U&rU}H&rU}PPf#%qo=?b8u>fB$3#$JWJh~4%?g7P|=fT(D`dlN;Rr!wN zevmKI;ME2Hb`f`a2lHVMDG;fAZkhI{8}Eqx)d0O-)~sRuooPe9Zj#8i9W6Mw{kPyCU`()jgH@V9?sWMD{Zy;LIX(S6ke znoC;_@VDFpuk3LXaOB^9-lzMh=fMXozMUov9-vDWI&&B}cKqjOU`XRW;nD3Tk=DtR z0m{7||Jgjc{RBLjPx$n*WP$F{Q1IyX}URb9~A{qC|h{+@_4?`}D@B1bB8wTX=RK@$Ala@a!)3 z@a%q!D7HaMX)C~IU4ahrn*c7ndzly*AjP;nGXuk9uox)QA~magI(<}7a;}0WC;&fs zbO&*OngZyBqEGiPP@(AC{bC;|6Z>|%Nr39ggRk^GyMOu^U+`7_<=g$p^WYm$Mt$ba zzn!Nd&5?iCLvH>iPzT48*lhV0w6?1CQukT@sRuonPZ7+RpkrznAeqyLiGd-_we@6) zF!+KpNF55woyJU{`p`|lxBEOKedsWDvwL>?34ofN(7frw{lF8HIo&!{DnOp|{Lcnb z;n^L=;mLf`qm$dam!$&S=mDL+)XU=F(djJU(^)O?5_HE-LybxS10*i_TS4cS_;#Q7 z?-c>%QXVG$?FZA8kEJ~<3Xxxvp;2a-!qgUb4nX#VX7eR@3@efZa(@Vxla zm-)Pp@~PK*8){T47)tp4I(0Zej_GE1>U81c06Pyjf zrT1OuY|GPHm6QYz~jkYypqXYza_zHUhkb8MKuMd_8yb5v2ALXt)xR5kE69 zFhDY5FcSjZV+&t4ff@If~*Dv;yIj1Qza z9cQTqo!aNmVSF2O>LsY#aIhrb_#32A>(T8FYScwAlo)w*y9;>qhNu`Eb5YR%U+)`m z%tb|k(Wjfmz^B)P!-pA?BS8l{gYMBOk@o0z=P*9$@gLM_6#%&rl;ux&Sl=vK0UDlx z-TMVvhh?qL4>{ns`Ih4-HHI&W?s8(kN2T_iS!Ds$@7ZnH3-tzz8&;lKudhj6& zIIKWbA=Yqez4`C|f1?ASa<{;FWmQ38;K9Sys>yoy-nK|2?34xIo93g2sNqQ{~Zz*%=rZ zEwek@RDj*158HjeJY9-xE_8t(JxzV6Y@;n92&axr4-ft{dR zZ}~kAdNd!gh>wenJ`B3Z0Cbf-3j=5@;uP3Aw002KW1ihNJ-dH+bbs>behKkcXAH=? z^FH0jkgU6kY#lgpJ}3LapK!Q*QX+YREeTWx&`TZ197#NrtK$k5;SfG60%nA}?6yabH$zWsPPZ4C`HxXdqzrPOS3RY2Z6#7Eg~LwJ@>G}s%i+2qERgPn5Zywo3_?kK1-uD70o-6)85p8r+87uZR={;aSRkFt zAv%#l2Xr74Ow&rZ4hRdRV=Y7n7YhTI2^#~K5gRyEK*G zhShLg5Ee++PKYiAE(Qf?y5Y|N*~`PgH3?!b=&T8tA#30!Kv*CX_Crj-DPVS#jChv-I%Q)prUYuX6c0bzl3+{LS76I=&`1=8^tua3=d9S{~s$4iI~ zw3q-5DT1BFz`(Ett_#8f>3R>*CBwlWQ^Cq0l)=WpWg@`9bqNxVyhwR}D_l2(1=9T$ zqT61S!TtslgXIZk29q5u4Eifr8MJ1wF(`GgGe}i%FbHRGGVq&-G4L~rFmQlVGXsMc zlF8fPCPP>tlYc`@2Ibch76$$lW(KYhCI(i}8L12m3>`?ix5ITqSRmbuY@l+3(Tk12 zD+J^t4p2S<4b7wE*Bx-35Ee)$J6NY8D-#2238-R3*}R6odsD`GV*Gl>?w- zMPOkKkq4Dr5Ee)`FT`x95|9oM6IBgFHOK~txgaw^hev@_f-poLRgW;#TqXtvlH3e3 z7j!Wh$XpP{X0{~QY({YUU||r-U}fMk;bY+11W6yrrNd5mXhK*ZAIn2@Yw$8?bZ|1r zRd6wgXK*tJr^qk}n@BSVGfFViX2P~94VkpgbWNY9UxhVI#5_cSfJR1=m4n#t*V0>jn!;J zh}i-x3<6)I7&wl|gUyENLNyg)K4^>-q6?%7wC4|>`Q{MwL2iGdz<{g+m)jvaK<4Hn zT_Cefk#Y${9@XtDA?7nMpqPuQ2BHsS7sOnU znE?dMT@N=GIXrNg3keU9nGpoc-3m7s4@s4ODw z;fg~@+5?#j+H3)G1qegJ3e`Oap=N{R5$2+*f#?H;F~nSunV_TVA$k}XAo8esj)To* z6l7-*EMa93OkrUVG+|~C1RcA_z`%gq|2huO5fB#0PiMip!7acikir0_10)Mk2XYUD z1H*^zQp-}{QxVtHUu3e2-A25t_#8f=@JF&QUvLO zmiZvnFkPT1cU`jM-W{gRmi zoB8Sx^BEXW%mrzJs0R5H!UEX^F&AWJ21p4Wb9Le7B8LY^6T~!Bb0OgYGP4Gx1dqAK zP;>bh7%G?<7(iOSa4|4k;bvec;bCA%;bmY50mUK%I37R-fexR9*a_N(4q<_&x*_ol zvM&Uw)en&eIRe50xziGACl{)nSnLADEkqxv#DlOv_CxFf*^^GdE_BqsxWKswQaeNJISbD{5EjVS-e4WzdM$(vtPG|DBnwdo zasz|~O6d?CAXT9I|G-)p7#JY(ARju$?xJ zIuyZag`I&l1>z!@E|4D~YC(R4us~@Aq6?%7R9=G224RRi$VCtq$oxi#`5;8GTVWG`MngF4>CK1fccZb<|D@xYy=V% zJ{RCI1z~|gawb>@Bu!$Dg+kPU!Vtm&#TP^e$Xw7B6CiUz7$Oe}O$ZBQ_I!ldDQw_T zS(pxxEJPiu*$^EdRfj-^LNP=os@Y2+W-~y>Zb3Rgic!@-^nq-EmBGY}Ri ze71viD2j71h(F_FJqFPYQl*PjjzQ!>E`zW@ z_Uwh&191rGx^plKRSiTn$j1z&S~vTIlYyaxgMlH1oq>TQ_k-++ zAYji)u=^Rgm_XzI;9AlX>;njK2_CW#7RblvAv%!iRZuuV)H5(#hU#vpKk zuYeai!JNRvzyOMk1z=quBS1$1fM^iD0~Q6@4q<`J`Uy5mk%ftYH1k(YMg{TAh z2f_lG4bcHo1zJV{G8cp)@~CEm7NIgTD7MHjwA|2Ss5zm;;!UyCk5I({T3I~u~5Ee+6 zERrrxP{{(G!vbB+1=0b+pi=}Pt1KWakS=AgE=4gm2C*j`3|vU%HB2Wc#X!`9axsJj zD!Cy#L8{Jz42NQfJji7b7RU}wh#gQBARQnksv3xDP^>}B1(}IFz66m+)uRtK7ZSSM z@L6^zq|gN&!vgU!gavYqDMS}PXiW)d2AG3^KL|2Ig1jyPbb}Q{CxivkX${fI$;iM7 zE#W~oVu1Vt!YANy0%3u4ID&P6^CswKTm}XPm=2IEL>+K--}}=7KOp z9^`un3#1!#E-9$CVq#z*$!w6hpaa1{=7KQ9L{zhVA?^lABe@w>4MZQv^$<6M%q)iL z08tQmR6W69bHS~>Ajp^uba4(y1H&132trsOUq)i+a09D_5NF{!AS{rMc)U8!!F51b zARVcAb)1LmfUrP1vJpCENX${83K9>H}$SRftEVEe!=1`|-M zF)+Y%fMg-+K(P&Bfzm2O2S^p@FnO5q5P6UZ5Ee*xC&X+}UymfSLFR%sGUGG5A8Iy4 z4ahYh7OMRamw;RkaWlwF(98@-B?v?0QT0p(n+py>(D5}44B)vUkZKTq3=csF3*^h$ z2py1$m;pJhK7s3ius}K%;??mSt^>jX=~#}?0d*DV#wn1CK==h*2ZROEu@*xI)NvpU z3@_n2AS{rM%?KS(rQTq*5aJbF2ZROEu@kS3*Ki#W7D&f_gbt`bpjLq`c>~u0VS#iU zMd*MkMRwI&xDE&lq~kPR9q-^eAS{rMiwGT{t``FXLm1dS5aK;t2ZROEaUG!pn$JKj z8i;xZhM#a95Ee+sU5E~FzUF2S3E^T8G2w)-GX!mYh3Wna*9~ETbU!9q_dmF92n(e9 zB^KR?HIASQ-(YtChwFy0K)T-}>E>k+3E^Q7G2vnmDT1t!{0`F!qT#I?(4tOeP>+h? zD?~TA-sNE6H(_JoH-gl=p#29RO(4t&*9l>P?EMYaDXGNHAYh`%AiyZa!1;hZfeqSX z23;xxYMCHf%%CmxAhST232q*Q1u~D36V!SFufHh)jhiwsz;u9QA?iS71%w4E=^#2l zs=Sd}7Z7<+sR3bubhAUu23gI3Fc(z~L^a3;h`As$rx7rh7i=!LG+G2P9J#f^0rw?@ z1#*cnLI*U5A+HDEgzJE?KsqEbbRf4m1>ia$ERYU)untK@b_RYEIR<`42?mY=M9hGW z$wQ49StMTy!p(!QK<23;nWx0S&nSap9_ZczRP%I@%oBo}2VsHC(?&8+hJl|^48=Uq zk%6e@2_l&%3^xzL0-0xsWS$CieG9_BpyL5i%>y0U0EsOI1`)V<5EjThb0qVK@h@nj z1rolCFsL-Kv*Ch>3DU34y1}Za2n(e9BgAZwVGNKJk02(h8i;C;4G?odX0{M8_dD2JMiI~sFL?$LMllB7 z58MwpFR&ND`V^q^qd|6oa5>!95EjT~f59dwDseCpxEVOEfHrC(bb{g^U-F4$ar zc>;9PGsrFwu7dj-!UDO>7;FN#B!K1vm=2IEL>@u(-u#wk5*sBXSJL zI&hf{(E&0y45_?^*bVYIgay)V4>6m80mWQYH4xPx*F($&nF+dE2IO84hRCDpafO%* zTEm{f#el2>WGh4m$V3PW6dn*AAalPVr2>dNs@dKUvq9rij0_AQ9Uxbrs)6VO*#I#Y zWG3jKL6AxihRCDp351&qQUMBQ5RIw^q7P&)B>q5ViXo*gh&-yEaIm?I{n8BmJ2)8H zR&X-Z&fsDw>ELF_uHa!v&fsN;{vyi|{6vny3qn14xQ(0`K7AbljCL7*g$fx`rJ!8N4z z2bm6A@eUN2Af<45Q0PHag4~h`af>JmgXj-U2F@2+;1&x^ z7f3TiEhr5^SfCOYq6?%-3~81UB9Cf*KE!-ziv^?u!N#cXp1Ca|t2Oy&iy+~zrCp;D*ERfIlLQLx6X6X6C%20KLg@G@Goq^AU zje+k0PXRYL{WCCVBe|ywZVH42GUYJD6r?q#u+~+=s3GMi zNIe5XA6y571=4XILq{S+F_;7$BnHWs5Ee+sRfrB59tN2d9tN2ZZUz|>E(RGR)DW72 zXjLL>Od4gc)Q_gcxKmNq!K2APSn$V0eM#7SMrc5K|y5kSWh0 zrUHl$wJhD(jkNe z%C`_5AanPFLm5Iqbo{SP-6WFE+;AR3psknjMRIh}yHtlXeJ z93u}i1J5Cj50J4B1_tD_J*L8A2*Lt|A~#rMxKTxWWoz2gv+S z1nkg++5s{R5?&w{uCRib3o?@zDFs6OhN?#&Y%X+tA~<0qH3%7|!BZcE1xgvFU>)GK zAfOen(3O}V*MMXp>Ok=YVS#)O(E&0SHgXP94Uq@A2EqdAwuYF^$HKq|+LZySUl>rw z@gZtKrb1Yt^bOGkGP?p~Fcd@NQO$RRm=AR`NC!wUsv3wskPjf{g3JV6r~pz4!Vq~> zJ??OGK`KB#1<|N#Ao@V&Lc#-NCh}P)5P4KRzF>11c~}{EN>~|qLRc7hKof}!3=G;J z3!r#9JeD9VP)G)Yb%JL^QkWT7O(4k&rVHc>h+0rcLRg@Xgy;gPLSCB>kq5a9!UCBe z2{9k)0+0?66IBgFHOMZAxgaxBk?JmpJgS~}u({wCCUo@_Y}F3P{26ecLRcV|q(XE+ zTH_y>10ZY1KW+>)PdXq zVS(Zuq64G~bg(VNWCjL^JjfRi7D)G0gxMi%49Ge_vJiEsW3Ni;VM*0_O28fyik1q%d6l!b1<}iZtW{5C@;3n`0 zJhlm=2IEL>+oWbfL1VkR> zI|vJ;dnd$f4i*NE6hQ_S69KTG*h&-zK`yu8tFrb)= zss^GDWEaF-keQ$o1E0A^;pT!=fqV+0ahVGV50IIl3jjeXK^S5$s-Dwea~Z|h8N^Ja z7{nMw7`Q%gJb=xTAeUkD;V}hafkN{lSU0$rkpb!P!gPRSA?iT>gs?y{1b5Z#~>6v6_Ppb*_4J7yu3bP#z|dmcmVVPHTp7gY^JAIK*Vb3tY< zB4F-IxVgyTg3DY;xPZ(AoxKPO6A*^ji>l{6++2_fkl7#_RSiTR$i0xb1ev*?fP24! z&CSe9&M8evjY-O{Fiz3WO)SZXPb#FToJi_k(=$raGE;Lv?j+U{Y`Q=W%Pdw%&Mzt|Ehs^ER&suBL4Ii-DV7A6B$lLl zBo=3&25f#&N@|L&L4|5gPKAyF$lTHrxBw_PQDUYvFFP;4JWnCNAUQuJRl%S_)xao1 zN1-@7v!Ea|FCFCQq{@=iVs!TwrKV>VmlRbhB<18MXTw4~3FHObYD@C-6^e5cb8>K+ z21!8p;ty_dNl|HDa$-qpib7(ELTVn!EvltB4TdRD0Q&~ry~RbzF-57#`9&$2dFk;* z;FPPE%#fL)U~8+OQJR@oVr&#&qM6A6O46xCx^~&Am1#wZxv4>^$+~thC+R_4Z)>Yi zl$!1g$_h@YY57H|;1rk~kds)MS`_bGl$uzQYNY_TP5~`c&=n>nrYPhlre`LDBgY~F z;ze{-WvNBQnfZB$q@Y@=r&_8|R;&PKVAoUt%1)_83gww8B}l0VrVqQOq|9{P)V!3; z#5^QFRmmi3P}JiJX~)38U_Wc7q&)*egFOSo0{gRPK;R9C@4&!t zz=455!BLq#O;lZKzf?l!Is0W@S6uY#G>kPX{8u~e*MBk7$@tRYZ>yvPn&a<<%;o-O z@l7lGjA-YPiuYg69{h6D`{9>=_Ch}vdoFYiWKVtebi)lRR(EvT*Q$z*!Rt~%9!J%A zrpGQ7lLZ7OroS#)7anxBv&;3h(=U&h{@dxFd%1Gu{Y7oUZwc(}O|N7xnsal@#7-@q zgWoC+ET6*o?Cn>Wy#~tGDGp@={hDSts<^ zF!Zcg{m~|#2^L4XFA05+$a8jo=B_!VO8mmA%@N&6ruXt4mIyGn+OxkZtL(X=d(dHy z!s>$DnJZ17!DyJd6@OMPv7KtC3sVQ<-@s*esuA<-!-ywPpwRW(J*sh@de|b`TXe?i_}P>lrT@1bvsU_GJ@dHl^t*pA z`Q~vYzX(0}>oK44-s)?N{*ymUHJe@7e+%6mFg~g7nH%!-pUVB!vtash(JG-z%kTK- z{JJQwIxp(L#$#f#vJPdA@At#}i=M7w>UnEkT-!cFr52aDFmaeUFdA2U^( z1M{b3(nn_2`1`eP6IVt#T|Ax+(~paW*++=Rr5{}!<_<#QcfG5o?UYJS!$rpwZ=I2v ze>(KNW9@Rz;@#5kLvAt)7$}u_-8@xlt+A%f( zqwe8SkN6(8p1{SeYt*l$f4IVejjx`^_P`>y&9ngL~z> zuWfz)Bx&tK?Tj^tjKZHJOjP*1JI?XzW!}==^s|Y}&}VGibV?CWDElEQ{sF;9K>7rqrD?-C$VHQzat0 z!N${VL1d3N@AiO91%(+gGer4g&oa((eVJbpB*Q#cO)GfECrhWw==Hp*cPD#$9e%&c z$9%@NIZl6De#96ur!0G*JcU>0@49qR3$N{_B^@jNSDNvc2M2wM<`q{}3-{*pFcY76 z@Zp>inRJsIH#t8p`TwTmshS^m)^^{*w9ovGb;o|ZcG=g-y!F>h$75yY<&tj_cgBAG zm;Q{EVPQ~?_{>hft84YX?SE7^^GEDz(+vK_{>O@%{B9TBJSh#MVdgq4QEZQy+9a!O zxlT+~`bUAC$%caJy&XCAOb`0k{pB!!Z+q&Fd%lU#+D{jcos&3X*5e&m%A<7u!oOV? zzqK!DQqk<`eD>47;g-yS%o079V(aY^A2xM+F)cVMA~pTW@oiHN{#9siyC=i^mNiMC z$LK_t_)U&28{{%HVfNczmo$*CyLOjt%cBdej-r8yO7`D{*c7(wR_tKsc@*|@Qsmj+ zb1!$t+ZvWXda%Y$@m|z5)2~zK7cn<&PHtCyKX2*1uV*DV@9$puQGZ^`1)r7iH;y{> zw&ym$+{NGi$XfCL3@ygs88b9i?3YhmIZgAm@&rYgxrg+{4z}OgneN-Iytrj^S+C); z%1s+yURL&OXWg0a)j8Ap(9$PU8%3jEy1n|*U-)bKAKQGQYlFEHZViORd~9>A(4-=UKwHKc@Wn!}-B!!--6b z38zGNd<>4X`@SY#>(KWU^Old+J4%?|)SY22`~Ef1w`qyly{&U2AE>0>TK4V!-A0?j z3bwPkMRR6}`8;=6U$au_kL&`g;}+bGV)G5{S2d;i`#f^IloV#CI&Jd`M``^8i|X{n zOCA|S-Puv)a?v!m>gnU-$}Z2!ugV_#WZDp3L9$c^W$tesq;*pGHRsWYBmTpr2<@3urg=cqi zFFZVcJmvLX-~Qxc@mnSq=8rF)Ye;ywv-sq;O;@(0ESAyolFe{8eU=li+mZf3lb7;T9qGd^AZL#8 zD4Z&Nt$fu);oJklzN&V)Nig$%B{Uz>PSM_I=^CTrbNR}>b0<9nPL!RQ4dd5dU_3FY z(DC4k#Xq;`E)LM#peI(o=*ES7m^@59y8LQK%a-XrxtD~B3qt4J3f~UnpI12aGiSNR z6`1|9{q=ILJ0Ecq;=}B#&+vY*XZg~lYp-kEHoSUZmReZBwR!bPT>0zghjdik6J&jA zw>SRky&daVMPl+x6-3T$N!fZ!?~d?n=k-3*CpHDb{O?&cH(Vz9ij9Z;1a(EmI$zu8 zXS5bxoiX`qvGwGga_1$yZiim@F8n{8EB^ZaWRBOn+3nXk{L+|s7UuuMemfN&JgB?7 zywF)>UCy1*g>!za*K4;E>yOP=rtDb{qSm=zoX(@WD;9%@#yWD2Y?Qy21z zc(Xl!*6*7C`&K=>FDbZO`e$|L(`|0|g_uLLt?r#&?JOPsbVk%F3DcmOV(YJb0zqd^ zHmJ>(RlWa%Bhcc~^vt;MhhpZsD>zt9#0-n;^AhUipC9;_d+KUbRF}nmSswR2vot0s zcCTCa>Gvn8*#~EATXjEY<_lTd-IxEZ4uEAgtED>5I9q$;4ff-AqNx@8;F6vGozY zvRdh<{(}^;uEvQOJI%VDtF&xUw*S!2ZgG@LSa0gS-P21qsCCYJ{_Rgg8E4^3 zqdyAT%oBA|o?)P`wq4e;s-~mirJzHhU>WNbX^+hTO&2Z)s?Jy1(O76<@#=p1ZBZw$ zWq0zoPGV_!TE5xw!IAEc3&jUiZp>kyTHP7HCVR_LrMqIje}42l6#k_a_wd)6wQ-9% z552g3T-aSjJ}9E%s|R~V`w4H3n-%@re|y5x|GU_^Il-Tw-Z7N>_E=mjBHE$##tTKU zDMzFvtj`6RJ+IxoX3zZcn43(34qK{vXZBo6-qgUI8M%gaLACgKyB8DQ=j!Y{RkT3p zj>xl#v-Q5@Y9>o%^zK%hn{nFeubNH&j*g0*ej868Dq;#S{+6+N?sR=O`^?UBtb4!P z@qBfa{Iy&RmOqX>{OOjG*dnmvL~2i<-ZeYhfS?$^Mfxr+-PQ{BH$I(ZF5zsk+qqtE zSugu~i&GpG)7&^>3=vPYJ5{%iTI^^4sX4Oj7Ex17#sS(O>@*iZ42~SyzC!jsNfW;;DA# zA0k}4XHVl=&U&Io#nD@H4Ea z=l-#G9J4(n)vrF9_V}Uc@}(C|k`gBzKmB>lb@yFQ^RG;fJA5kiS;WLQv)@nmno`7J zxi$N@$oh}%O@(o0S%ou~`acYnI8`_G<%bBHp4aVOy5V6K`3kNaJT?bD#xNzG?aDm3 z(aiZx|8gzU*L{ss*b`6A|8C^JBF87W{6HaGeYjVd%nQACkCH{%&pV$UYI#{(H~D4n zi~ggTCE?qEh|e)|?4bH(aNA5>}pgGOQGSb8>?| zEPN&z9FJYNt!@81^Qin^5w#cd%+4Rk+&l3_*gcbl8r6NP|E+Yo>bEp1;qLzY{=O53 z3U6rm9E|$BIwUvpzTD;iR`)(dzj^xjV$FGU^IUpopY?C8-{}%`C*C@eH}cqXE8nL! zb6xH%^z&P|Q%azC&1|(`OXc?IQs3206O+!t?AvzhRrRXA+0HQYj1xaO*RGa(e7@W9 z2md7LqT7vc9v%r#U2pJI{2RApa9c^$ipUtaX+R$_mB zkC@4&Ul&z>H0kiqmrD{msq$c{x3vDp?`aKxO!uy;ey;DZh}D4AA@aTIvTc7qq;T)= z{F1bS%{qEdbN*={5QZU6b4@e2Ggz zL0+|~Z}Y*-n>pJTT=4#S$>z-~y>&00m|^z8+>6eKsbAf;>PuDF$4D68PF4NJ-ORN= zVd@$4vZpK0dc}gyCslofiTs-lTr-8k`4lYQO4q5u{0nP8G)-eS)_l1>d2iR|u2;8> zz0vv8bKG}oJdP3!d$fnYDCyT79Txwb3ic(BCf!-!KCOA~?(aqm=IgwAv&!+3+3&2GdXkP*o{P$;)LC(1^|NKFQ!jT+XUj4;xc9=1Ti@4B zd@p?G`I{-Znjc~52j)LmeFo#h((8nC3)f7TyzASklh<6YY&rW*sn=C%zlY}>nLQE) z5es;0jOTkbs_lR2`{}!G@6~({iQ+e3msJ$Zot!n*`0-hxxJH5gYXv#)qkg z<=3kFeaXT{vXeb{{wuZJ+FA@NpL0$oZO;g^6LhpneUjv-%a^(G{R9_ZzEXx8bMCFo z7GH1HxPXI6{pC6)m(9PAbc!E=#s5*cSLd%iFN)fKZj-0%q{+4sDV;1YZkj6UGN^4^ zE;zY#@r0X;TAmiwJo;VX_3`Fvm!GoQ2^&Ray7$)GmASmwe_7%3Mz*ykeFabbnEBkk ztlZoy$olB+w68kZhqk^FwYG70k2Vn8DSf(lkww`Iv7XPf_>SA1X7Ie};_etXce3-v z9BHeDrjL$0?)~fzvHH}o^vj#eheFg=7vyyuoNvgj7O|%G*Lz+KU*Q!ZSGyM)ZBA)e zrp{PVSXbiKzIx7EAi-?zM#vmU`Y_AgKAKb!TCL z@`dhKLQxab?JIheKHRi5Yu!J4+mdrh$&()+PdEWV|1It#4J5Bqj$hSNO@W$m8EhTceT zp_~IZmk7=?JoZpBu9z`5>!RiSj70MVCcBoWRcdYLS+CT&sEx1OtnJZvuZ1^xnTqCz z=35H*DjX~+`8etNRbd9LTd%*Kso^lmF}0|z4>|u^?y1tnBmFO4pM}N8qRL*umrKiv zB;U+9_wSbal9`9EH=LZdyz7x+O0>$p((v?aQ=VM?ns#-R_;|gZYqd|$ zMh#f{tv#stZZETmEsPH!wQ_3kr%hZ}kxgHiy+8u=;n+XH%_@JNZ2tUWdA|2^6n_ z@vr`sTah}!ndRxWl(!S_^Za~$%zSCut54U|e)>1E-0oOv%5uD`>*m(KPo61S@TR?dLrBHHb1tUPjPK$?^c{R6K}=Kq+1#f>u;Xh!+8C0 z_^$;l$B+CfQW5N{%Ig1bSz2#9?(>#m8+xUoEU z*&bN?XS>0qGf7kRnavp7=e*o@S7zHg0nUuT+nU_sX=hb0&)<5KDYr8}or`%&)#sM& za~=gvUSj#csVQ_4Q=RAm$JRgr<+DDkj(l{t|7cZ`a<}W=f4)8Lo|Y>Yl}Nbc zPps|)SbK8HO@O{n?HHLJJt z_a&Q&XIMmMJURO?AnTzWYyOf`j7Jyl_s?-#@wF(l)W_8G^2#HDp_)^kCQ2l2I(+5u z>T|7urn_F|D$4%exPRi6O>2WE8u-q+pC+^71^cATiBFs~xW8_-kUo*2Q@Ve3?fZ0X)n7LMOTXsKh~!MLKl-{RTr}o- z*(qs_3&l6HlNKA@jdD|7?>X`DrjJ$+^uOq)JpN#NB-rkE-~~TJuKUfE|HU02ckWX( z=TUL{v8Q)msr|Kwf7Xjjod5KK@!AF6{pX9DIKx5(-c7j{|3f^vA^rZwg=^!tpG#;= zW!3uQCa!odBryFfd&RDm$@ypQo><(b_~#>|N`5P=?wxbu+QCdp|2t%g1m|Rj2Jt6` zya{GbR|pM^E8Qmc@wrTm%BNYZ1s`Ic@t#{!WOqZn`OLjMqxK6j^Ore2I z1jEj->-BNQf>k@_cUtV%l{~Ghtn`=1_w&ENmcJ91JMu_6+ch*RBpzEPke(|L`d4CB zmc!#4GkL{C?sa}Q_H_wgCGmaQ)g|FC;%{tuW_5O64R6}shQ3MeYhtG@uL<3TcUwE}vXDV2>ZjQ{_G*26obr6!i>85m4p zWNd(nl;!8><)(nnh+}|==%o~;CYO|@f=mD{2V!Q_2Qg8wUP@|GX}SvdqD{~;6J|!( z8ajkBy`q%F66h8Y&{An;#vG7w3=9nW%3DOd=5Ks>M-5CeP?s{jMYy%5a6APu^V z7DWWK+6uJo0-?*4fq?IvNbL@ERck+7$!ZQiBiytyKrP9U%fbO>qT^UCj&( z450O32sNM^*dCy$0qFp(zd@)u4)O~l#0MZz28O!~3=9G&BA*x-7(lC+K=L5Wz`)4J zzyLZq1H=Pi1_pjc1_scrKOi0mGcbU5K7sD*0r5bXfkBs%fgu5IvR+AQMTtso9>@Wp z&}C-iU|;}Ua>8&qK+=%`9M{NdKnHIyGlD`26qi4P!gDayfKFy-W(3{l3W`LH-E#^s z)qu_gXJ&+*Wbx%g5*E8aE@5Vb>=R&M&~N2DglP_F)fzJ+Xmv8kJ;^`Uy@0qL+0CF8 z#mtPLeFY$28kAg`glP_F=LR7);5Z{-4rpf`Gb2bP$gaLw(W^1-0~a1Zvh0Xos2nGv*785Fym0^C8E@eW$`$IJ*iB?)BLnMSr6Of{gpl$aSoxgV5% z9t!(?$5aD4HG`ReIiOO9kQ#9AWdxnc0!rCBEG$fzc7b+7Ff)So*T#dcHF2Ks6H^W7 zMoMNz(AkQh{B`5k;bKfR^5Ad=ZN3GmS=Rq!1*RHMjv!$>yjL}0oHRBsSc14`|L z)PQVZW(4id0Qp^DYw|u!yFjv#^aE03Fu^n$Qw?ZGG$Fe{x9kv7qX$kKAR9q;VRsLx z9w+2xaGeUy7a()6yBSo%Gc$%k!&&?6_c}~}8G+s70#y_FB77^R8e^~;P)LI83V-hV z15*uXPc|VpgKBldxnA2HQ{PB&m?1nn>fsaZbvf;^@g&?$7xjGq}87(i;yE&%N{2l)`W zZn6Wr2Xq!GNXOIZh6`%g#>CiuFuC7 z3SqhjT;nj}ce6X#U--@O0ILC=!vw0o4t-p88`Cb(y%dD2)`4mhW=4=b zpt@VnNqZ8eT|PvZ13GYmkbC@yFb8z1BcZ$uYK1a0f=VG!3cYN*@GPdAL1(ZMG6&Ss zC!_|{eIcX$_{&~UX-Oyy!@=g@_j?3b4Su^oB_T5-D6~Og`2ESu$(UgX zx(|Sm8qlpagu*8V>>g0<46+M*8z2^}2EUu*h)@#`Rs+h3AiJ>FP2k=gfqEI#DJA4D zQ0s_L-3>aYmykIrU^nCUJE*Ho$Q)45i;x;nUzU)X3?kf1??eZ zW(1uq2kK$|=B)R^R0BF8o0$=G))>f~*894Sm})?`a56K3&S(RfQ}WQx15-^o*k7RY z>OehS&xPwdFx7zeY%?>0PB#SAm8M}6V=&cJg3SS)2MAJgebXTaOf{gJxR@D1J#BRL6k^Ckd$mo$pIX zO&5xLuJ7H`h3V#Quo_r=MXzFP#Z=P+Rs(9`fc*YuNm3Z5nqIIP{9yK%YaV>kYL@(j})(5ZiraZ!+6_iSsPV5*r9HV1zgf=<^b_o#J zO!sUDs{xJUg52CEVsQdf4QS+tnGw`J0;%C=_r+okXzZVv5j2_&QnT%WKMSTgyTEpV zM*l!+G6fp{W2)H=R)gOz(4B{b+_RSmbM}GNfKDa=jhXj#clKkt2XyKeGb3nUIj9_a zY9@+hj2d*91T!OOBo^eav=w>(FwHp#whJ_p4{F!t8eF@EsRncr5HlmVhGJk~=m@tE z#Z+?`Yz}zzhJk@$NzpYdY3vAC4QLz=WS9FL^OuSPr@(5aGegGo#AB7i zG0iy*Rs)I)kl)>kbFqvuo*_aF=vF{ToZ>bIbYmwoBk24QP}<;OWx>)1IS;lAbk+$d zK7u~j&Bb&xc%GH9o)vNq)9KJ-Skznun*+Lc8#Ft0Uh^WB`9aWKyUdKBn<7E89dE)8 z9mBNiGT0o@_$$a?rZG+dm};(o)qrM0KD%*0d!IzNP&5p!{Qh@LHJ~#zm>EGcfFQg4 z)BKlXs(A#q3#O)}N;wcy&10|{P&p4WM@0JrmcH;4uo}><&!9BFGQt?kIQCPp8qod4 zpt@&)rWBSsW(FxLA42_ZUTjm&$+Ew`tNVR=74%?Ab<58 z+p!4KJ#WEk0+<;XK;_}v&wXz&)w~0%fwjXIuCT@uKJUS5Kr<#F_o&PkU5#nZ2e2B@ zC@V-!ep4uxasw2b%#0@(85lt07nxHxoX0c=w6cSl5!8|d#p(TJGqKEVfo5cw89}3M zAiMruY1)oy&KIzIKr`B)^lWlvsx_vXuV6Kxx)Ky$hHN*IFx7kms{xHlfz+sOca+0a z1De@kW(4&sLGG~?yAX=0<_Fju{AzxJ)!>f{P%XgB2)Z>EG}ph$%t#H>uHRsDIzS$0 zU|={_ktKtv<_}nn7b61$sGZbxr5{V14OA;JGx9PrfN#Xh(Wx`VH0K}K98l{HwE85J zuaF;84d@(AX2uzekh`LPeU@8~sRp#nhM5u6!vf_Cq3ug;G1Y+9_%Jhqda|JLG1!L*A7tOgWcpn5r3y%$T_%L-Nl z8Uq2%74f=EEyXm44Xg%qvk54CJm-HFz*NHyRs(a-KBG!3VaNei1G=FHWX^HJ2F~Kvx|y$j@To!gLQeSPf_v6_lR)i~n0;s^I~v>11MH0M#oyANOLJ z#|NbxW=2p>1SLgw-hU#P=J0{d0p)j4oYpj$F2+;?TFJ%C2&(Bo?uolugQZLWuMcAc z&9s8lu$#}slHWnIz08cDnM9D9y$gh~)VV@n_kd=&Kx*n9))Zm7M;NRIG#Uv~v-HSM zEcr_Wti}+efPsNQ>*;GHOmjrRYVfC3F|Zm?FBMeIA6t3(CZ;*!U^PulkQ?Yb*V|%A zV-jFBpxF?RlYD38?!+`l608P)T!7AFXJ!PAd4kHr^o3ej>LzKhIiM9jAiL%)ipJ7D zlmV*&ox}%H^KG@I0j7IEv&PKeybMw!CVVa!Qw?ZMA2TCpMjNEYt3?k>n_nJm7wrD9 zOBr!k=0+93YCvfWl$R&|oSBJfmm*kADkw2CFfh2g6=Uh^fm)c%jG#~gm1FGbpyeJQ z4ajQ-l)>hJ&PoT>ag|ZKqA=}J0jmMExIp>BXWkwx<+CbS4QQqnu|E zrW()*`G|hX)lGcQG1VA>%>k`c1i5*0{<*K1YK*~ZKxeLl%7kB%pO0Xw0Ub`y%m`W? z2QufZ(1RVAYD~fAfa*9GW+V+}S3zZx5`8qn+>D87mXo02iz z3|d9Q%m}IrKxKk%#m8<;yX?T`fM)zb`9jWWEiU@w5;8M_ z@;b<0CKW;#G1WMN?E80b9dmN>L8U!N3?kxIlnR=72MK`HfEfOu!~tT!MWUf1ybKBqpwMGr z$b^b;!9~iTB0O-B7N`g}SR~2VH?hJq4>Xm_AP5!#SuqK!h664#7b+qQ7g+%n5de!M z89Szc))RnqvqMFKOOrrc1_lNTMwkmXLv@Kl)%cd?1cTO}FffR~L^471M%q=tB}D~!R=a$ zq2@S-$RiAqKNuq7kYohj(EyRu#Sn495Q)MN$%l%tz+Kn_6+wjI91M{yP!U$Ju7aY> zyb@?*%9at7FF<}i09Au9=LS^79;~J~524QvECSN?2&x95>obN3xUmjW47MG_0Np|j zqDA25fNuE(i3q?&)IdcW0|SE~T*MG6A_Nz)fr<#jMcknxKj3zO?%f5M{24A13sv(4 zE|LKic@Gx>-LVPMB?>ngbbk*>gas}FT2}!Q;e(6Jg_^?z7g-Axk%Wuvfr|Wt+jSZ$ z@&+z)9V#LL*Yyl4@(!-%3si&?u7(-ZXk%bt_z71d1Qp?it5JZ8e1NObhl+fMi`YU% zzQRSkp(20bBGFKhKX8$3sK{@)NCi}c7jAnqROAy}&2*^9Te!$Fs0asK*EXmK16<@B zROBOE*E6WdH@L_jsK|e~h#;uJ0q*g`(!3T_gdJ|O6I6r;E)oqD5rd25Lq){lA}vr6 zR=CJ?s0bHaWF=Gtbh9_41lR=?VS}qV4HaR8i`;{X9Dw`c9aIF7<9=g^h`_31xGq%; z5i<-CUks6G43R7hktz(4b_|i(7$O@mL=IqxT*nZ3i6QbELqq`7EdgaqxZl+=L>w?g z5->z+F+?U{h%CSm*@+=?8$;wLhKLAgSOmq*+883P7$Wf)B83GOGx8{M&c?=f;-5CcGc?cJo57l)WF0vUaauqHDy3Got>jhlo6;#c0 zxCjSmc$a~J;SO9x4JvXUF5(Flxd#`?go@mSi?l&SF2hCULq)E@MYcjkp20EKE z21CRPLnHx1q!2@-A46mrhR7}qk@FZL4>3eOV~Fs;x?J$sQNj>0#1L`B5DCN(Nx=}Q zz!2%j5LtyGvIj%tI)=z+3=sj?6eZkW8WhphR6{Nk%t%}tgt~Kcnqpwh`3;g zL|};IV~F%(h^)mBxq>0`3qwR4He3pKp%I3NKZZyihR6gAk!2VnhcQGRVTf>mHXNhG zpgM+#GlobkhDarb$aD;mofsk)Fho9Li15P}4!}dq5JMyoLnI$Vq#r|M8-~bD43Tdb zA`-9#S#bAQV~F@-h-6@hbYX}r!w@-%A#w*pV$Y-d?FZie$ z3v}~@DO`;tRKyG}q7M~04cBE46*&tR34n^6fr}(UMI_-Oc~B7vxJW%zL>ewK5h@}D z7g-DyISLoq0u?y|7dZnJISdzh2o>>$i~NC#%!7;YLnrPw!$l0CBAehMp-_>%aFGV6 z$ZojE3aH2~xX3Z6$TqmhL#W73xCkp~(?0_P!w$HJ5maOkTqGJQvK21U2o>257g+`s z*#Z|i4HdC~i#&mfc)&&eK}F2qBC^ma6a%;jXaxu;GnvChqM>Sx;Uc9_5pB50EU1Vn zTx35~L>?}32P&cf7hwb)UBJM=paK_>hl(h|MO>gFN^p@vsE8a~WI9yD5H7M6DxwS* zxeXQ3hl~7!ifF+_lt9NMfKKOti?~2Vbl@WKP!UbING(*v1TL}=DxwM(*$ov@gNs~& ziWtE~zCuNG;Ua>dMT!gz4C-(ZE2xMDTqFi6;sO_`fr?ndMdm_9?BF81p(6Hhk(*Ev zTe!$SsE8F@L;}73qVEgh55R;UXnakzTmSWT;3#Tx2U$WFlPTHdLes zF7g8^(g_!lgH9H;!9`r5A{}s%T&PGpTx0@NWCC1dEmWinE^-E<;7!;36NPBJOYz0nmXN z3=9mOa1m{&NF-du7b+437s-c;guz9oK}BNVB3qy$;c$`LP>~3@2m@%dF9QQZG+aat zDv}8o34n@Zz(pFNBI$6El~56XxX3xEh#y?!8&o6&E}{xu&KwFC@rQ~8!$lgPB0g}D za!bO%tMUvqn7oj34aFIVyksP>)Drn0N0|P@L zTqFQ0QUDief{GNuMK(i4vfv`Op(43(5%9)G1_p*~xQHfb8w~>kLq1$27%Gwn7ioZs z)WJnoKt-D2BA1{dO>hxr&_a6#28I^6h%Qv51}+i;6{&}dR6<2+;UWv5B8_m7lTeWc zxX3rC$RxOk66jDC1_p*{a1n2)$W*vU1yp1XTx2m+WC~p53{+$mT;w}cWF}lh6Lh!> z0|UcsxJWouWHMZ&9x5^eF0uwHG94~*3o5b*F2bM>Y5_1XEQE{bLPZw9MM9t=^Wh>* zP?2SDk=0O<^>C4EP?2?T5f0FTJ_ZJc)-#R6|9a z;UXPS5l6Vl45)|$Tx11QL=P^q2P&ct7r6lyF@%e}gNo?GMOZ)w8Zm%Q^@GKQI8;Of zu0{_kVgwiQg^HNKMRK7c+HjFxsE7_+WEWIK3oddVDxwJ&c?K0RfQvAIj#FY_U@(S@ zh(Se8!9!jJDsmDoVhj~I1Q&6FiYUU(34n@l!qvn>MO5HwDxo6ca5a5Uk@Ikol~9p$ zaFGL0k=JmM%TSShaFJ(Fk^OLyuTT-h>pT6qN)fvW+nZvuHy>V1d^%!!~i|6mbwXfehB+9?1QF@jkS zTC)unfeA2xc4UA>ETKgU1883fSj5l*>M_vjbg&4_D)3@DhzLv- z7O;>9?bic~n7{%Wv^x(X0<9_;K>LZnBBn4A(C#6y2;5}QJ}Iz>p#jt&(5@(m2+TpC zy-Q#bBbYBhJD$KIhA@*s`=lTuX66hGe|Q-fz&im!`O6R#KA^SOAQ7+{Baq2cnIUQ* zA|N#}Y)B%;AfI}2Ad7&C!(c9C5s-U!B8wOqf`k~5)PU^*#UN-0AJ{HKV>1(m1^x^S zpurkuunUdNOc`W+kwn0zgfl`!z`8&VWJp040sDOpiU=sx9YYZTx%mT%2*_wDCM1)= z;p2iVVgw4+87LwklRcP`bQyt6?n4m)nY;r<1mtMY`Y@1B!M-pCnasn2WR5Y&WNj1? zkjbGaA|R8CS&?;tOcrKC5-|ap{18P1WO5`svKo-d3s6KrCci)t0htV13j%jDIQ4pP zBAH_fGMSePSp;PA6ciDV$HP((l`??Vv*nan1DtP5naAF_xA$m9+b5s=A;P((l`D+nT) zV*xTb14RU6@&;rPOOVMwP((l`8w(?uV+k@jR0LTBWHM;>2p(USAd`QJA*nGmFl1ne zkU$YJW?(oZi6Uakz`!MiB4Wl7V5K9I_fvCVYk} zV$8r$qJW~tl!0NjB8rGP1H)M*6cI}X22oXH5l|*fQb!RnW?)#ai6Uakz#ylMB4W2E2lnJ9zMT{937Nd%oGBCVG6)|UEa5O?O$C81e z6Ga4+2`{3G7&99wL{Tm$-oec zA_B^TOHoCP85m6Mk#&LcU@fYMIRnFIR1r%C25SdoU7$SJj4EQxz;GH>#FT+S*bzmS zIRis9s)!{6!(tQ>P#%1ZDq_sQVC;l!4k!;+ql%a_FkD0xv1DM7bVk+%%7clhBE}31 zD^W#E85lmIikLGnSh}E?W68kKh%90Z%7Z6SMT{931YMDI85@`~Fr=f3m@_b}M-{PT zU~qLq)&?WC+^B`e&nsZd>DH7Is@qu6!qvsx%Z*Gi}^uo@J*eBio3VeGf}l%p;D?t(o~ zU0^jRcKO0}fx=Mr%gWmbU8kYCz-mzJ@&oIFr45k3?BRCZgz5sTL9q+8dkm2_7Om8~ zh0ygHstc?J#V+vO9`IeOr@gM_*um}k1JwmqgJM@8*oCmPG2y)r2SOJwv`-IKgJM?@ zSQl*3IVf#>fyb9DR2Ntcie15AT`;>Cw7w)GbQwT(fz_bc1v(o95*ncRQau}~Dhl_P z6I2&i4T@c%D0YdTxxN;mD-5a&tOmudFt{$5T`X|BGNHP_YEbL~^{XK+1f`9L&pk5{ zx@w`iz-mzJ0^hvC2wR-A_mEH*AKb19P+ed(D0W4{T?n#k`5&gO2wf|oy1;5s?23Zx zg87RHZr4GmF0dLDyFhyu5&i;&Awt(ps4lP?6uV-;b|L#q5^mQAs4lP?6uV-@S3_2T)yLH7Isvz;(gW{8zYL-=Mm{YEbL~?aD>? zOK3CyLWC|J&}9n@ke$2CD0XFm?SiGlKjMaq|HAE3gz5sTL9r_vtPAF^RJkHsgf2^{ zF0dLDyFj;JK~7c#`D<=kfaD*zT>(&CU^OUq<)YX%@v4>_LRSV<7g!C7U3p+#Fn|5O zBOCA=ZdW~27g!C7UHM>LFn`^ix>60HYYJ2sSPhC@1z=sUMdx!u%ck+d)8PuJF0dLD zy9&X&V0MA(FNCh$P+ed(D0UTrb%Fc^Dnw@_URL&!GB|>$9)u7l_1=a;!?#2+c{zT$ixLq|+U0^jRc2$FQ!R(5D zc`gN^Ycf<9SPhC@HE>-Zf2kgMQ^NsIhby7Fz-mzJs)g$Uh5SWP*4YSM2cf#aYEbN| z1M32XAxPKGdz1emblrgJ0;@r>s~)Ti<}bEsayJmVK0w@{~ zP5&w>gsxDiF0dLDyFh0oKu!e&`D=fO@EnA$Jg6?P8Wg))QS5S;T(A(Ks~xHftOmud zHn1*OUVc^MnTXJ}0ICbD2F0#+ur8Rt7T-VEfY7xQstc?J#V*i!We9)S{tr?@=(+^e z1y+M%S0{>HVK&9z5xQPOb%E8O*wqEr1@adt&3o${(_?_Ac^1&Y)eH;_U^OUqb%S-m z{3Z1Kc^X2O3RD+Z4T@boU|pcJ0kUh4MZx`#@Vx8{)dg0AVplI%7tAiRa1(xnu4Je# zuo@J*`oOwi{>qlm`tuELR})khSPhC@{a{@%yWUPKeTUGs0ICbD2F0!kU|q0w@1*1X z$N$6a+6&bMR)b>KM6fQHT_2VRMIm(Eg6aaRL9q*T<_DylhlL?CJifj`b%E8O*fkl| zt`EQFB6Nv^E^cCAU;wK@u?uuI5vpD6aJ$T*y1;5s?3#*U7eZG!R2Ntcid~?4tPnRX zI(X$v@WAaVgX#jSL9uH(id{GQU*AUPnhwb8 zfz_bc1)9Nyv|vI0n%BUn#0s}d9jXhg2E{JWnK2Mupi%m- zmcJJL%<$lX+Z6-V1y+M%*L<)pSen0R|M3SxR~b|nSPhC@3&6TS`3vO2io`vY2wnY9 zU0^jRb}aH@1lv1>6{7tF5N z9T)B*bUlFT0;@r>YYA8v%&y2w`?e!={f6oSt3k1ADOeZGt~)C`$`QK6phr)E)u7n5 z46F+lhF?Xu@gQ^=LUn=FpxCt>tP5tBLia@^fB8Ulfz_bc1v+yGlD|MH@1lv1>JoUEFDoNdDRZ z)dg0AV%Hk5E>M~W`78LKYC6KM%TQflH7Iti1?z%^p;+daayz(j;DO{gxg8Wg*BfpvlUUm#s;3(6-WbU8wGfz_bc zwHvGp=C9eCrmjTjiiYX}t3k1A4_Fthj9RC2@;pLUDO49&4T@cR!Mb2}1 z>H@1lv1=b#7c9QYBMu25bghKy0;@r>3v@CWA|0}4Tv9{mItK0kB=5G!F_x zw#={>2wiufy1;5s>^cb61@qTQfvtfEUEiU)z-mzJIt11Q^OwrE=^6-Kg3!Z3!D>+K zIt5!JWs0^DD>pt`_n zQ0xMohX-*XC`GlG7bhWfeTC`*t3k2r4A?Gs{yOa|iO|IlI((ObfdQ-r#jdkpU9k8n zKJsQaLYFpF7g!C7UFX2MK$}`YF6@uu;6&(hhw1{WL9y#RSQpH$a$Eb&2wllgU0^jR zc3lALg8A#s&O;j!y6U02z-mzJx(L<g%W7ZmYlN;us4lP?6uWMMb;0~);%##bp{ow63#w?X3D7!?Tx(Lt9SE0JVYEbOD3)Tg* zYpbE)286DUP+ed(D0bZg>jIU%pfEI8y{ZVIiwAVUDFXuoSPhC@_rbbgc1=2O?1s># z3DpHwgJRbMur65s+OVjq524Eqstc?J#jb~7T`;>;0*;v?bR|P|fz_bc^$4sB7GHI$ zDgP0=8lbwsYEbNY4Aup+D_7;}a)hqAP+ed(D0YEP3`O)EB5%8KB6RJ7>H@1lvFj;{ zUCG=__z}9ULv?}GpxE^ctP7Sl7O%MZkY4Jd6qY&ftMp-UgC3#K8?Y|qFnk3M!_`n-U^OUqy#?!n z*=1x=SBB7a2C56J2F0#-U|p~@&nMu16`|`5R2Ntcie2x)x?pyRNqurf=;DJOw+~i> zVi#ytJt7_6yQFy@q00cO3#H@1lvFj697tCKT|Eh>0bd^AL zfz_bc^%<-S=C7%mk4zD|ra^Uq)u7n*1*{9ZT|1zZb5Z{ z)u7n*4Xg_}Z9IXejXzLbU^OUqeFy7;*#(+IMCg(LU2(&}zyMZ*V%HC_E?Ale%^@Om z8AEk})u7n*6RZnnS5$Fj0YX;*R2Ntcie10Jx?pzkd8F1Mbmc;Ifz_bc^&6}UX4ieq zf8P+gx}dtiYEbO@1J(sAd$-NhJBiS>9I6Yf2E{JWDa(kq%{%kuKM=Z(Ky`uDpxE^f z#V*j?C_>jms4lP?6ubU|b;10#e7{>WLf1d2F0dLDyBL@l7!Z92P`&&Vo;IYtK<#b@ z@Uh>_D0VS|b;0a166z8~=(2?B0;@r>iwUd?)?fbC{#6a3D-5a&tOms{X0R@pT^qLE zl|kq#h3W#UL9vSktP2)j_kP@6gU~evstc?J#V*jTXNdTk#WZm#Lf2-fF0dLDyV$^X z!TfdmmeEv%u1io|U^OUqv4eHN{8j5=umhp%BUBey4T@bHU|q1hoW0|)>5s6m|e$iUNS@IGKT5`t3k1g3#4 zhUx;VL9vSmtP7Tx(=?|^BXli*>H@1lv5Ob13uf1~*^>Jax{gD2fz_bc#Rt{}D|;Vy zCGJA#dI!}7R)b;}KUf#cE`|56ln}Z^eIOwZR)b=f09Y3+9a z3v^czYPsZ$*NelEjR;-LpevFX7#P56Q0xMoOb?l(2OV0^|88OI zCAeL>P+ed(D0YdX*tPo$vp7Om5L6df4T@b7U|q0u_+TUBe}t|ws4lP?6uTtBx?o{= zI$5O;p=%CQ7g!C7U7-Dekeh};ZBZ7p4poG%Lr`5{H7IsTqu9m2$x|Jn>m^hdSPhC@ zGGJY>w4vTvY>v<+;0H;)U^OUq$%1vk{N>7~qk_<74%G!#gJPE)SQpH$aJi6m2wibd zU0^jRcFBWv!R&GlKKdM?s}-sXtOms{1-LGdA5MhWYutdRjWtkRU^OUqDZ+JuLVkXp ziY!9cIjAnM8Wg*f;JQF|f!5qg!N~k1R)b=fDqI)HF248Ajv#cIKy`uDpxC7b)&(1nx@_z7457;( zstc?J#V&QQE?9hZKX`AB(3Jz#1y+M%7wAq2M0@@b>&9Ayu1=^fuo@J*G*Rrjuy@W~ zgsx>!U0^jRc4@(Nfx^%_IQHc)c-lA&)dg0AVwW~t7bpx>&YeGt&~*o@3#4SB_?An_8vmBwz8>$Pe2E{G|ur646xaNUL8A4YER2Ntcid}|aT`;>E z->c;!bhSfufz_bcWdzm*v+Lx-TyKP~Wl&vUH7Ir&gLT2|Vp%Hi2chdIR2Ntcid`mP zU7+{^#d2i-x^oC!PoTQMYEbMl1?z&@m8!YtF+vwJ=!zr;1_rPi6uZp8x?u6uoXodJ z5}po~pt`_nQ0y`X>w?)e;pmQRgf4TaF0dLDyDY%EknNIy+vN?_1y+M%7wBFWM7uZZ z^r!a-T`5pqU^OUqSz*{E4Y#WXstc?J#V%{GE||YSb|G|4gX#jSL9xpQtP9y+QgFN0 zLv?}Gpx9*#)`e^rLf1*CF0dLDyX?TaV0MAVqYlIEdIZ%4R)b=fJy;jauFf;Hi3nZ) zp}N3oQ0#I5>w?(@n!DKrw@Vsyks1R716U1;U5;Q~FuNqnZ>~e=a)jyvt3k2L39Jj0 zzd$7mXy1(m+^#gJF0dLDyPUzgV0MA_1t4^_Lv?}Gpx6aE=>X9_1nq_KfZMekstc?J z#V%JAyR`k6-$3X(3DpHwgJPE(SQp4&puGGb#Ahc$*DI(luo@J*+`+nF{?d{>wF#k% z7j&H$==MI48Wg)cKqBBZOCY;^Smysl=rVxn0;@r>%M+{%W>@*k7B_^hV5lyz8Wg*{ zz`9^%)bXVs5)rz}pt`_nQ0(#s>w?);-Sp}gLf1^FF0dLDyL`a9VCit;`mA|o@c7yT z)dg0AVwW#i7tAi3zd08Xx*kJyfz_bcD*&tu*)DCkT?tTKU^OUq1%h=U+lA283)KZygJKuxE-XZv2Zf<2+^(Ha zU0^jRb_Ju@_4Y-k7ed!Fs4lP?6uUyex?uhS*(DFRODGhSzZn?7YEbM71?z(O>)7&h zlMuShp}N3oQ0xi=>w?(@iZ2DYUC~foU^OUqg@bj$?Am?rdkI2U2UHhW4T@c$d%6)} z2(n8BZr2v5F0dLDyCN~{Lg=~$)dg0AVpkMc7tCLvFjR!w^&hGWtOmudXs|Arzd&J# z(4_;p2#x`)2F0!zur8QgAb%;r?TUcv0;@r>D;BH^W)~<75xRPzy1;5s?1}^HLbgi| zZr4GmF0dLDyW+vRknKX~dJokFR)bl2)dg0AVplR)7tAiuem#V)2~b^NH7It0Zn1;R z6M_67Ii>c93*4?9P+ed(D0Zcy*cJQ9!w{kC0aO=Q4T@c9U|pd43uc!k+%9(LW$9ow zD0ZcTb;0}vvJ0Wh0ICbD2F0!nur8Qgd)yWI?cjDrKy`uDpxBiO)&;YRwf+VVLRTGB z7g!C7U0GmVFuRt`uIRUi+qD>~3#%Jm%orLNFt3k0V2doQJSAxP& z>z0d*E!?hmP+ed(D0bz7b;0afDg2}mp-VavWD)}dSPhC@d0<_j{xWDhDrr)HtQFiY zcc?C~8Wg+o!Mb2}eVM=E2|`yPR2Ntcid_X@U7#=owKtyM;#`W*H4CZ>tOmudLa;8F zU9Xih^ANg@LUn=Fpx9Le)&&YfkX;g`&tnm~K0tMW)u7l_4Aup+>rd75^9WtiQIIeM zt3k1=1gr~|mkVF2FPjJtLpP`{uo@J*O2N8dc7giK2wf#mU0^jRc9ns3!R!K+^F468 z7D9D_)u7l_4%P*;3$*qdq3ak_7g!C7T@_$m$ab~D?Ro{(1y+M%S0z{%%r4Lv5keO~ z=-OBY1_rPi6uYXxx?t(><w?+kr+?BBp(_@u3#w@_UWEVo$7pN|<8Wg+Qz`9^|6>Hnm|dVWkI*#(stc?J#jY-} zE|^^)f4Rf$+6&bMR)bGFYNt*&}9PE1y+M%S07jxvRzSdyJDfbz-mzJ>IdsWwhN)F9jXhg2F0!kU|le~ zK<(ZnxLq5dy1;5s?3xJH1+xp(*F)%f1l0vrgJRbtur6e~;^B4)$3wyptOmud$zWZ` zb|G}RL3M%Epx8A9tP9z$*>Jlmpt`_nQ0xNTdyE)oTjO>@4xwuuR2Ntcie1w%?3w|$ z>oHUpSPhC@)4{rs{e{pakpKw|uo@J*K<80HTnJJDn&X%Yx622r3#)dg0AV%IFNE>L>|WS7K(vO~ww?Ys`Wdw!K%oo}>5W1d2b%E8O*tGzx3*;|Q819!ktg8l}3-|@q1y+M%*Fvx^ zm|dw}vwk6Ti6??gVqgHPL9uHQSQjkKgZf`GaJ!75y1;5s>{<-g1+y#i9yW;;c3ggsz!TU0^jRb}a+z0=2P0 z?fG4~n;y!*)8THYF0dLDyOx7>!R&e*obHLxbsMS+tOmud6<}Sk^}VYm{S!v$`UTYm zR)b>KO0X`NT^oZY&PV8yhF(z*R)b>KDzGk47=qH_FM-wf5V~xjy1;5s>{<=h1+(j# zYC|bPS1eQ)SPhC@YrwidCmDn6DiaD=iqKUD)dg0AVi)MvcSvai(iJJT!VIBn0aO=Q z4T@dsz;?m%m(}^}XArs$L3M%EpxCt@tPAF^@<`=ygsvx0U0^jRc5MLbg4xBt)8GR_ z7h5tYZ89)`)u7n55v&Vl*XG?k&k(vapt`_nQ0&?S)&)w3Aae!389uLor$aBOF0dLD zyEcP$!R&gm*Hsmvs}QOStOmudEnrw?*J#%2FZgf4%mF0dLD zyAFVL!NZXGS2RLb1ymPU4T@a{!Mb2}Ew=fLUbiIY@0;@r>>j+pEY)#;1JNr)vUHoZ~Fa)bXvFj*U7tF55 zbM87IbQwc+fz_bcbquTv7KXE!1EwQ%g+q0L)u7mQ9IOjw*ZSj&S0Hp%Lv?}GpxAW+ ztP2)~G3qnT%HUx*AF2zi2F0$EU|le~K<5J>bRB`}0;@r>>l9cQto~ZRS-=vZ>jhL7 zSPhC@r@^{lcIo-p2_SUwr9(mEL z5W1qEy1;5s>^cY51-kSI)ZQrbk2zZk55oqiF0dLDyUv4k!R!K^jfv2;3aSgN2F0!m zU|q1hY&rjW0YcYhs4lP?6uT~hb;0an$_$&0(De_h3#H@1lvFjRG7c30fR({%t&~*Z;3#K-yH7IsH1nYv?^=sLiI)q(WP+ed(D0V#p>w+Fv#E@ctdUrWI%};>p0;@r> z>oHgt%&u8`ZUrH9?SSe6t3k2r30M~>e}T#iW#M++hw1{WL9y#8SQpGLP+JtC z>jzX9SPhC@&%nAsn>Io14gV*h_Yt}TvLPW4R)b>KbFeO$U0clddLndbLv?}GpxE^S ztPA8XkX=h=-Yr4sa)asut3k2rC0G~Ct^w=|?h&!)7AavD0 zb%E8O*!3E$3uf2x;Mw~Tx~4;Qfz_bc^#-g9W*4X}iqN$Qstc?J#jdwtT`;@Wt=kuc z&~+B73#T?NWy70;@r>>myhf%r4OSD1H@1lvFi(17i=7$z|?LzLRTkL7g!C7U0=bvVE(Ey_e8R5F;o{= z4T@dgz`9^*BYXRW9SFPjLUn=FpxE^ttP5rrON6~FLf3VuF0dLDyMBOm!Q$(4%KT5t z@G$%c)dg0AV%JZwE|^`~(*E)YT>`nF*kE7)t3k2r7g!f8FY7djvm$7w_ezAWKBz9R z8Wg+!fpx*+Yt2OA3WTn8P+ed(D0ck^>w?);x^w$!gsyW?U0^jRb}=x6_J6_bI{r~& z8A8`Ps4lP?6uTI~x?px4)%M$s(8ZSri7&7k6uX$fx|OY@-hq8f0!=0SCV)u7nL4%P*;3$#B4p=&Qx7g!C7T^wLtFuQn} z{@&Au+jSeN3#5s6m|a<0-+3c+G3SFqh=BpD2E{Heur6e~bl`SrL3M%EpxDI? z)&;Xmb%CP{LRUOg7g!C7T|8i2u=vWo-OjBAw`(#~7g!C7UA$mjFuV3|+{=s5wE?ON ztOms{KCmvBT}6kIv<%>Ooq_5Ct3k1gAFK;z*N$B)brHJWL3M%Epx7k<)&;XmnWIgAWbWgzlJZmVy3z}(3#jK#Ys`b2IIBZ4eDuwC-t3k0#6s!wo7yG=m#t2=Fm-g4uN`a%Boa*Dk0ouo@J*B*400<>Bj+ zUtI`Y7oobqYEbNw1nYv?CEP3Qfzb5=stc?J#V#qZE|^_fl7(*&y8c6Tfz_bcB@NaE zvn%UH%WH%#u|iPHGcbVFpx7k?*9B5xHSN&llkl{m3)KZygJPE~To)*oL3b`7bh$uv zfz_bcB?s08O7kFH?w=~wBXmVUb%E8O*d-6v1@l*8b7K`kR}oYfSPhC@3SeC@yF42T zgAls9pt`_nQ0!6!>w?);$0Hzs(6tDv3#OBJjOX4l>AZy6D~n2JC# z&%gjygJPE&SQjh|SBaDrB6LYXb%E8O*rg8E1+&XZaB2)fml0GKSPhC@8emdW>+@HAfo)dg0AVwW~p z7tAiu{$+%&$xvNjH7IuJfOWyjLxB@B+Yq`|LUn=FpxC7g)&;Zc$@hLfgsy{7U0^jR zcIkn2!OBCAs}E-*blrsN0;@r>OCPKYW|w&y(_4hDk5FA;H7Ir&fOWy*i$nItc7!g@ zVn~Vtt3k2L5UdMkSDVXYe}pbYs4lP?6uXSTx?pyJ_AeuJSweMz)u7mA4Aup+YvTIX ziU?hSP+ed(D0Z2Eb%E>x`N5N=%t{enFK0q^fz_bcWeU~>vx}ilW(h)9JyaK14T@c6 zU|q2IlDzn<1EFgQR2Ntcie2VlT`;@qI(Nt+bghQ!0;@r>%L1$mjP95SPhC@)?i&QyFhz< z5W3i*H#>vXpx9*t*9D5N-O7>%D)9J{h3W#UL9xpgt_u_zCz9@%B6Qh9b%E8O*kuRS z1uGBF?!9mnp(_EZ3#w?+k=NVFn(B%x(1y+M% zmm63Y$X_74^wU;8KH@1lvCAE-3uaf_zjbR6y6U02z-mzJ@__3C`8;$@u>5&= z7|w<20;@r>%M-2(LKj;Z$khxCU^OUq`G9r7?7Fh_%r=BBEvPQA8Wg*H!Mb2}B^kGzMCkH^>H@1l zvC9vv3l?8{uPU5H=qiHh0;@r>%O9)@X4jG<_jwVzra^Uq)u7lF0M-SwtF6>`B0|?L zs4lP?6uSb!x?pxC%xrZ==(-2h1y+M%R}feiC=5Yk=9#Ny^UlH322(jCsDWxhR{_4)dg0AVplj=7tAh>4E>!5T@#?Xz-mzJiU8|^)s_2g zf;J#@t%2$St3k0V608emmyGZOM})2uP+ed(D0W4Gb%DYV^tNanVvZ1=bYEbM-0P6zT z1q#Fa7t?YOx*DOnz-mzJN(AeI*<~h}ErrlE6RHcW2F0!H@1l zu`3y@3uYJ7=gM$|u9Hw*U^OUqrGRz8>aSVh3_B3I9zu12)u7mw3f2X)YkN=36@;#z zP+ed(D0ZcRb-~icJ4wBF2wj4epg3e;0INZ<<*@j`?yO{gxg8Wg)Sz`9^| z-TG~_7@^A%stc?J#jZ@SE|^`~m75a~x+0;vz-mzJ$^z?x*(H2l_Zvc2AygMw4T@da zU|le~W*IyQLg?y*>H@1_&?`zwEP<|>%mM3?hXfWwM$zAI;A6|c{Dn|mAT>-Vx^ls~ zxWK9y7$lDEQ$Xmt4%G!x!vt3GqmqFE*4jliCl726^ynytL+Pv35a#Gtfi*LL)S&9h z2kT;mNHbU`O)*61%7f|xsX^6M0M^9@)n%?C`Wm6D1*!|A231!fims1(mVXht=0J6U z)S&7r0_)O;+STzw-T|R&3se_K4XUnU6kQiPxIZ9ty@%=osX^6M0@h^)wd-HG1s6h> zRy9Nzqy|-2DOi^gM4BNl#Y+yMD-o&-qy|-28CVzS-g}V771IviM(COW)df<6s;eBV z3wrD)l~`B1zIBnQt@`L z?KOlhTc|FO8dP0XU|mWeEes3{yAOI@MCdAo>H?`j)m07Fr47{;-&f;|(6tt-3#0~B zR}EN~9#mIGQutzouBT94AT_AEYQefpp}I6*s`4Oo$<#uGL26KS)q!=1L!=q1{lc;k zy27ElKx$BR)q`~jL3OPYO__zz)dJN8QiH0i0jvv_4%gh+ZG_OZ9jXhY231!hSeGW$ zu79t~|08sLf$9ROLDkg+)};p3CGH-ojL@ZB2N4FTLDkg^)&&bgW6iT05xSD0x(4k-;87%oC}fz+VtY6I(nrKpoVTu5aU zXFbGUAT_AE+QGV1A<_(Q_Po7=aG?WK7f21Nt`4v+SZHWmkK#t?Due0*sX^7%3DzYI zwaa+-wz~*jYoWS8YEX4`fpzghby;3E;Ya9t0o4UkgQ}|=tP56RZ(h2f453S`0b&?P z4XUml6kQ7xa$X^H89{Y{)Iee*04f494b_}pusJLs2?hp+&#AkXBFu?_>H?`j)zych z%lyP%Bsb?kb%E5N>gq?)73*T(h_I^$stcqBRo4U*UGD4dIwExSKy`uCpz4~4qRUn{ zZ5u+@9H=gk8dP1AP;|{=i{wS5VnA6* zd}?k%No9ONPGWLuMt)98YEgV{YDr>BVo7N(Ly|E>+9k8NBrz{J)j2Ua1LOm^3!L&( zDuYWBOTdD8r8zlZi#$_6Ize2JJc1XTkywz5;Ckg}=9M70&iQ#IiOCQF&%BbtyU{A7kCW1sxQ6ofq>9^4ub2Vo0{hp@ysCo#9cGY@2%Z)Tob zPJSYonOFg1`sEkpCgx;Tfjr}%mR6iv5|CfakYwzen4RkA=;XwZWDKI>ol`-sV@NU% zPA&0C%}X!I2+hkZDGo?2@=YyCEdsd~mxN1dT4HHV3E1^6sc8&J#!wNE4^RYxON-JH zlT&>YOHzw66LSzM!QMkr3X2`ES5ahq^GouJFooSRb4pUNhzBGVCFbO$=3wdwD9S9( zO)SYw#*_+5Ez8d-ElI_c2`PVT2chcpdN|E8D=I< zIr&KpNyZ=!IGSPEBPcaFC_dDJA;}oRGBkrTjZEN7Vh(*?$L@^^_u@Pa)Y16&bYFMm(J5Ck_kz{54j6{geKF(5e92hMeO_4h?6b@TKI zaSehg^>qw!4f1q^d(+o3IM~H8#1SU%|!K-fMbxOkB_Sl*y%{(E}p?5j(*Or=;9EEqKo?ahxiAf3kC#v2KzdOcsip?1i6O! z`-FyIm<cCLfph$=l;lTrnqJSX(2vD5^PK_Z!uCBo$ zjv??6404PNat#jkfhV5;A4fk|WFgnQ;?km2kIWKS5eX{7z@@o!eqLE>QHfJ!NosLO zzH@$VL4Ii-s7i28g_L}5Mftg4mBHY$Ewd^WoKwJMTro_@Gp{5+I3qO&RP;F)r6!gj zD=I0tw!Nl+pv zGeZH;B*snIvT3|#s{)#T^oWTvE|3cBVcCgr3C=cJ|^ z;DXdtMDRm=6rNa=8(flDRDx!7cz#iKYLRn(DY!Oyl1njDamSeaTB z577_F5Ktkg0VraiROD8ameUGAJJ*WZe<6PNga7si5qHRn|8%58VDM12=oH zDj_5fE_+a%?Vef!DxfgK5GsKd5y7b?E~!bS>EK-MoS2i7l$e|i%9jY|xD+LpgM$au z63I+Om4rnkNUjpArEsknp@fj}$OHuvB&~v64=Eu*$sEc9QSrg4B_WjspdN3DP>@sUmS2<%cL72q zBp=>b2AdC-3CS-iEJaleagAqgL1|7g9vQ60K^=^yIXJb%H?g?b1vKCe^)=XXkRYi3 z0Y@w-&cN;hwdBF2m`h?Vv}A^fLygCjfR?Sn$%#2|CkLmNIF=`7!}-vn3sl}Cg*~|Z zM=r0ycDZC0gDMA@I*3*n59FPEP#xft4@rk$Ey1ZJPNg|H;H(4|fM|ji>5!5eQaU2Z z!YT}q=OIZ8+)z&ihX_NGF+|Kcv7op#2TjnUG$#jDnQv-w2C9fhYG!&yiCboBP7107 ztRVmsg_gH4E|^BD#v%OR)Dmc7hVxxg^NJx!7B1vgl$i_)98jMO)W-y62~b%Ktv8W{ zG5b{rQ^4IKxME0H2B+o~=NEx%SWtjL6how-!HiW7R08Mcl%-;oM2-Q7fx)RIkVt^r z4yu45Q2-ZmPc4DAU?7^n={Ye7?6Wpg_NHPY~piuAu^^3p-3aF)wMHtkpg$p}F z+APlbd3mYHCE$tzA{U;Ro$8dI0x9JnLLdj`=M|R}C1&O!S`TnJaDjlF#^ExM?2jx3 zNpUE>PlhC86h6o=!Jw{Jx<_hJNfmBsXg&=}EXf4bX&8q1CRRWk08i<#mJQf}pb{23 z*a7B)?1l94F=QZ#0OBYNNpOS~W#*-W!yza!FCA1SpjZVm8loHKB^=UdUP4s`afMH2 zF1V^dl>?P(;PDCU_CvIS#|W^wA1ncD)nORyn^*x>0QMg|EWtwHfCG)2z(*fIUWF9# zNYx5LEl3(#%;J>yO{{>)!K_3z07(Hnf>BjKTmTDfY;tHJjj9CdLhSmXl6Vd9O{{># zH%<>gq~XqoX6?&+l zC`JSdEYNUBqlFoUDoBty=B0yM4XDx(KZ8RA!*QU@49ex8pu(mL7E-8kppXD%Ls&k> zl!b;9TA~BjM)0vaP!)tSmm}^mWfPxE;EX1QB<(b7Lxb-8XL4HOvA7TO| znPNH*BZZ^shRdVH7N#;tbOq&?fGQMF@_svL62=IVU`Yl^35rQzc~F``QUEohATtjZS4ff|-7pzgPC=Ch zm))6p@Hj$}g_>NFnhL7#QG*9$FjNAE_o1@5Jr7Dli50<6b%Y%N_B>8kfMwBw2GoB9 zckN)7APT4Cpp+u#jMU_8aB&3UL5H_NqlT%)sYPX}!O2C5CCM331@Yi|$|(`tiwjCk z4k=1Z&IWZpAjt@#3N8q3wL(Ne(<~sBu%0-G4{qop3xJJ>34mRO!a>##mPIiDEP`T! zduoYKW?3qz7XxtwQ~;?HgCY*=!$1UGGK&j9evgOx13Zw0CWRP>fvW~9gQgl#&k)i> z3rbDKrUKN-fDZ~{sKGrxh)pT1-+|or#-#Mz*^81MihHjN<1B9A5FQ3L6m zA`dHKQ;cm+>%HGG*}hEi!ZFQ zkT5_Db^tm;+|mR6#naXu$(^Atd_HhS4yLfT)6|2Kb;E z4rLgFYdF;5N;6=0ftqHZJ`HGG217q+a0@>Ah9L_s450&W$dZucfHLHS79tQ8D1#|j zRX~Ch+*H9T4{pQamPYLkV>JP#BZyT6I8PyW0WrM_?>=BP0MSN9lZK{uZ0-k(feKWV zaZ6;IF;#$uFtMq?GMb5@3}s*wLl%?_FvmEtDFauHxY|`PCqrsKT&hsg4LEgz`V+8j z1G1&qRYGeP*vKb74akF_$m&oMCMXubqlf6Rhde$C$+z%sI6lK5iogK{8;>Nc9Gmyi zT?sK5kM~hDKwJ#&2jTM=-1h|C3Na9zBMBG?Rs-u_;`2LfNB|sIXoi9ffu$%&iwQ^L z4n-xj`9X|Qq>)sD4nR^1N!^59i!Dk~oC@(2p7=yj4fPm7Q=#gJH5T0A2W3EFf&h{N zu?7HYQ4C2d5anngg{lyL{zn~Wi zkp^1eeSZ{{5XWK*cob#0!W~5s#4K=#q6IsOGEi8eju<225kn#R;4yNjqqZL)wj&P- z<5ml)cHtw-xD`SSMT`@p`3uzrpguT!z!|+8fvOZ@F4i=Hst|7?!J!mSD#6eJNhV06 z&xFDPTl8X>3RZ-pCcspTFOK02KyV5K^?|YF7ED!m!T?z@w9F=CCR7v)|MJZXXz?Q>tI0dW}J^3QkLj4CD&Bp6Jm?CTiEW$)kfdHDh zL>m1@PK5}CP^+OM)p%`&sv#V#P=!PVD%7D+Em&(~T#f|?DlskwE5sf8pfUzLmWE|0 z9bUqNmN=$@)`z262w%GB1X?c(wFI>B0irIrB(XRHyq?S{H9ZqF6%~}4>}G7_l9`s4 zS_D-N+H!&-09pG8T>u+Wl$cwPpOXpRh5{bK2Q7>QFHwc`II$}Lt%Aia6Ofabmx?Y0 zc4$CReg$-88))dGv?LYcbR@6A0|YDsbs}_y3vvoWmIim-A>o0l9ppyH%3TbtASpun z5G#DKY6k}qR%;&qyrJNGnQAXDBO)FDy+=DFW@*0C7MP@tJugsp+XjAbxUg zK@pq}-i#2EnC_FGpIurIpP2_!QeI7RRT8mmi^u=0P{@ zpoqd3d?Kt%&Ph$oD+MjnMo|gff0P)X58i|W33i08lKhgyocKi0W}x_D$Zj1J?P-~L zDe-BUMa9s)P3Zc;@|j>ag13!;7LLVd=EWyxB*JnP!m`w|%w*6SOcWbZ^HSn7OF$ca zP=ujo7A2OWTUnF}iUo)$Xj4*1VmdS~LA>CceDETAPy!Y(0E@yb}7i%6nK^gVgV>&RtBf$ zIOpf&Lp=*x^XidVf?Pm>gdmwcKHfdpBQY-}Clxwp7$0Apnp|3xSyCCF44OoYPtMQJ z&P-)cDa|X-%u7*8GDd8u-YmE}6hWv`GK`6ov&0BC$RK>83MyN)^ zMNt)l;uFnSh!CnW&<-C@7c_-1K~$yS-9H#=;i9OD!8>j+6vIVP6+<`Op!y3Yh^iF6 zy9re>LKs~+bn_Fsa+olxa>(8&RJBksRE3~@Q>ePl28RBfPG2W{d))d3epRSb@HOw|Zsh|>+=P6zFzLQ;=K z0-^&E0BAa}NI-NTf&xtk772(B6I`ysA_381ic1F;35X6eTsp8wpaw0*t~%5R!IVbT zj1HU*;U$CO6Z zj=6UbRX-MaR0B}<7^3P$l|s{ovJDYUAF3RxM&zxEsM=6uP&I+`CS)@ssvcwsNa975 zR-i0}q6e!KL>FofMbU*-3Ze@&m!jyxDh1Jnn(t6_VU>dDLd|<9y0A(?beW@MWfWal zr69U2aO=V<1<_@RTNhR-h%Q6a;uOV+*yT`@24Yt$YRW(ohgbx+2T2A}!C{ku=z*sg z3_aLnP#uQYU5n~4Byor(@T7ty192EO8HgTuLc!33O$MR|o=hJkoG7e>p&KUD@WTb3RjOV4%dL#2?*&qp|mz3y)D=_ zQx%Bsp$P*`4icZ(o9!VwZ&KgqGdd?ZhsLCF-F|8L-7YTnb_#v?PJr2bY966^A55C#qx6 zbmEYN=!6y$*zLq23DJp~tkCSlA&Dh;K&xNDLsi%U2uTuRDQYMnNn;BnBx#6l)Q~`u zhUmsAjm_D}8@%Bq0V2brYyiiaxlvU>T!5M!QB`2|0v1=G`2s}+#0=DEMYRRu4ty#g zW20j&70u(gt1lkynB`o1m5UWws09+E{avYKnov6W!rW1!GL?>#XqUppT z3DJoflxR9}NMZ?|VE7pUSON%15}Tz+(vUzxwH8Slq8l~yB1uDZS@qvAn=0(u=*TT1$IMVW1!F@2e6xit_b2A)bxaTd;!!fg32JKp{5j^rV&&IF%3N$ z(T`Sux{aVRh-v82io-O5%CJWPwxbfD?gMwRVFd~HLlqFi!H5zTG^z})I6#BYpxF^D z#$l>~_!Tv}FjYYkIw4gMvkXx~1eak1l|fBI%^x^TBcu#!8fqI3r)h+gVTn_e<14V$ z-DnDsjR;C90&fyRjxo3xibB*4L@4Ufq#z!K=A0ljNl1L+kc8-j<`V2WaY&*#6=`1+ ziaU|Tku|`#IAQ4bKsCg4_t)hI}c0|f?lQ;_Ac8URgPxJ^J&07(VVBnEdX ziV~Dmh+PT96lhYzt^{HVUL_Dyph*s&DR`A&4HQslfL3?FcX1&X18}P_RpB!Xvd|U2 z5DTA)=t}V!3tbLHl(`sc@fi$RjRsyc3Oa`rd-8$>0eBew{3B!H9PNTsKxS*TyaE1zsBD_W+ZB4^#8nQa920}_q@XAQ&9y6@wVNr(1IFv1E zc+5jphsQv~7B)PlAt}OR6k@*{9;1*H;V}wha~vKMG1cNR7*^SW>u~Hv5hOy;6hi6< z^!f^`Qb@ibN-4xt=qLj`li@LyD5Y4v5B4y4$tmgy!B_(pr((QDLly&K4^)_mFg3Uh z0v9jfZF0DshNK3oL68tc+32rE0-y8HRpK!eWjg>qGtm^{F%n$JA~qdjO*oLKMpuc)Q23H^d~QTigwH7W zqHnxrA*sS@7&ux%Yvz%*Ib!uIR)s_w>4e>hm_|Awt;j=Ksv3_-TOg-`&qu`HsKl!Q ztE*9TKpP4q*nyz~t0kyO1=NZMH%{T(DzJt;K~2P21@<6*A7R*prU$!4pi}q3i^#FN z9wCja9kjd{B~?HKP--uT5Tx#d)>055h$?6Y3?c+kg(8G(9e5=;XauhkvSc0EsTgv| z8X>FJK?}E$7MCOI#;yR<2=oQ&m}X$8KsE%_c0gOYj%)%pX-FtRhYhhQfP@(y1rQUU zV}Q6#z@q?Sf+4h*i`xi%DxhZIaVNwhu%SyO6pC4dR6)$bnfXx6BBTmp7PM-_%aZlwmUtw6hJHey}*iFNSDQ0hWR!9;{MOU1$M?tP7hIR2N#1AnU>=1=R(e zEQJL(vMy{=$N>Rr662h+#+1aW6};^aITYdgATr3Bf{```B5OerhjMXpseS3W|D6afo_oS%IM*Qyiimnzu33V~Ru6Lw7x3sK*qC zsE4hB#?XL80y#KA+lEm*3~tUNi9(ErCJrQVNN{3`L)1fq8ACm$I7B@(fncb|6o;sX zCJ_wvnBvHug>RQePC5udh|#cG1h%yrsun{Sq8jQUgdvb{!4QT!1LS1bVnamHjVcOP z4Bg!iQxD(84RsSZu0YiZrUF#grKYA}DnWHARwWQqppk&xB8Vw?l^}Zued{^0PcYJvjfrZ_}BwEDzQk0}mO4{dT|sK*qCsE1a!80s;^ z?d{{89pVj~0vy~O9etdgoIM;e^OAE)Q&R1XlJYBzQ|t|M6H79{H^zbv2r0HV$#7tR z>auk4_4RRc@^W$jpNe8{YOmtrZ*OYgz+fNm54m+VxFo+QF+J76-q7A2-|e&x4wep< zFmqjf99(=nJlq4EoE$)9Kj?5C(D5Jk#vm)4-HPo^GgItMbHR7G$dU(70I241f#zKhuxH!6cIC}dzWP$EshKoA~IJkMcc=`A` zI%7B@qckVS-ZUM0<*jE5*adFLTK&D9{rr60d@!`;rWR-5(dy#v;Opq_>hBidi=j0G ze4|(z=teQ932+--oE-xEU7YxQ8#8G2nQ)TWXmL`b^2IRtn) zdU^QzV|ovMrV~7R5N`Bz@bK{oaCP(b$FQw5FFP;4JkQ=VzaTk3CDq>4paQPX+1J6_ z+uz;U)e%!)5%jzld()(x{N!wV)BI9|K4%B_0AE)(XLo%1O7ipVO^b6Aa}fG`99&%; zz5TqsFjF7GK4^+Zw$IJa)$ic0g66H8Lz*15Pj_*!Eilx%NQ1iJ?`z6gBPr@cus+#V-q2WL-5XE!Hb z2Tj_HuCdcXab|^@gMp)Eh@ZSsCtHd(#rQozCtKuC8t_&MwfrgQ+bk z5tR26(=(GHMGd3`b8&NU^YHTXcJhT}S4@3nsYRg6v%v`yZk>ypgO7iJtCy3bBRKRi zwH1I$h14Q@)AGy|gf>qHXTN{|cUMmj9NLmH)9o!&^HMSs^XyIG`kZ|n{Cz!q{X89U z>C4S5F3!wLw>K@$OGIdMbMWwWaq;!Qr7JnVsHn7{#NISNEe);~6!QoO-xU90^O?;A77N3nB!nzYMy9dU|_%ibk(a$FjR!0f`Nfy zF`7CD1_39%BG4ICFjWi;HH-`l($^7u5X*tV!9lMiwW35NHxI-I;ROr~4AXCbSO}~j z5Ribb?i>RHgZWJc2G&~;bu0`F4A|67XJBB^y~V(A1gef1hq^ij28Kts7#I?6L(F5x zp-zK=fnga`4MZh&doM6BFr2&1z%cI)L=`Iz^8^?e7%o87KvZHkFNlGG;lUjS2BW(W zRZKX{6J=mvaD%FWsKjpG6h;Py=(`LI@9#lWG2$>Umw|!dCsYkYC3f=?85kJS?!#0- zXl(JJ&B(xToCtN23=9mP9xyO)KZKZt%YD7f3=EGQSO8M<9-ofsxqMf%1(j5$ZtZc@d!wWL`B9>b5g5FfT)Y2?fua60es!7*3=DHWGcX*3s>7A;S2Hp&T=>kuQ2iBR9(wyU`v1P z3=9lUelsv+|ADB(l|Ql>7#QmRFfhD=s>2m79*hhO9Df-Y0{%hF$pHF7#MJ+dr*CJjf;`NoDaV`P<+Ni)xdm=&0bJ_ zlud*>P<>Rx$HM9q9Y{4ce@HMe zFtA84GRR1PR4_1rNNnmX85kJUBp4auq3UpzLwpPj47C!B4Bb$5xXc5Ui&G^S8DrsvJiE+;tf>Z?~-L?aFm0nNWYeDwPF*4Z6L)3BNPzS1pX2>%#Oj3ZT z@WS9n3hbtYd2Gui4 zj12M05c9ZkxX+1!f#Iw&BLlw*L>(^o>4DlqDvS)NP<6Py?o_yDPsFk@s` zYX&ljfdQBMK;d`ajFDj}DB&Xcx#Xf-2MfbH_@7rVFy$lNHsR|K?B!&RPw+^1~E$WUyD-(HZqb~{Ff>jcz+-1pj!k-^*^zj+{a-u8?P(+Q{p`D3*` zBZImF#5`R70QGzP9T*v=J3`cf+8@~B7gXLWJ25hxbAqVDm5-K##t)ns8Fo8E)Zwxh zb#rTj;~cpymrvhs$1&Iu~C?hMxq~{byic5cgwb z5c7wahpS!J#K^!9_A2a=OBnWT;*CBBLhQe5F^9uV2C3$Sw;qi8R3i!sgV$M!Z_mh5d#B*aug#2 zYcxb1u5h`?$iUzj&B!1W3sHy5JWzRhA(oNhJ5(J=HMZ~rl`o8Oj0}NsAQiaNDM(#v z93#V40_s5Z?a4SshTBkeAX9PK`!tS`!89JE0>8bU@r(>JiBPvTo{`}%0d=7KE11B@ z5DOa6#FH<>85kG}6BrpzK-J+&_aO70B``7=CF1u7NS#|EBf}&j)GbS7WcWlt9msub zNsJ7!pbXEzz<|$v)k%yD$D!(Q<#SN{-cDj<&`!qh50Jg~$&3uspz841y9J~s1;2Tq za#tjUk>LSU9jO7`Q;K;i6@#>g-u4WbU0e?j?UO&TM^WvDukYHaBg zWZvC0Mh4AvkP6)C6r|2JosprFfI3jUIyaq>K{5kk9)Gf?l zWOz$J9mrnBOh$&Ns)Ks~~k#ix?TAit(!hO-F7iW@MOJ0#S#n zod_D{{8_@tzy)eh;OW1eWnf^4Dr00&FNc_i%Y7Rd7#JGM85s;g4G#te23+l8PTu;>kh+bPj0}ub5cBY< z6Rl!oNF|^Sl%6ZA7#S`=)!|Fe@2VIXtg7+b3rf#^)r<_Yh)}n#nvvlb5$gDA7#X5K z3mfpX4?yaOQHL*_E$SEKPdV>haqPTDp-^&&XiY08xi4-l9S2zJZZpD*<&=7#J8XH!w1!H$u$A z)ourc^UOv@hNn<2ieJ-KMuyf_h&o*Mg4E4yWn_3sgu1`2j10bQgw0EAV`SJ! zKpiMtj*tW(OlfDO4S`wAXiX<019KNd9j^Gb0L}MwF)~C#)!}jj$}#mFGp4Y3!Oc_4MV-HZ&CM5ycSW@NZYgu1ugj0~1N`0WLyKi?ikhPgzj z+t|a%z}QRJJkef8hEyWdRrWG6oFqcs-Cjlp-9G&Gg2Kj0|guPe~cH2B(RH z?G2mA$gmcw4xhcJCo(dKPr`4WH3I{K_9RAzG$PbhO=4u&N`$(TlNcFTCKI+-VlpE` zFadSD7#JARCo?iAf)*^`Y1e_qHwvdPGBiWgfo#WCpM&N#dZ#cl9D%CC6(7E!akwdr z460K>CgEOJkj}us;5(I(VG99uAbamjWn>VZ1~Ctpy&!e!(-;}zh)`EFjges`0d=6} z`rc`b40 z5LUNi4kJVUT*B)1&t+sNn@3pPv3ZOPwetz9J3F6|p=AMKbypTJGITE_tnT(gMutg? z2&;Rvh>>CDV#4ZPE@os{u!OL>4@(#smMhPIoww{qedjmurF83`2&C_jQWU$%@QHQJF2y$P;Mn;CxO%QeX%zM0vk)dTXL>(^k zK=z*6%*c?v1;08_zjosmMux<#5OuinIau9RMux;~M5x=w$dI@lzq&OH3=A8$Gcv^P zz^@LpG<)q1MuyIv5Ouiv`yltd-O0!>cNat*K7YL3#mF#sH-2>>_r2ZC$S`vcVRbL} zFfvTti(ehcA5Zr(G926oQHRSPplqSKpONA00f;)#`UY(E7B?dU!_j11>N3y$#A=b-hh@~0RX7M_Bbhb!HK)IC4N$dGxOu)6i985s=E5LQ=y zhLPbX5$f#EGBO+lO(fwN9|F0L{Tw4h=Q+amoMc` z>eMeXGOPqmU=i@g`-_YWg_j81yZsU)gXLwy>gq2uGB92ttj_%kBg07|)bU?sWSDrB zuzA<7GBQM7!>_Z8sSi zgl<99;Y#-)^CE6BGH~36sKaM(#BD|fjypuCyTi!9aTmWjQ2vd$%gDfT55GE4J2B)Q zBLnw+h&o*3FCg<$?=vz8K7gphTsC{Qg`SrBSYRh{OUmU>b`f3471)t)Zwe&{=H{pSoHy-4xhchJ}@#Y`-op1 z$lh-s85u5pf~do1ui0lthDVhRfX^o5b(-WS5^^u97O-1S*DBGmn1WH|K)zdBHU zllsfZaPcoh9lrdg{g091`ag&|eDS9KpONA6f5PgN8JHN(gIB`gUQY;0_wtNP45t}E zBLeu4pa?^Gcz%~XMv~#?Nh+ko&+rep2^C@u#XL*4%a$qkh&0dCWcB5 z{OUmKrT96S7y`H;>TtCaLEX;}Tucm8c_8X=l|!IqyNbL_41RnNb-3II+6M82kBQ+6 zKSUj_b}^_ORwls2@IwHi4p({gg@J*=PLPSgNC=`1*LoySdR`^O#1JJ6QHLwNfYk{z zF+_It3<%p9=WZftGc8Dl##2C_&WWinraMebGux40Dz7s{^Ie@5)RJ zuT>!GaE%{;;x|&2iGfuOq7GNMfclN0YD^4%>JWAK>bFhmObl@v5OuiB18uKar@_Q< zR1=~Omw&w&85rcXm>4p(A?k4Db5Okf*Jff^uLDts%U-ZL9VUkLxV6wAF)TO4uMX7S_-@F=aM=i=4qtqj8#6IHHioFf6)qt6 z8JjRM+&96m4zvYV-;{|#!3?4fSGod~Lw#mU46f!7b-3bp1_J}bd~+rSa|?(%T<%-M zz`)RM!Nj0x2~mg3Jdi({Etweftsv@fnFmr=Z^gu*ZB1BRwKWrix(#7<87piBM}L>ggT^28QE-Obo$65OuiPVW4wF zW(F}a7z9Jq;R=_vpmPI)nHY3KAnI_Hlc0U16GNC7B0}-21KGPLl!+lY45AK~y&!dq z!HmZ1=)_R9{?)vPew2? z?1}`bU;y`4u&D!`J993QiNP%jq7LK^Z0bP!vGbys7=Fh>)PeS+V^ar;{0;S)XXzCmU3>@^phb_WF3UZFY!Xze!wNSO7bp>E`Xy$?Xb*G_fV7kHT zu$Xr{iHYGkR4p#^K=IC+%)}s>42e6qIxOayCo?fPqpQQ>zQklEhC+08Sj_8BW@4C) zt`5ySQ26bHsv+dxgUL(`XQ680{y;Mi6n?LvY6zM4JDG`rJq0aX(98p+Q&p%MLgpE! zFfrId)#7p=$h>H%8bao!r!X-TL)GFlZz5C;A@k;@Ffpuzs>NjZCo_Q)0gELeuF86`rBN3{Gka@YOObnG!wYba! zrN8M=HH6Gtp320q8LAeSc_8;)gsLHA-rZCthL=#axXc5&k39_%#)QlhPh(6QAYVny@4OK(PyzVq6hUrkXxXc5E-&Uv^LgpPzV`4Z9Rg23!Padij@##zq+34!f$}3R(cBV5i%tTj*#k}q5 zObmz7)uEXODi0n))e!RU%XB7&uTZtP{0nlQa0Vny2$`px!Ni~sRg2F&U#J>F=EY_( zF=Rs3;xZ59-*%`PLgw{nFfq)As>NsCPN*6}<{ivnVmJ#`i_1JvdVUR6L&&`E8B7e! znUJu?WgaL#6rpMenP-^E#9#|mi_1Jv{)mREA!J^DCKE$7R4p#^K>nQxRYS3>I)#5S_l;0S$i3>l@Y$gU# zs9Id+f#TN~s)msJoU@r2{Gn=bnFk8LT&NmC=2d1hF|nfEfAiQy|$EiUsw{uRz4E9I7YVn!p2~|VLeW5u_42e*+_{^(^ zsv%@vcMcQ7bf{W<=52+lA!Odc943adP__8Xdks}X$h_}4ObpDqkg&#Qo+4BYA@g)| znHVggYVnyD4pl?QyyRRahJ2`6eCG8+)etgoW-b%MQm9&d<{gHrA!Oe9TqcIwP__8X z`w3M;$UN3OCI-Pg!ttRGRYS-;>pUg~cc@x??n{NLA!J@*9uq??R4qR9W<%8wGH-bv z6T@bxT72eRgsLHA-rYPVhL=#a_{?L^hlDX9^Mv!6804X9ahV4y|7@XZ2$|=b&%_W3 zRg2HOa;O?Y=C$WDF-(T4#bq9-J+u+3hLCwj^O+bfLe=6k?>$rvA@i6Em>BpAAYqNm zJWzSB3spnNJj((m23M$BeC8!X)etf-zkrFM8mbnbc{8DE2${FEfQexvR4qR9&O_A@ zGVgW)6T@?;T72fQ7DB?9ka>cIOboJ6wfM}lhN>ZCo_iq^Loie=KJyBpY6zKETgb%F z2~~^FyyZ|egv{Gq$i%Q8surJlccE$snfJ1giQy|$Ek5&vi-?O4`64C;ZKzs&=6OQZ z5OQB=5feiqR4p#^K;>0ER1G2X`iqztW<%BDG7pr0cS6+=GVf>+6T?NQT72fchpHiD z-tQtN2KHh|SmQDeRQ{<#)ethzvY3g%6{;4WdC5>Ugv={1W@2cDs>NjA!Od`VkU;~P_?+s1I4ds2_%dOnWtF7#GngRi_bi7s2W1%g_kff zBtzBWG7l7fjZig&%$r!k#4s1C7N2>$p=t=3ce;d$;W|_;KJ&gp)etg|wUmiLuoM#3 z_{`IXsv%^aeJK-zH&iVy^FZSVnNT%^%xf%VV(5jc#b@4Xs2W1%9WG^JI1g2e%REs2 zeG64X$UMd}CI;Rz{NV>OPaCR+ka_lHObp&owfNkZ2~|VLyxKA*hEAwjeC92Osv%_F z?lLBZ<50Eu%zFw|L&&_JWlRjL<%Gje8LEbmdFJIz49-xs_}rHWRYSawdlTP__8Xy9-rA$h?o`ObmaaYVnyTT>%MWLgwjLFfmv|)#5WR5~_xf zdASu#43$u|_{^IQRYSXWm?>8bao+ ztYTu=3RR2GyvtBEgv`5N#l-L$surJloYjypCS;yyH4}p(R4qR9?4fEHpa9qU4ex3u zhH$7_eCCxx)j*A7z-3-zH4{TGR4qR9RzuYgGH-h|6T@MsT72d`gsLHA-rH&>hM!Qi z_{ZCUVjY}!)&NpeCF+hsv%_F z(HbU(i%_-r%zF=2L&&_}HB1cbwUDsJXPzol4I%RkYnd2qp=$A&7Y$WI$h`DgCWd0D zT72eBgsLHA-rQOyhLupY_{=*FRYSnfDi}hLCxjbxaJRb@<~0)Za3O zsv%^aa~%_dKU6I~_vJ#>5Hhc_j)|casuq`dp!WS@s2W1%ZLecuI1E*b%REr~;2~5E zA@knWF){pvs>NjzNpqL)GFk59Ge1P&I_iyI9Y}a2Ki;mwBM@`wdk?$UN=_CI;~aNLb@C59B^m zs2W1%IW{mc_(Ij^ zI0#jX&%FCkHH6H2-OR-B9jX?ed7>@E#fM@G6N4^PEk5(Sp=t=ZFT90`AsMO`pLvZ? zHH6ITZDC@V2~~^FyzNjmgv>kK!o+YMsurJlZ=q@knfJ4WiGj5h64v<4Q--P`WS)L2 z6N5EWEiUsw^<^Yf4I%R~TbUS2p=$A&HyNsika>$+nHbhX)#5S_R9>Bhsv%_F!&WAS zw@|hC%;Ro@gfSuWrL&&_?HYSEls9Id+f#Rbbs)mqx)7zLB7DLtI zGw&c&4I%R`wlOi>g{sA69w#?_^?#hN{J7 z9>{%_P&I_iYwcuWmI%ft{4Rg2HOQm7h2<~8;*G4w*!;xlhG zR1G2Xw)ZkI9EPgJXWm1o8bap1?PX&42~~^FJn=r_;zPNQi9sK#7N2>(P&I_y7umU;)?#qO#A!J_Z1SW=Ix!r#BdU-7N2>~ zp=t=3_jw8v!+)q+eCEkcB`!WRr!p~^Le=6kFBqzZko)4NGBIRB)#5X+6RL)gc~hq{ zF)W0t#b@4rs2W1%ou10Xa2=`^pLt)QY6zLfIE{&ccN!$D@tLO$RYS-;^Jz>B&QP`Z z%u9r-A!J_eG$w{hs9JpHO^2!>WZvRwObqLxYVnzO7OIAjc{isqF+7E;#b+M#bVwKz zGLL^c6N5BVEk5%sp=t=3=Q^ENsCW~drM=Ix)s#Bds_7N2=9p=t=3_jLvn1LI6cSmQHK9;$|rdD=6X z7|fw+@tGG2RYSNsC zcc>ad<}uG=V&I=eI6ictY6zKUIg5$G6{;4W`;wt*2$`2Zi;1BcsurJlGofk-nYVNn z6T?QRT72f6hpHiD-tAdT49}rz@tMau8xqEZ%oCi=#2^b*i_bi3s2W1%xzA=|2!^V~ zXI>#x4I%StXEQN$Le=6kZ#h&AA@erRW@6Y6Rg2HOyHGWR%zHVTiQy|$Ek5&v=MWbk z@^hFNw4rM8ndb>rL&$xhbC?(sp=$A&R}WP~$h_`3ObpYZYVn!36{?1ic?ah(F`R{} z#b@4Ys2W1%eV@a`z&sZc*7(d*gsLHAp6*;G21}?~eCCBi)etf-c`g$}K2$9}^Ln9b z2$?r?E)&C2s9JpH9fqnQWZwC?OboZ7YVn!(6RL)gd93r87zF1Ljt_mP8ban-&tqb6 zhpNTrzEr3hLgp3DV`8XNsCY^WMS<}IJc#IPBv7N2<+p=t=3cXu8W!%L`IeCDyw zhlDX9^MvO!F~~#J;xo?{s)mqxp7WU)LZNE$nO6)|L&&`P`AiJmP__8XTM1P|$h@ud znHUa2)#5YnK2!}M^Ip$qV)zbKi_bjK1;oXN;sPcHU8q`o=6OTa5OQDm0w#uJs9JpH zHA2-8GOu?56T?iXT72ehhpHiD-r)sI4CkS0@tOA)s)mqxKNm1Dur7pzH9qr{p=t=3 zr@xSi!5XR-pLvl`HH6GdUC6{x2vv*Eynd(}Lgvk0$i%Q5surJlN1ZC9{VCD2H{17BAzYH^td8sD7@ zRYS5;9^J^6EaVBF%yF(R4qR9+@Wd+ znHRj6i6I`U7MFRT@T-NYA!J_fVkU-}P__8X+YVJj$h_l=nHVlZ)#5S_6n-C}Y6zM4 zcQF$K=MqR*<15<= z)#5YnBvcI{^R6yoVt5Eui_g6OP&I_i<6g?dAik7ve3(Mj5HinkDHDS)R4qRDWkb~v zGOv6o6GJmpEk5%WLe&s5Z|zbhhMiEg_{_TwRYSNqsJ5&uJ^CmB6 zVwewAi_g5hP&I_iJGq>R;VM)uKJz|9)eti8|8gbNsCX{Z`P=3QUG#PArZ7N2>HD|VpLwoOHH6$3xQdA(7OEDXdDT!g zgv@JS#l$cfsurJl8=-0lnYVWp6T?ZUT72d`hpHiD-se?J4F92O@tG&Pnz;DTT+PH_ z3RR2GykMvrLhg%S&BTxmRg2HOPN*6}=1pDA#IO*m7N2?hp=t=3cX~Aw!*!@yeCB3R1G2X%-1k6I78LqG7q%hAQ7sDka?wRm>3$NYVnyjAF76s zd7IZTG3 zN~l_V=1qsHA!Od_wM-1#p=$A&cNMCJka;iHGBJFGs>NrX@H*ncPkkK|gE3SsKJx;h zY6!V6bsZB!Ayh3s^ZKD`2${Ed9TUTPs9Id+$uKZ5oQ0|(WZuhlOblP4YH^tdy1znr zJ#pb@yq<}{9;z0Xc_8=2Le&s*U*&oxhE}LrT;_q?w-~C1ka>sKGclZps>Nj zR1G2Xey(R?VBG)-8+_&|L)8#6Pk#dwgEdqwKJy}>Y6zK^x`BzI5ULiJd7$y8eyAEk z=FQ*0#IPEw7MFRT^7$lG4I%UHZeU_~2~~^BJdpd?H$uXgka^-8nHZFzYVn!p2vtMK zyugi246#tP_{^(@sv%@v_eLg$=}@)!%-af8L&&_N8<`j`Le=6k?>$rvA@lxkWMbgn z1PN<==4nFJ5Hinl6BC0gR4qR9lA&q{nOD4tiJ=~<7N2=@p=t=3w{{Z~!%nDLeCAz; zsv%_F^G!?)pP_2;nJ2gz62^qgQ{2qNpbJ%t&pdCa8banpZ)RdhhpNSAUMo}$A@ing zW@1Ek5&pLe&s5k8=wXgXk7WSmQI#7^;SldCpsy82q7X z@tKzkRYS52ytHW}i9RF4( z2I;MY!_RUn6N4wZIxOa;Z)IXAMpuV+KOLyNp9ob$$iEA>GBK=$s>SEu(@-^p%)7sp ziQzR=EiUsw_gispgM5Z|Rg23!Q2VNI8xun> zx;iZJv3eU5!*+CaXz>9u@9H)thL7m#u$afcorytuJ0xsyxet{7EVnZ;_@b-BVqW%k zCWdl!b!hGbrI)GOnHZL$tHWa6;q6Qe=h4++G4JhmCWgQ0>adt6y@QECeFtIxxf`Obn?|wYb7hhk=2i8LEbmdFyvDG3d@jBR6ch@)e!RU z1j69e;p{Qd>y55@gV42J0Hu$bq+pNSzFT^*YHK;~8M zXJY6?SBJ&C<@=c!HlwRUGY?c=U4*J36dyPDGci1cs>K%{%mJKmK_-T?P_?-H3ra7q zp=t=3_xT_b!+)q+T;_qw1KC56up?xi<{>5qQ>a>8=7Ibhe29r59bFxk@M}H9#4r(E z9a{K-(%N{Y(!Uw#k})}nHX-Pt3xvn6dykiGcoWUf%qBj4=m{&6N0=CL(bZuwulooS!*q0YXy$?3xAh1U!%1{?Sj>BVgo)uZx;ixTKQ=2MiRY6zLsO6^E!_)F-(Q3 z#bq9-{M!swL&&_{$Cwz7L)GFl?SXJYsZRg23!Q2LWT zL0tG*pI~BehpNS89w_`$p=t=Zuki#ELoZYNqsJ5&uJ^ZHLQG0cXlg_~EDl30=m)5*X9sxNm!)xbm@1RNan(A23jFfg1XLY*E1 z1H%U*)PdY1cN$ALquC2mr$~f4P&lh#sB>`8OU}qHHZy^Fjp2b71A`8lItDHV1_cEL zeHT|Z$55XTA5SMMB_B^GXWtMdR6&0q7e7Z|*I*?D76t}}j|>b9*$fN}x(tkhAoIhm zV*FDw^J2_41Rl7_1m<1R0VT7%ngfGpI3C zHZs6z-^}Eq{Nnty5(SMQO$GmglFZ!9s?5A}1?T+Sg3O%MB9JY(aDzPq0}O-IfY=}! z8B-z#GY6X)Fga{Ax_Uxg`f<7t&Y5aL7KJyDQe&!c&QDOPaAM2tb@|j=IktNNMKkwjY{#cN# zC&wp#0Vf`iF#$Z?P9l!o9wMLk1zl7GKJ&*P`ou3Nq5?9&et?&XAW**%)j1PEpGJAA7b9i(*3wZRhf`vPsB|JLg z1$?@XdGz|IBzSaR1}pdJby0Ef=)UaH?ZM$;?IGaNeV$){!I6LJL4E-bf$kQS4Gatn zoh~XCAjcSh9HZgVS)!uAuj!)Vz_00}5&$)3jta1cU^O1y)dC)^|4aNmy4^WES`U<1cy#*B|JLwIXpV^1w6XF1&~9oM8(0QQ`@66o5Q0s zTfn0`M#aFRJ4eOBqxpzJ{9yxl2!y~{9dK4ZJh6h3C?rHZx&t|k4|ud*;-7lJgZa>5 z2WZSYa)4sL^+1W0N4F;dmm|^yTK)vbD8fxHaQi_z7(F^2IXpTY1w1+(B|N$hVnl{x zh-0W@m?Jn+Jh~kPJi0&nbU*UxKIO~&#AByN%(cM zJn-y3;BoMUxTo;}pKdn^5B~KB!2$nD-^2KVr}AZ=?n{pR+j#;!mCt(gIx>RgFTU_# zKIEx<*wgx8i7vl<3#f!)0I72D>ArwNVd;I4h0grjSpvL!bvV-;jaf82dZQTA9F>pm z2c^9~@cOwk8Wp)v}PL*hSJLrmv9^gxrf8(03<2+bO%cKbf4M{GR~2IyGT%)^5rx~>kH+U zKHZTV{PGSAX|Sl`-+l@yEo#TDZ2+Y8DhRCG_6U1ssM$saK|u@?jjCQ zs_Cwh@aaAYN<4`48%i<1kSfs>jK@7M0bf>5&cy=H3>8?@H zKyx)}UWFA>juMAK&2vcI2TB_PpwN`?0M(`8NI5sar~4tlW{HZ!Cw>7_P_{w}+3o|o zK>+~@Z`baZp2i1!yFY*e+N1juI0JcfpYS;NQs2Y)1URUDx_^3f_CTuTgYON}T$F#i z^hW&WmuG-vu`mhG?iU`)FF_HXmDYNJzx6J-alywQe$u1)AY%plCl7wDldnZRx(_x# zWc0ZB!h`!DNa(~T{)m&tC;7t<__iM4pL)=f`2^lP3d|dp-WMd35^;cru^x>1E0C==1?47C#P;{|9|KT~sta^XG#^ zIzfefFOS#DNnk6^g7Se+FAt|P|8@~U{_Tg-ln>6f20l!`ru9wCC|Nr;s zrez>*1VvMCEaOg4;Q}rGq8LiF;kf})QvT*|X-6(8HQ`E2??FmQcocxb(Wm=>Pxooh zgAYL=&j}K7?abi>`;C7)kJAothEqO~=E!{>UPyX$`$>RGNk>E}=?D%CSk!9P4%&wlAkTUJX9KD5><;7bWIpN9$?e_CQUUIi^g1zm_OdvDIwwAz)elycfvb2ZtukLecj+QzVCEAE8F6}=k2tM<-ECpxkV;;;W zyn1acJ(Pcem6qOj;op7?DdIpL2Ipy5oN;=B>cegym5gpqr%oR(4o^rE2svw?N$cdP zg0_E|L_erAAW#9$^^<^=7n zf@Dc>+t$*NgMZ3_!yes^9H1)RqxpcKNArV!hdsKFcyvZ^cywk6cyty>cyv}MIEFx5 z#RZ_ga&OUp7V4+?s8W*h()@#j2xd3@m=4WI509<2xX zTQnFM7$$ghAM@!1bt(=%VDal^sRfrw%?AXHFL_*i;n^$W25N71#;ACJ8=%Gq(wvU7 zR5LPobo+A{-}Y!d3GOx=EXggXvE?&yJQ zhYgJU0zN7Z{DSQKg8l-p7y85d}VUL}v%1SLy=6pKsVSLcl_>#wdSZCR>`8P+6 zs%x(khfnuuNQ`FV_n5Z{v~j_sk;!$B*zdu={)ESl*Mf$^yFD*V z@#wzjaqyM2hw()Z{`D6;x(|6Ad?D{)dDwF4=wW;iBm+{`a7C}W%l=UR{M6Q14IKJg1MfXDF^x?NNh zJdAI6fM`$t^>;iDz5wyN9YJFwcRY-5bUX5Z#X&3qI7kywOuTgox02+gM0ULGX7jO{ZpL&R2K%ira$^lUQCg`FPP~w;7!C!Zw z+k>O|=l>E%-)%;_NGPEAxZ~X-7D|ACEj^r23e+~G1?t#QQ=YS)@)%XBN^B$ECAVH`?ukG1yS5#k8VirH9qkAHlmjRQDl6;1Evg#2@1;t;FdpVg!edjOwy7-3?c z`30Cfx(|MKDh%;}0H2=!%8wibd5G zi>52~Ah9PjL-b>cRusS zAN$NN=&|NAzkp}XXMVwe4WIc1Bes0z7j)RcuW^iDqu>Squ>+ytSq^?dk3IYXOgkb# zf#g`g_=#V@OMqXH1vLBO!y(|&d?W%~nF&BgGH-$tMRzq%XSGB_wE#ofaTgT>28LdD z<`TY6XC6?E0Ugfm4(6~vRHyCH&E3l))O_68`Z&Mu50BQ9rCc7#ki_wt!=?K;*gTI; z=FVykkK|w;4`yNh?JgD$3#uB`|7 z`=)`iPG<~gt|muC0_JMZ0YDq0~9pfzg)VPKns|gF5P>e1r?+g;MXiT@rhs1p#wA>$FEtj z!-ZedVTB97#$m^92M%z_-07m?(e1nq@qe3@V4hsVK30v_BKKoZA5Zh~;Z!S;z?z}&OfS0IhQ z?h{ymU*i~v4@$6U{B?)GV%^~!ATcoe6Tg5Nh=X547(zpu3xC}qkPfhRP$kjpD*$FY zc3+2!J9b}zv(xzNe!)48|6O%Jb5o$e1&w6zYaEA-WH|C`6o25?s8K0!<=1e&0Bc`^ z$D_|PFfd#Kr&3T+dJ)V56-*Eo10w?ige3@Pslr)?aF#us<;@5)FB~qG3}+RgT)ZGf=8&E-zYeC|Mcztz`yMis1e`z^Z)<<3}upz zJ8YR47(5{Ls7Eg^ygG$A<22YA-NiiJ#}EGC=zf3juK+0XcDSfS_PQ}UbkwLsl(2R> z^St&1WnDyBbIh5DT#G1cBGNjU96GX?KxK_f_i@keYoKmpcMNDMHb=z+R5*Y}z`@=I z%?E7(*HR#F?*g+xwbT}fx8cQ8A3UqifM@lqpc5n@>Od39;Hc=1{4LNC$pns|4o60iE8`D?7L!1n2U;!yah@>) z0|V4~AZtOH6LlWD`Hg``au$bA_eW5}qWc#()P1^-f|_65Cp@}8fCaj*dK`BHE#hH- z)#skQej1%QDhfW`=X|;kfD!|A_7)Lz-6bjk9^Ex65#VNT++oo09;DQ^gqM5X3=9l$ z;PjKsz`zg!W+BoKs5FMpxD)MCXwwPJg`mMpuwOud<^bx7dw^0kK^Hp1T^LPU7aGGR zVBu+xm;gjL4^)ZMG60deb%G-r$a5z|HF^P7kyRSJNbKwwRaP2+?Pmmd)1X%z|kQE-yM;sjELDQI^%nQjd9`GJZ0%)ZL zIGfCbmo1!(3=Az`F;Lb)3RlpOJ_jgZ6+F6`JU}Z{Zi0Hc+@O*1{}=eTy?D*K7Zk0a zMwf|4uOFy}hF8xnDrQhN*p;nsK??Y%9_HT$S+DZnO^1IQxL<1la<>P_2ar1Aa5%ia z%YYY(Ag6JFGBmU?nf%MK`-VsN$JR>~9KG%=){Z=Nl8!r!uvZD7{_}YcWhpyW=E44YWFx<_<`P|Jbomcyote;}}$I6+-OTxBnnP zc(Vu8NC63eV~StnIEanfXu1p%0!#b$mdSWDzh~rcE&30dnW<5608P&XxVC`J12mV?>7pXQF9=$80qUu^s0e)G7j#tc=ycTZ>2x&k z>GTA3J~R$HK!&F(JUZuq7bbaj{{YSRcjl;McyxOzxbSa#1ug}^q2bdVZs61FZ{ceE z-w{0I@7sE*RLHZN5tLC_T`Vk0SslBNzh-vqJ_Z_jR$~H9h%kD>hxHu!w_Wns0U!G^ zz5wd}gQ|T`{`HqVyAOGEZUL`k@;LZ{12o158u+{DaqtCbaOyI$0BB?iML+>$0!YGze898&8feB-0o0u_04EJlVWY6=y-T-`iVkS89(1)AXjrenqkD}C$X_0< z-%5Brl0!gi4I0fffNJ&VhA$2D7XVi=KHcC{2}!7+6EUIT0Wii0v^ZRA+4_FBNdME@rO0x4R6rc6r^s*VuUT^Yhq+zXa&~;+ZbV8ObsSj z-QdCm>oi0&!5ZyVOt8^;(1m@FhCJvh2}na8v5>V^Bo4eaZ#h{ag ztNVDfGY<=-(FQge)X{+W9MSpr7+njt{vc>s$mm)yTx-F)z!?MF3K?AsHo6uJG}-`K zzB#%UY;-Lcbr#f(t_1@v9Hh-!ux@bS4O$BZDaJu-!TP{r-~t^ntTnn8Y;-Lc+UnTR zwP46={3u)t2FZx1Yr!B{a&#@&=vpwGYr#Nk?^zfqSqlc6Aax9Y?CvR5>GgmN3LBsF z=;Q#+etL9z2tenNJUTrjJUX2NK;wbYhe30ukRGEJd>j>AZ-9nI9YAaR4LrJUec~4| z^635vA5#Mjymg;P9YqV~fURx##4k|HXpA8xNjIl7o+R z8NykhnIycB3C*LVtGKX`P10(+xd$fNrwc<|yAzd#uaA@_xW zd;nI?FHi>BaERd>w1G%acpO6;heQekkfjO)E!98@!!jL$%1vO(yTdFHOdAB#0l{>E zF+IBTJUm+8mh$iV{~y#Wfv!0)H@#CoFqFRK-+mdU zx%GA(p9^Tn`6^7r_!5lO?H1wDeV%`NTujS>P8XE~{;7x3I%8B)z@gYsqmsw~l7^0< zHq@vjq4ATE`7O6m6{jLgHq^zTa^q3C38-9Di|bJBt%F$$PK=Bw7~(wLJR`0fUDaLXMyJYA?mKf#a_W#U*RmotT;D( zK3fdVQiQY285tNZfG1Yt;4IJ#_*t-66?~?AI($%hIh?f}&N>cfU4gSetKlK*O+LfL zn3!Ng(}Hl80-U7}XNALAkaeL7F8tdMcqAY4IQT%ohx>rX!B+;J2OkJ{a$f+K2a52q z^p;EfQxAc~*bq}E&2KaucYw}}0H-qWwq$NB^T{6Fw_w@7JGcOrVjHSUkW)%`a0N`J zJGchUoDAACjNpN0J7Bzq-Jqp)C}vFV0j=DCF7xW13|bC>P(20Qx9&aL;0$O+s5d%#{Kv z85|XYBs>9tswjwSZ4oYmvx4C)(BLOT9cU{ug!LJ`R~|HJ(j5ZcSgQd}Ny2bVkj?ZU z1s*6WK<6Dn7O21jK>?DQJi3LzzVPTq)P3E-Ab+C8AHoRGZfS^Nplx#ymw~K=m;>5V z&&t5S0Eug``#@8W5M^jw67vq&?V!PC@DyW$NB1vKECv`~0#Ceobbkc9zz4Lkx%(Vw z*W|$$3jEtIfaV7dJ^-yr@;vy$05qHD$$bH=6tW`zAZV2_|Mm-@1+NER8i2B}2RCRx zw15ZqMc8@>et~ickM48b@dlsx1?n|WE7EfqGkV=1A@qv$99$Q8J`cK%0W_TlQhoq2 zCkQ$u05aRaFHmj*w$=w^tqa@$aN`AJ2SO8EhzizrhX=qs-W?u-U`8OAF$iV?jM-41 z0$M=AKlKnO@j#}#K~+!yXx$WO?l=Kb7J%v@ND2Zqg&-_YDu=K@r41yxfM!-9tZncl zj;LQi%RwQsPvNrf;VgD|4I%|+>B3o{rYc0QCtNHB&Ps!`3gN68IIA7b0&R4Jm@*eG zb`)NhfRihzyisVdVu0i}SdrNP-lzgEK^v->kc;1jYGza}3o4ft$^`{7QVA;#Fa6Zu zEKm-Gl$W3_?hqEJjDfH~D-|IuxLX4}y1`|m1Gs1eWh}^K8K^x>u)P_K)ZQ!xl_C5B z)e5L-_B>`*0STd}SwuMu$tsZc9XP9;=RbBFIn^Kc0JU0=Bbxx)778)}vf8OT*Z^88 zCx8;811K?qf-L}&7-iu}2DGdS5}k-j2(&gCB8F(I7sGXcmIpv&K`T=stVM9yt#B4- z9Xv!9v|Jy;`VNvaIqjb3sEm;!t1wYxGch3pkkVqT`;gGK`9jEON{h}T(Ab?@;vE1Hqagq zNO0A_y$m|X0wT5&E`}Klutprn#h_*!eivhF&VAw+*bHsZ;nh4D)Ut!OPVltqx+jBL zbqJMMTXf)H?w$-fxCNnd38J-!o}fVea!4}(v~&>?E>`exafP!$XQx19Q{ZCda8@gv z1v-raqHZBvY(1Q{7tT5jXWfLezQI}W_<**6AihGhr@Mno;JyR5r9pWGR9@g|$RhlQ z=;g!hA#((x8&R5q5(=m|#T(b)EDmxjdNklq+V1dx0WC0u1TP|KBLW}p9cZw1L)wiP zH=s52j*hL2UHZ^w`tf7$_gl2CL{N6x>0gL!){RD z230kv#Q~3VSWZCm2%`T2YX3rNO;G;@5@Oi;FNk^;(VqtOUm!Xf;dL~k&{z(S3-o}& z7243S0M)x_?!^}HpyeeH+wE}Lgw`QIGZoPb0QF-a?KQYt9ehBm5N~;QAAl72pv`;T zKk>G}K`9$ltKm=CptKM6DM&e}jDiELe`aecGrQA z(PDAz&XWKgumTbGNd5}i9XlViB2>WKqxne#gk) zn)v_!f6&TQHwTaI(~kTCWeSe`0(AzUOhs!adV&XeAr9(Ba-3s#n1=^+6`f;um=BB% zc4mS{_i4}WumG^*Al(tq?yv}$n(h$LNn;Q*9UJO07(BX9!#Y}y4Rx7l{46wnHX1(% zjh~Cg&qL$qGl2WMaQi`(7s!54Un2pwHy`DwK2QfF0JMQ94m=(LY7#+5I2mALodxjW zKhVSgq{x^EFDQ1wS!dxa#Lgqo{!&O0gjjOR5AUCXMphx_A$r5Ha9PmEDnxHHTx>F& zH6PB}1ZRO3)k4fW1sA&pXFYBj)HMm4~(8$6K%s+J-^WBUo9T{oc3<_?aK6l4TX1@Uke=-5+8Od`4`pd~C2G0=FO z;||a{SzssO8ghm=U141oqS|Ks0>z-l8HR^Rn`|P~P;1x?>aw6VFAdDHF1rgIL#&9uEk_8W^DtLhQ<9zgJ zy;Ld!t|viB547MO+U)@~L=22?TS#Hdt_eV9*TBIDpIW;P8xsVjT5vLkm~|KwG?3B` z6buj+XdD4Dp98W5yrTu4@<^Mu#Wib7bkYMk7d>7PJr``Vxrh-WY?%*HXnTOqegK{0 z>Cp|{XdR+r1KN!U+V~5u2|zobArUDLk6d_&D0Elycy!O!049DJZNTtJ70b-1V)LTUzq4i^<82vefNMa3AxROoO~F@Z2O zI$TstAxwh~P*daJD+{Q<%zE89Izm*;;r@cAakL2VAlhG`gHH@RAU@*&B}J%#pppO* z0iY=^qzFJ{Uyzgdw|xTp@@B6)V~H?4QGgfSgV)xhIn0q5hk^FeGdpycF#d;}0DzpC zK|_HMcY*psNbUj;dqc*&adllm-h)KkOVETOw*D%CfdZs%5WYbIG>Z@&c|?b5zx?T0q5YOzMJ1b#%O+_j?sK1G)7a-0iJ{e z_hP}R2Hbx=hq`#`GOWsPs24$+1_U2K7XfL_HXm_-OuB&Pz9GE^P~8HF1H|BO9z5@0xPVS{ zgsk4@7XUA-vj7+3yd2mXV`2SI0yfm^|#b$a0S|40jqT=+FyR1`d#k3g3ffe!G2 zl&Odui5Qzj%&#Nbd7xeoBoA+axBZU8S%_8DPvByRz78LJU{3+ga)Glz`5!VE0*_N- z>Wgk?&=f8I_GljPns@`~ns^g%7&O$V7(#0k7~cquZ;Z@GUlVT%l?0V*kkJlsYXDId zfmVk=sv^(|EC>rUJpy5YI@AzWFT7C{DrkBK76Z_r36WK0y)ABM0%gFk4aqqHoS;C-wF zaB#u8iUFVi^XSIeRRraI=-j3Nw7aMPTC*yFn%!x+W>p0i;tlm0NF6cU>rg@0H$n1d z6g)5G!dalC1<8k?^&t=zXl*-$g&2|83DkVpW(fOiB)1at3N%gsC*kAUorKMdOa0r5Jh;C~O^xB~Jzxdb%oPb{urN>S3_?9-Zvqdh!#$0K}Nicn*(be;$u+c78z?M$kS6 zMuE-}Mu}#39vRSyoS+c$?2d+BclS8{Flagrl6OJJzkL7)D(J8&$Z+&;_~B`wfaU;& zF=+metS}}yVL-yzN2LHBz@Tva#4p$bKCcQK0Nt<+IiLfWL7wwyJOXkdWOxj;oDAYQ zP~i{p9Ma@1=%`1Kmq14&<4@M0rU>YsL2n7rnXU@_f(+n9BIqFiP9uU23g9Fn=mART z5&VJ%8=yH$7BcR}fgg+w3g3kJctYHPsyg+g>D7ql+3&h!H z=io&i;_Nd}B>>U!A6^|G8fdccW|;we%K+jCSkPuSh+cRC28A~M*bV{R%XA)A9)O}& z15_-5;ucpS22ZP?asU*q;LrjkN>FOVQiOq;UXTERI}D_9nB}Zt=p|6N$V-3%(AAVsWzH)t0EzaSH&r1gT9v`(Px=)o`O z1zF+Xc#!{DMuOyzkN31WJ}*Tfjlpjauq2cVb3 zAh$z`9mLj7#4@;(@YqA_uXzp^L#+L0g_pS^a2Dt^N=STzZV-U59O1G7a8^8=RR?Fm zQwS(Lu%r-BHUq^O*z2%-1aD6W6jy-TQs9K5>7xQl8AO-UAfrL40&FxWkHCTyJT-?l zs&eexD$u|Mf88fg$qtGDP-X$S%%kxL$nB8eMywA9VqgY19=3Su`NVuJ&0?_e2xr|oul69aVB2_(i4O_8_o%9^=uMLO~V6k&rh zzBj>R^b9;kLDzCYVji*l4|L7~q_HXtk4H5)%LX3bpgRX3dQ0H4D8(DR?J(H-r_dg0 zud_gjNT)jwtb~J=Y=BE)Az4TV z^c`jg6qKQ{)Dhs)30yaHH-jtA-VhZ9P#YdJ5Khfjgg`f_kpgW@@C$ZG3X zQ#9-W_vFyJPN3c#EIV~m(33-JS2XMa_u|kj13L)XPi)u&E)vmHfP)FD0@0HL2V}z@ z@Tdfu251`ttfG4g_*yC??|?E85*O6kgEmM&OX)otkAPwhQbQs-hO6KuFKFW-B!__v zg5;sw@Z5dyaM{^#)+#vbB%Fnk zpRpuGP{j#K72t3JCrMCR1f@gJxEQFe)^q`-O=r@(l^~-*O$o5kgqkaOt6_MC)PNks z49k$_^V14ST>D70tn*Jcgu!x|tPJYD2Q}Uw8)QfoC6ic!tx1iy;n@0QD3gdJ#Q^ zJh(bgW`!g(L}mqTr-A5L2Um9%&Kj|+K~TC4;9QMZ7Z1&&&2LDX0UKTM;?Yg))B>rq zE2PE+#C%Bp2XAo)O*dg(gajJ9f#ey)(qS?a0K`(9Hep%DLs|kG9_A0TNTElrheZ6K z4>-tNKBx%b2i;TynRmvyH6M8{AH00$6KMTTcQh@|@;QJQH-RswDFCe&M?W+Qv1B|H zUN?iTAA*z+)o?MyzI(($g`mbdL>-pN=h3i%UQdW6Y-m&p(yMI)$(@6#*oQ0tfn`MK z0uY=T5nQf-4!r;^z#62hLr24yfq?Oh$WDNzIjTI zmez2w1Cp1)B|GGZFPzoWX#WRvfHE|sKs6xFkQ($E1QO}Qx*u_@5omE_0cb4p6Y|PM zP#Zph`vRz$9smv=(827Wb0WLrctB&TU@_R_7J364ED2iQ2U^M}P$tlwr}2qjpiTk4 zkk1=3VWJJ2Fo_{BVS<<|;TJRkE#0eO6zI%>EZqY+40PC5cbo}v4&xUn1Dyh&<^j7R zJdMBj9w_d?BA|W&=9xOM^K>9X^oSkYh@;IT;k^UUHN=pf0O*`f$XpF*Z3ATQI${Ft z6I=)45FK9l5jJ*k7P!JfESUyH)h}>VeS}SXgCYwaOXV!!4W($Yglj`7Xz8y2SOexN zYP4vD9~Z3P(R~gaR^WJsh8{!&l>CquLwhtIfu6?;y15w=D2U?=5vOk>0uS5T=q`9b zf!1L{mfC=hCV{X5j)d${JV7G5}eCo-Yw8D*<$14EV+Y&`HnG-7nZu7NYx( z*b!68z`$@Hyf~{99$=s)Kaf-jTIB#?fmS&{QWj`!1B3-lV>v3Y_0-L8hH{a~FHi{==fZZaBDDA)j462C|Kne5~xOjuEp$8XkkeUdRO8EtfK@|~bz(@q6 z#3%X`9&j4Pw$TeIZSo5ggH}?3Lj|fHsipvzb)djPA4Wo?NYH>1Bvpa77(rNw>H%~z z1w^bKo+2l~S)jF0kkkiSdIn*Eg9%<0&@nUf3zUQMGDb*HJ#T`JyN37`RAoX~9^FFV z-UO4EceEb_8W1FAT@@tIK!qFD8M9&652E9#HQnK$!(JfiF&uQ)2b3v;*i}fi<6SD@ zg%6_0K^%JoTG|I$7cg23L05m^O!1)MR{Dk&qLpbt(vnC}mm9QtsJj@nFVp}Qt&F{<|792hY@KFbcPEgTY?Ucf(%x{b4LNZ90E1E6Ns4Hhm1!-&*lK-6AjP= zKd7AzK4Tk}OF+x!G0!Lm?JYrXql0eS`2?;oA#HR>CIYw7QSa*kZG-@2PEf8xKBN3R zXjvzCvy(=NN&X+nO~{JxXk|dvuriLFJwTDA(dT znFw~{AGF8=m2}|J3OwilD$vlzAwWV{%Q%qx96+NDpd)?^AjK1+^@ONpKnEp53LwN` zmY@@AAYx<|JCG&;*x?j)nK6d(phsaqFM$O45hc|MhNE!~7-0u$g&II6Lop7&1od|z z9b|Zu6KnetoX-gKq2b+XWE0?b-hv_wazHGo7Y%BrLeE@Bo%w|Hgb_2Jpu-g*jZ@IA zzz`O;nNP&dN#gctVT2O2tqEx#f*gyrsR?SdK^%c-s^K>Z9CDCy1Ih#)xPa3FM5`0j zs)bmExY`eHDP}B#BDh2aat#@1X*OuOyE__fMGw|vYGPC%@!|nL1|M|hHN=W80eCm?mz?BrBlXlE%Ocd`#_t}eL8DY z99)gRd32X+bjR~}GJgXNtGn=TtK;c)W`rC>=YY0;&5`K!Yc3r&jQ`zvy2IcXPP%lv zf!H41VG`g(_5Y}6^DzzUd;|WT%K!iW`*hc+ICO`5z&50UT=NM2rI}Q10N-d`!TT`CBPJ^u$rfAlhp)A4+ zzz!LOsDd9ddJX0luu~(f^F3;sKoQ^px;YFqsi5E(7aw<65}wLHgV&IuMbIJ~$j~Av zs3EBlv>_OB_$X-j3?c?P*9=m8fOg43SfGvUkmE{0J7OU$e?|rdh+c5|1rJgCbh@ZW zfG5^Hx<7h$-|*=61l>FZ4ueggwQgXRM>qWbDA1imE*{TF;s#}d zRd>hnXvax3zh-Xy{r~@ehB8UV9m|;*7(9AKK^Mz-^zwozuu6|)KZ)+c9?X0Pe{gh2 z9{erP5y{ly$mr4SrvM5g^wV8I_tftKuS(KkU|{$KW`Q!Q1PcQL3j^rPP>AP2qqAEC zL1Lg1a~GHeiuWyG9iS__c7s_&27@DP#&M7bLlA9)!F7lPLlkX;Vd4-8hAP?y!=xb+ z3^lY322fE5DaraWH^}8XQ2^ zmS~o!7@!O`Xv4z5r<>iQ`y7EQ>cDMo(5@+xuHrykmI&5QwSfw(w{w8*jE0O>fW|f< zqZOcolOSUh>F@!cDmV)?MgbYp0bMfyS*-|KF9jLP0G+Z38Os2ja0nU80Nt>FK9+$y zh}Gy2#Gp6=6S410=>k+g>0s20F+N z5@Sk?3=G7_m^vL|Y=mbDO*#beXr`cMrqH58j1Bip0jeYvKr{c{w?MTpxai%a3!AI( z>1GDcjvN53;lsP~wmY6jJD$j~et+;-KR^0dzdv-Wzxjv)Wc?m!ngCL9f~rtR#R-}U zL{yxR;uW-#4^q5>HitrrSJ166#22p`qbZ5vHV4*}WHRhh5>{`Tjtp;tP5=Ry5X6@Y zh}Cppjr1=jBH+tm%HZv>S_TFNNPz*`qW~!|Ks%?A3Jk2#Y)N`F4=ev0jd1_#lkR`y zg)elkB0)RhAUze(1&5Hz612q?lJ-HjK|<0#=>7q;wC?~~v5)AR-vZ~}O)8`Z0B9)= zXk!sErzC%CZ_21EhQFH-S#ABQYOAHBJ7)>Xn5zKRHoh6@S+(04)WG529?jEo zpwmUgfPdSB-FZ0c7Mul|HG>o+prQhj)qXd3JwQ1EUekPz-PLA3~_`m8i!8^UsUY=O!nNUS32bWk`$;th1~B_!Uy zfGP!WuUP{%-msQYN+Y9;!s>U>1{Y8WGI)SajfHF{p<-2pcbY|ufq?XAt?8U>j3)$vY&x+e@q-;e?T@QQ0|XO1MCmT zjsnX4;WEJffNTk%+#gN@><`GAe#-sfJiz{dEXt?cACm{zACOh`l>1}C0Q&>7ES_?I zOc`K*K-RTW?vJSh><`Gobjtk!+W0$=?JLO2a?1TNZGhtgvNW7>e@q`>e?ZoDQ|^x$ z1MCmT;%v(OAqeva2{Tr7pW+5BpN4c#h*$-NwVO4QbZ-u;iCMQ1?tey@|G{e=`L|zy zT@VDW8|c;HMjUn$1R5X#kD7xfRv;bjIFJ~4&;&HGLa4*N66Pb~Gw!fT(W^(e|Cf$% z|1Tlk|3h(N18Z%ziZZ{0FMg+M!G~DT4H{L1j36WKp8~DagjDaK^~sRJ5Ofn2Wb_d< z)kvr?TuWJi(7i!}2nCYY>|w0CSu=wDzG4LXeHmqbAAW_va>@c^NEZTgNca1&Y7NdK z-T%m~f#H`K=2I3RLn$*PKv%ItmwenpTJkZA^iY7VBLv-{DA66QfI7VoI+-4AdLJZ& zKE03sLXHCn6KF8!TL47YXhA#F0yOfQ5r5kBX?5YAc)XB~jE zC|C!8C*Ed{#v5p148x#|H;fkO0@7m(x$qrIWetYk=aS)fx_g-N@zbqjcrzHZV0OT} znUr_1wu~IVgVyBpV{8apPkI21(2Wfk#lvROgKpRqjhjY}-?8VLb)*NtXubhg7@M|{ z9(2Pd-)tYje%~;H{l0Mo`+e~U_WPm{?DvHu*zbEt_xrFKv)wns{l8;``+qO#{zq=( z4Zkt_1C#{_-G`N^Ig7o21p9s02=@EV5$yNfBg5|=ojEE3pbgp*;f`UBpao3@KD~ts zpm9dX6i|&yfM++KPdDUB`b}?P!Qu!y8~Qx@V$DyE`~u~md*2+p;|X8<1|CiTT~p@K zTh7xh2huCx*qyHdGNoRj*IA~7rIYP-I^@~M=kVz*S9mS$(tQ}4II~ChHIHOIk8XB;K@-ML{DL)%0-ZUG63uKf9^Icnww8kq zH*xHaH^G=%#5yquaxv&gKExe{Y5c|aKtt;Yk#0H2v78kgjlUTf7#M40abCg&a=b@! zIQVc*anRwMQlN{tqL@0I7(KiBKy!x)KD{w21)kl}3ZC8B8lK(72A~b%0iNBD9pfG2 zAcuAuz)uLVg6|PW+-3+mP69HE2s(!h!b0p8KhD6w0J-w)J_7>-_8~wV2wjyvp~xzU|Gpsz@s-r1(cuoHCjEwi zEX0~B@S!UpA+lFx@e33ycyuGh9G1AGR%e^&b{*r!$Iz3xWG%TY*hkZfH z-gWR8IRk19f#cyaJVrpLt3hIf2Q@}OnF?dw!FyO%^5_ ze1ds{UjTHb8K@W#plvZgweTW3kK4j?ETS`oxQ85c?*SwiBaSSd1lO?vo@PJ?FG5HCWBg`wfuq21x2qeCHMSK$&c3%kQ*V9bVk1)3cD zO7&Ah`{51HS#Z`;IBN_1VChTn=IKp%m*yTw3_MUJL`0V+jP%qwtd`3}jBx*lk8uBo zju`(7fCiQ&K*h8IsH1A&(OU=^;NbJug=23XJrQv%R$*9;*&Q1}JB zLDTx5_ywFLK(hv?_%(f06h83_x~MpSn+o7$1{#>=FTTM6QURU)3Q>Wda@bG}YB!wc zhaDGBtv*-+;&u@HS4m{S6B{TDA;W;6kb?|dfd)GvAq+Z695U#P?PddFhN}We4`HNa z2hWR*zyJUL&rl|bbH#*5GIg#pC9`)5Dh9!87a&C-(gm(DqzAz0l^H}_nGr;KxDM%r zg{^*#Bs~m3i>#paE3{q}fYhkIkQ&vbH!^^<8WpuJZFc7ofahAO)SwQanRgFFgqLTaQVddgPKQ^XS3z|GxXQc((hFvRm$!gT0o`7KR!nng+Dl8&cD>!uy~z;4H*6 zD5zfwQ3qO$24R8vr4Un|!gVl%X0pMt$_1a@2HjExX&ku`(KtFqdaMqsUhAb1?*DTm z-2Z1rxc^U+?*F0Km;m+DF&d~>MvmXH9{_Nf^Z*z=007b&x$YD7CBoDzde^9VF7}5 z&8Y{n=czT8Mqp$TaY(!ayzdE0AdrX$ZNY#?!?y(_f(G)ygL|N1KF9!9E=UYKxCdHjLui1DY2K#E4kO#RCgMtR*Vd<_(FS8v-551(_Kj_X1%~{60dO5u#S~ z6YV*~G*B!klE4v{0*|;%kQg}PCKECF16r#Hnb-nVis*;C;h6ZMe`^R)IU+odm;(!i zr)1C}5RiDUf(HobI0(p$&`QhzK`(E>VX=t~Z{&j(p@1d?yQ7IbV3BI+A5ubiG#`N$ z4%p_+E#ZOT3uh(3S)gfA$hjosKK${dHZ41y;DI};s=g|>rTd?8{CFHa-q_YUu z$nYj8MT1j6_9A?U6?ol@hr1nl4m$*UG`~^s=ynwF=)UREdZ1LL*8|d=Fh1$g$>GuI z0m1?vy}S$z3=AHf9ugj%&H|tl6`~J|!B64@9ofUlz`(%5;L#bQ!U5WUhk9bGL$@Q3 zEC05izTF=@x?g#8|Fm@EsL|ie!N9=K{DY&0xmSk4qnp#Emt}UZNQ2|S2h0Z_F@wX} zqm$jI*PR2jvyR25*O9}co82Qhp2MSiiV7%ydi3(l_UIHj;nT}=qPs^0v{t*j1-$s% zvC~t)qq|3C0s{ksXQ!uxV`n6XW2YmJYs>!K$EW**M>qd2MsP64 zs2D&Mob%|u0cKjq^MLF(;Gc2;wC}(I6bJE#!AzqA5DsVq!d`G~r^>* zoCdQ%TM>?dS)fRg@aPOt5eN+ncMNe11)rj@|0Am`(*XIL1RA%=bC z7j(Vi(HpvfKm344XXy)L5c$EU`?zQ4{stii2G8#E9-XcS_yt`zfGv0Imh(w=-Q&~g z`k>d@z`>$ipoG=2lkIf@Xz$6PI%~+rljg$(@ZFpuu-%-9gMD9%qwMPhANKp2*|Yna zC*(k10oN1!0=_r+1z8yR1zcb73;Hk$@C&*yDmXT?8TfR6a_M&6Csub!K1Tuha>;?_8CG93?92xm6cka%=vWMH+i`MKLbPB{{t@k+h2g> zI|LaR4n7cY;eO%Lnd{)&`h>p^v{%!o`JLE9TVyRUk7p9kCO(|y3R`|2lt0fue|j!*o8t_!+-7kC)o^Z-%(>+gFUe5v4J zd=nI=AfE9J$8HB64>(f*!4yF-C7?_Y1-1p0I$95uSa)-H7~kynVCiMdEeA%rd?8rq*cB4Q&H%H|HD0Md;0fjiI=x~g6jERqnJq$Vl zISgEEfDY~22F{?M+fyMdP>BX9SwX!Kh#2VHgQH+|e2feXsbIaHjIdLYV;NzmA}?lS zU^oj_x0R8B0isu%iGd**tYab*149a!^^*y91}rl(1H&({4oPMP21qkVg&B5wvJ*4x z%w$hy1_nrj$PXk2){)1|z<_8F350nxzX|Z@-U!NV;Hu>te~T6q0|O{0)~F^Bw%b;m~2(;mCOKzkowWB@^7y{M&bfvud}WLfZf1{M%=PGA7vCO^h&0`L|#9 z=stJwkpTB)ungq5W{>U~m4xQk%tWL~Zh~hhs;8f@6n=hGU0=qDS{Zh>(LOL{`AD!$Aom3Sw$OL?s+M z9F!rVAf`4%RKc;sK?NcTV(LIdLFTGLL_thlh^T>Mhl3hK)WGB5D+|XC2R)C2FD)E9 z0@NKlJoG`eT?T01aNJ=~{{?bNaXfry9pdO((26!lS_S1B2n*Y-!=Od45LwV}J4n<1 zF}xQ93V2A<9<)~rl9E+GySBioTaOWzGwk5!iYLN%cV&Rofh*ozkQi7;FML#BCL;qw zI5?+)e4BjH zHU^0h4h9Jmb_NL}kphqoP$L9HgYZ;VkSdT)2n(cB1FTb#m5G5hg$<;L0j2{a3sJ|z zz`y`u2{14)Ky-jqRfE+-2#7qY*?JJOAqwE;qN;)D1K9vE7i1=~Zz1xidQ8CPGO{r- zuo*Fd4zykhG81qmk<`nA65_@{A>*TDeMgVCTtA+M%>`=0WH>s>6{MN31NYB zIzV)Cu`qC%urY8Mv0><%0oMg#fpocH)dd>#fY~(@t_#8f>GDC+#SXXY7)%QT1H&x1 zE(i;xD+sEKk%0@ETEvlb%!ccLus}K@Fmy;D>6io80bzl3#6fkShJrbguDNhs5Ee*R z3RYcEM}qavgX@B@K)SLZxcO-yFfc5E>w>UAy80lxL^&8lOL!SXLwFcOO}H6Eiv%BlG=dH<1<@e96s{Y> z0_mOt)~(3P!oZus%)pug>Nqekz;uD~E<`P;bbzo#7#J8JxRA9bmywGJl$XHegfd7e6fcAO6v6_z zWEn&Us2oL0d!U`gP>l@B;W{BKkj^y_omf&4=vZf%&J}Q-5Ee-1CWua?(jF9gFdZx5 zIv^~NjvaV)tb*%+us}NYL3E&{8qjbY%%0V7T@V&X*Aa*=AvOjfw9sgWX=7kuSOeDy zVS#j>g6NdtV34U`W)R8%l^Z+^T$3RA7)|>fERfDS5S=ny3^Eza3?eDK3?d=i3?e363?fAWU~8k9Kzbo~ z1Kb1%3uMA0hzUA;3_2C83@RCH3{oY+3{ojV3{oM23{oZn3{scGzEeOt z!s1GEQ12UR=MK0|2n(cB2%;0smEKStAZjOE7lZ}UC4p6!D3V>f;JP3zkS;kSU5MJg z3uFlt?}qDwut2(0kaQv1BcPcis3wLza9t1M*N(zO#(Um*Bb}ERe2sSal)iyUTE0 z5Ee++7Oc9EOS~&^T@V&X*DkEOkX?8ct_#8f={kT_7qSbl!F54cAYI2Gx_DU`cuhnY zco~HlAgvEW1g)rmrNtX?T@V&X*CnjFK%+Ge;JP3z zkgji7b*+JE0bT0^*L5Gv0_pk#(FJO!pp8l_N7DHKtRB>wg0Mh3nK;3%DJ(jFBI$ex z*9l>PbaFs+5(v{raGekqNGBgeCxI}14A%)^fpm&MbUJb|I99MQ$Y!uI@Tc%I@SE^4 z@Na^oBhaaVFo!>Zn*d>fOpt<@pv=XfT*1O1j>Uukq_p%DZUTe_GC=`i0*-WyJjU`2 zt{cJv=~jd2#*vQIknDaA*9~ETbn8HLEmFK}HD7D!hWR$a&??^n1k2n(dE0jnb*9BpLbWOsl3)!xpa9t1H=L$1k?2gt_#8f=~{(V7qVS{;kqC!kgg3_bs^jJ53UQs z0_oa@RTpv?{)g*=ut2)@VAX{jh74?=dK6T*LRcVOhp_5GPQ8q9T@V&X*9n5UnBck~ zERe2qNV*VX0LY^v%y3;07D(3>th%_7awQ8~7lZ}Ubqh%sV&nihzF6V9AS{rs2Uv9> z#}^x17lZ}U^$e>nWV_hmx*#l&t~Xe9A=||P*9BpLbbZ3A3)wDCxGo3_r0WM(UC4HE z!F54cAYK0;y1;FA2?kz9F?gF@7Af?(;W{BKkWLmZP)b7||3YrF^T2gMSRh?o5M4s- z3_>YV3_>Q73_^@z43IVx1A`-yoxE_}5Ee+c07N%hIgD&4A6yrN1=1x3(FOLS7y~b( z2;7gzMhUo`prg%UrH~+8 zH-rV!tpU-E$4()*E(i;xOAn%pfFFh7Iw35OP7{buJobsebwOAlT~-iX1nd)q>x8gC zIvpT7@z^H@*9BpLbh%;Gg`6kE;kqC!kS-srx{&jP1Y8${1=1CSRaZ1D(ij*RB;mRs zERe1Uth#n1>5_u$g0Mik;;`yMt_P&yx*#l&F3^M(GXr|wL#_v8;JP3zkghC(cFDqZ zL0BMN1z2?<+a(9r1z~}7l|gjba4^_Zurf$wurY9%2rzJ6f{X~fLJAdmxNZmwq`L;9 z8$7bb${>WM+Y70bP=M=(ut2(-AiBXR3^7LonqPxis|eQxVS#jYVAb^nriFolK?$x4 z!UE~)L(+x44pN5eg0MikreM{DoGw-1x*#l&u31=hA?E>AxGo3_q-z0IUC4H+!F54c zAYIF_>O!_l9j*()0_j?VRTr{d8gN|@7D(47th$ix(uC`Rut2(YVAX|emlj+Xgay*I z4@nmX16L3vJtDVHwBfoSERe1v5MAKW2~Gxn6AlLcAV{4hj+7R4;5s2Jkj_&`I=LA5 zO*r8?L6@k)5|=JqCxivkc>$tRgo8mOg_}Vngo{DMgp)xe2r_?Xk7TbNTsMRT(tQo0 z8%K|A8_Z$`1_ph&ZU_sc`wm1mjvgCmj}puP1GsJo3#9uIL^n9JP}im)j{+IObwXGm zoiA|eL>>h)g6o8^Ksw(+bZW3OXk>6P@S32uWzNH#%fP^34A%`|fpmX?=mwVp;{xJ_#vyOyN2qERaqHZbEG*Gq^4Y3#5w;q6=&v>i8w>UAx>~U6LJngexGo3_q^k?7F66NbU$`y^ z3#4lTR$bw+S8(!xBuRKU^1t1=2MKt1jdgd;nY*gay*I2&*pS zR&^j;7lZ}UwF0XyWV?dkx*#l&u60;-A=?!U*9BpLbZx<^3)!v^xGo3_q-z&eUC4HY z!gWDdAYBKr>O!_F46X~p0_i%2RTr{d;c#6L7D(3_th$ixih%2aut2&lVbz6fS0r2) zgay)d1FJ4%yQ1K_AS{rsdsuZL+Z7Gh1z~}7J;ACAIakKObwOAlU9Yg}Le7=3a9t1< zNY@9fx{&i%99$QK1=95mt1jgH6%W@1VS#l0!Kw>63=`nGAS{qBCLU}pEaWgugzJK^ zK)N`v>O!_F39bvm0_ozzsteh!WVkK}3#3Z~t1jgD0xe&Mtc!=RK)R%`>OzjMRJdIb z7D$%@R$a*Pl?K-ZVS#k1Vbz5kU+HjN5Ee+64pv>r{>p&sg0MikjIioL_E#oc7lZ}U zWr0-}a(rdMbwOAlU3OS?A;(uXTo;4|(&d6x7qVSBa9t1ErRQUut2)O!`w2CfUj0_j?XRTr{dwQyY!7D(3yth$ixs)Or- zut2)DVbz6fS3O)8gay*I2dge*yBgrSAS{rsLs)eo+tmoy1z~}7oxrLKxh!si>w>UA zy3S$MgO%Hc3tSh31=4j3t1e`JwZe5lSRh>wu>w>UAy8dC+ zg&beqa9t1ySal)WH4Cl_!UE}v!>S9}uGw&1 z5Ee*R3RYdn@ihmo3&H~F%EGD(Ilkt?bwOAlT?JTmA?M|Ja9t1w>UAy7pn!h3v1@a9t1kWh7D(4Sth$ix+6>nPVS#jgf#?GF zFPRv)Oc1lLppzD0BdlBCIw35O&R-Cn;Ql45&W|u{pwpw^I=6yZAe{_+*xESA@wN@D z7PMXt!UE}H!>S88-nPSaL0BMNJP=)A|Do>kMqb;$1FjRo0_hZj=mh%@RVVV={+)20 z5Ee+M1VksE{_`%lE(i;xOAf0pi}FAgay*&gjE-^T?gU1AS{qB z53IV7?K%Y41z~}7`C-+CY}aA9E(i;xD+H@959Ut3pwo{h3kT_K)MpJ z>O%I{F}N-W3#2Ozt1e`J9f#|Jut2(UujYdEgay)7gjE-^T_@qXAS{rs3aq-2 z!|)Vb7lZ}URfkmw>UAx>~U6Laxuwz;!`bAYENpbs^VhXW_aaERe1VSal)$ z>l|Dcgay(y4XZ9>f1QWxg0Mik=3v!@9A6jUx*#l&u0>dNA;;H6xGo3_q-zCMUC4G_ zg6o2?K)Tjp)rD-=WwjrCeBFWTg0MikUSZXR9A9_gx*#l&t`AssA?M|La9t1< zNY^*4x{&koeYh?N3#980R$a(p_yDd8!UE}H;>Xr9LJq@+a9t1732n(cJ3Zfglq775G0FvEL;kqF#kZuKtZm`{$d;N|g`TH4MH-rV!tp?Ezwi{Eo zDw5sL;kqF#kZv7_Zm`{$E`Nw*_Y1gg2n(d!2%;NoH>Pgn_4Y5}x*;r(ZVQNRu-%w0 zNA~wCxNZmwq}vXn8*DeGZe)MIhUcutPF}7Yzz`9A`B8C!VD57LJSg@L_P>TfS7ax z$)vY%lOQaRNdXX(2)IWO$&`0+Qy?smDPa&(h;YwRB$M95O@gpMCdEKZLTmdYuUY&6 z*9BpLbR}WcWr386KEicDSRh>)SapHUlZ0vf1lI*&fpq0TbcwMsh*hvKaA&YGux)~z zZU#DT7pC(wTqlGD(pdu0iKC=IUX%R=t{cJv>8^t476GLgb_Nj>HU<$RNE{=VrC;GX zAuN#22BLI+gX@H_Kswuq()k^(6T$-N>>*0$54cVU3#4-rQ96IZbwXGmoim8i`3tTS z!UE}>N0iRraGekqNaqrwbpCt1=6{JD4qY{Iw35O&TT~L{14X& zVS#k+Axb9$_}n4TxC4X*(s_s|os4ju5Ee-138Hi|!F57dAf4xk(#Z_h31NYBULi^+ z3tT6J1=4v7q7z(laez)T1F!D2gB1u23=FJrT@V&X*8{LF$hnT}V6`wEAX$hy&}nKA z7U<+Qhz^jdT(Eiw0g(rVJcI?({S0C@L;(Xx2Z)KP2BI2d1H@dAnaHPaK;%*NyaAib zh-D=_=)_Tw9U#mG_br44a?vMKrs(tf$W2r4KlZlfZ1YD zvmv1gat$b4Q0<493$g)XF38M%1k9Czn+q}zhe$Cf zn@BP!GYT=te2{t|aY5_=cCrE@L(iL-rQ|hUgS& z21!Oi2B8N67x)hF6mSP{flq-PZ2c%LqD!*aV{VHppNo7KHl}!UEZn1<@hF z#UPQw&ma-P#~@+C%OFuC@Rc7eukKaY6HYvkeSG*nm|;d>Zt*n%P7Ug zAXUQ6AeF+!AQi&NAZ5bAAQc2jndKmhpja60YX}QuYZE~eV6FujA_6x7!UCDlL6Qlg za1$UbkO_SRO+fCKiNQ^Pus|kEftUa)pGue*SfObKbWT6W)gUYm*9BpLbj^b3lILQO zH<4nH|0McB_<`UB{sX)PJPF+3p{-tKkX{IufSUkeflOEcF#%++2`}h8Q*e%yhiCwk zl5kxR7D(4Jur6>)11+m$U|@jh0Lensfl?ZT1xjfU9UxWuU{fIkL>`nPAS{sXH3+ja zSit7Obbw?b>QK#w=m4p@3^5i=LR6xfy$NDA)V&}bAf>2kAo@TyK+FZ183D<{AjJ$2 zc~m_+;O2sqfP4v}QPn{7fy{-31<1@Gq`DX)kE&-M*jz?-Mh151ag(676Ub~3mV(C& zgarzpBVZkhEKCe686dM57+^X;vJiD3H$YgRn1Sd3sX{*O4k8aS0m1_5J_RuwWHkfA zTvRm>)gT)n=7P*b?sY@tQT1E^o6E?{#=vXB%fK7N`GLKF4V-#)pmu>MX}E78ERc(? zfpscMvoT0N;bGu8!VN#m8Kx8DLx_4%eGXxP{0h+tQe}n|w-9-d>mV$U9d{shfSka< zzyQ(#Vxp>ns0R5OVlK!`cLL@<0-MVSJ_$pdLC8dmL5NWVei8;K+(7n#ungSi5EjT) zFCe;+PJ)KDEI~SC;W{8JkdAjy9V`r7DTtF^TS zgCM9^!pOMpMOhd`OXL_hOk}|62&N0wRERE6YK720jxu21LuAfk7L|H|22mKv*DCJRqhZ-FyMM zO%&vM5UzmhfUrP1{2)4zTGpVE7?5fZu7>M?us}LOFm!;fdIqTm;TpIO2n(bm3Zg@X zhe0NVhe0NUn?c5ei$TT+HH4-h`K=ai3WNnRB>`fJA`gRN3J-%~2seYG2^WK+k?aTQ z2a*@W4?udZiAXK9dblYN7RZz|h$*r>46^8bw@cvu?*Wi6ULd)p0d5L}1u`WEVhV}z z-U2rX!UCC81Tl$(cyEQ90%3tnseqV*Bi`HKIv^~Njyi}A9P!=>*8yRHbhKdTK#un= zxDE&lq@xR>gM@hRhMNLmflQeIF@=P9?}eKJVS!AU1~G+%c<+Oo0%3tnnFBEe+^ghc zkO<*rkTBta^eUnAUdU_c`r#%(SRfM?K}4JV6rfyf1rr4uTZN?3G+n#m%&Js| zg2c+4{KS;t%&JtlT4XUB1q}s51sfX$BNGKphVmqaq9lg1XF%YMy)t{6sJhgCsRPqa zDSz$je-dK(r}6Totan|Uy+x+$l-RO!X2xsI#v}56`xxsF7A0H#MU4aw0m>% znye3;OLJvr&iJ0VP>Sgb>)X!)ewBAKtbV?VUn8?tS0Z`qw4E!9|Iaj6jFk^>*!@xL z_uXWAhiBr_r}ySJFpIwyO`lLS+eyr5#e+$27~EG&WlXu6B`x`S;<-6#YMp|5ueuGk z8m`H2S@+#fF>Yh7LiB=7fmt;of?@H}HR&5|EnWS0cBol-9jrIu{CMu#-2GV#FKq7o zow@DB>l^A9Kj+rKXqdT$pNao_IIr#(j?3 zMxX0GIU?tD+WUTGeGz_N)G_&)O~osYSpIuqA$!)PM5dbl+8@VIWV)m^q-Cbiw|Noi znpwxJmECT?uycO5SS4q(#rhWBJDswpW3DHN>e}p zJV`D1zjLWS!!gOX-@CW;t=z&j<3n-9hg*qmUYg<0lhxSzIX)!)-1lkLbM|*T{9iV( zwnmnvtGMiXmv;H}o|rkZ{c`y*_oN&V^;u;ZoXLNvFM9*~EROm$^Jl%@<(vRBPjyAF zQRy6&MN6zAmGxd^F2}{cb7{Na{E3HhVDd0Nj6SHw;IFb*V+lGve}BZ$GJigeaQkH| zwhPIsV{)&XrzvOmi#Hlq zx?tjgg@0B*{bA#IfBD6V<1ZWcEq`sZc1Dt8hl#;U1NM)XHvap4^!9P-vxi{fFxpk= z#%t|Wdo|AKCkswFbyqyi`QeYN;ooc?$`+@pL_Lv~*Yi0rVLAKd(A5cl?iboC$Vb~< z36u`myP`UKZ9Rj9~d7-;}VC-6QW`6L#NT*OURsmN6k%t?X8jh z&(-d7ZsWesF#1?bp~l;jKaD??o;{t>`%Gogwm;t2zTAm_)}y!Ssxfa~l-b0l^Ggn% zU=ZJOulMb1HgtEu_@uh$b?DW*3naFd!}Q~#C1m-fHm7&;``dB66%As$(_3NB;2W#u z3-d2}x_-D^Cg15Xqe0$#fwM=~FJ6huT$nh_92ku&zTz|tC!4411;G5tY!_O*VbkpO z$4#f+sk(A<224LL8fG6M8kc@_ahN*@iTixp!}?kMr2sBklEY1*Q}$>$EPtIS)j4sw zxO1ao#J=qj)l4rsH(p&6vtvadOdOqdopAn|Lh-5px3=&fYG5+#=F{#!84-P^)=+VG z!VI0?N~eU&g70Zx`s$&faQp79RYhld>=Pob9G(`4mJZ^IbgM?DLLE%;=$9^4|lt z%a&g=bqbK)H0S@E<60k{B^AxQ|4PPT-K@L0%CT~LTPi)~ot|-Zk+vGInZ3@ls1+;k z_6_Y$A{!1r7B$Gt8eB%@sZ?g(TzE)V^4@SDLBEnQ}R@+j_=>z68RA0n)3W>r>6fq z`E8-H`q@7p^S-|1tynWPEXjP$@tJZV#V++c2J$+2;kma~eRehYdOhohP=1@t>f@6S^27y7$+l7nNFt+dPq4S|)>qQ+W`I!>pj<+^7qZ7opq&W@WHc63(S)d#Wf z|9`6wojldxm0RT`k1xs79@N`D+SGlUjU~qZM)u_JN=K^Gw{_E*#c;FA+c!eyun$MUoyFgRi@v?!W;?U6Fg$^7*aysxBA@*d@w zr23?N(x*qMrzK9W_}FnV&}`F}N$!&pO0zaNdbvpJ<~xrKD}sVTHy=`7tq@Ss)bnSu`thtovmcdO`RkNvy*SoX!n|E%XTz&%xjxjwhU&mkn}-fCCbi+76)l~Ob~xmMmjUKrLp$+2lgv62JVyqm8#1+-5u zogCu9p!IXzpE#%2jWMCcw~J?at#|k)TJ%MKzO|qwbB_5u$Hb^zWp&%i>!O~Dcx`>O zr|$^Yvopr`$dLrA6UwP46W+oBuBZ(&jvJ{vCR#gjL9T@{Hb!t(*6iAG45h z@$JjzbD4i)-Mw9%ruyRFcbi^27kx7XAvuEPRfA z?S7aXi?+^_jjvBfir)I@lP9OF{^oNAgYUzR=Z1Sjb=StNU9>&+ZS7(vlS?a-|Ku3P zZ5Q~@87$-XtJABnbvx!m+hZQtv0rfH40BNVaYbU;q3kV&HQgTZd-vD>XjS32 zpM0RS?5jmp&9tA)`*IJ*&)Y5Hb8wUI-EAWLA#)__S@pLx{+_+N=}g3Y9}l~bM@JVc zm3Bt|K6^cdf6d;)+Glqrzl^d!E;%Ld(T&q{)n7gKU#-7OK5NC~bGu|6BERfqSk=0% z^)K76Gpx3gP8B+3R9_9$GoEuN_J#QyUiNv#)}n%b6N)tz>{y-#J*Z(`J!b{O`4h{# zR!Ge_Gv`Hm_T|z{z1uYwomMugsf_5fuT>QrgV&{koWf8e9?4i%^?&JM=_d7GKEJF} zcy=fE!o%aoQ(o`&?bq!{|Df@C-dES_>s>!Qw-kFYm)p53zq4QDIFD|UjK+bOH*11J zCa%ype*VFne?PjU5^q#Y5H>u`rlcr4@$~Hrw_dk<>4t|{7?IJk6w9lGD)AHAcnf@|An&{Q3;<2YZ$;U7A_5XIn2z+m*PR!oh)mMXYig+!gF@ zX(}m%dzHz&&})bB7e-CJcH$4~QQEI`Z|OvLwW;eF~%G`TW_} z8~(G1jrzN%y2#_yq`=v}KLvODt(+3dvc%#N=l7TuGhZ&;6`#)5o*Q{z?(%=DdnK8d z^U8AFlM96(U2%yy_F3q#=Xr~HuaCg&i~n+Oij`3OGFW_cJy&VjqHO;Goe%Sm)VY?< zovC5JoYw_roLn;H(wCLBg~u)|6j?7CC-+k4UFrR8BGQjeKTm+Em)q-gexvxEa+rD> z8UIJwv$SisR_lDx|GducP}IKn+x)J-n|#!)!l=+Z<4|B*oE8L_QU)$$NTulgC+@YVCtvreWsSqv+pfBAEsX6QM3K8 zIjl`EdB(i#>B_TSvB3Cq!?>$zT-%qy(o2qE#;!k~XK#YZuWnoQr7G-WB#eJjRsHru z`vzVZAC^8~{8#Zj2Sp?}gJAr&Ny2?A9v3md_=o;X`Bm33n-9h(Bo9kpF!j<_4NV^% zcie;VamnjGik@A0E3M?j{b{y(>5n*I@dFFrIIV)BxSJ*Rg}=`)3Vs+qY2v(V*&cb- zOFB+}D*YuQR|zxUNBGKWrJwo_V0>75hVgOPPpmv4|D%UDF7@j^{r)61```>%dWG44 z;OW}>p3}`1Fn&V4{PP31r|&#>^jWB9SI+v=+*%sfLk#&+u|$v0vA5`X5OT$iHTVdYi9b^WC}ym#-x_=kSQ zShO?j*CAy7rh?EA{)=8q_a}8H$viec?|A*qv^=4#O94mrEPfCoE(mi!tbBvH-{|X; z1S6M*@(L!u<8xb*TKJ=*#LCmkeAW+%d3WOicSzs3HuvtAy^}9q&2L{VoFl(S z{H0jTl|!nq@Px$|jE^h4sU^>#?YDkGW}_9%y!TJvHXT)&+z8_nk|*RobpOEku<{31 zp27GQo!dlHs#k6%)_y|n!zIrhdX8fz!^1nLPF{1pvgPbMn0>JPqj2px)0@dfYs_tz zIKO(rmxnIT!D=FASX`g?_hRJY(|X}+(dCT;r}1ewtGx;4^eBF`{^c=rc|!UL$-~UU z3V`6W~ zy3Xoy<1Nuw)*3w1gvA#ueZtEB&bXAAPcO1V(dAd|=$oJ8GHcsR|MdGObO>DQB{`F+$K7CRlk^jhdgZ% z*WY+>-M@2!ObUz%uX!6*2|fQ)088Jv+&^5(A6))}`JYhwB_vPC{Gpz{3HhIpJf-1- z%Y21^Rko|k*)J{Jum9rfn(tGyq3t#6ofl>a`~47&ck2o@Rd~q{>wjwMmDWFc9_Y10 z;+)~;e=K39t1aKfY}wFhdzDZ0iAMao=erhPI<#!g#t?b4H#gFpG-3MBwFa8*dYP*T z(+}&v5z>Fe!>4NXzg=^AHRQgeyg%PBbW>Bn&FoJd$IM+%G%rOs!sI`F*X_NU?;!z` zufDXgFP?k%4_Nv{Z@*rSKC*w#mVfa6?2(5*-BJ=;1o~AJcQrB|4mr7JS!nUEkSbVs z!1_mL5C33#l5%$rtbIAs_-t&KO{acdnG$mPVxG~C#4w?GlY^wWT^HxhQCV`?>|y1f z%r0X-*N35th$yxx<>D|KYyc+kZB^x~yf!Bq!#J!P}ZPq$FS7 zQ4x`+cCdO^JIH48#Ik4%goUI*ai`vbF%L>;%bDg+onyapLZT36s&?zydv*av| zBvk&*jq%*PZR+KY>1^)~I&12f-vig}Ki~r@W z_*xWN>SO9@5%KO^@^RMtYd)K5ecZ|K!E>!MCfdqXC53f)?UXgI3fjK4tt*;U8=f9F zVQ&0aCw&&iD_^qpv-20%KHip-l`j{{U=TT9bn)j$g1kv zsLb76u6FhPu{FoP2Q6Rrdlz%Se1ovow0V83Jh{cC2|aJkSG3;zJB8Ehxy|+K0tv0w z7QDWStmoEnugZ6SXtmwx0o(SzLu@P5%-dk|A=5gSI^FBORo5p!z!744D1uWhu+sjlLiMzpOD^rRvkNNt^j5 zadPWV{&nr}JQMrbR~}d9sQ5*_$$fs?C})f0zjG@j`Q?h)ErL_x)8?$J+L6QO>m2@P z#|2YwmWxHFpZ>RoxqrGqs`;+sO|84P?%CwT6hcFin*C5E{7Ubd>Ld_@I6ZamxbC&#Y{ zx;mG^Avr&{Krb_;vXO=H|9{Z77>Bgf#1cINg9(g`4N#G?{2aa96wu}o28f7WN>OTZ zNm(k$6wtbIW=4Gw69wy~q$ZW7s|14tLA%(P8IiZ;fJ7M>^omjvOQ376L95)E8EZhs zfi4~7f4>%@21J53SAb}+8W4j)nE|q09w7p90chhELd1%J0lX0yA>z)!zyLbR1|br{ zz`)?a0P;5kGcbUh13Eqdp$4>NBLhWE8v_Fa=!hDGnn?@{;6t+!BB1?vpeq~^A|MaF zKr!by$QO(dn?dRr818~L&Y_5WVqjnhKoMbNWMBYo4g~1{VFm_%Mh1oi6g6^;3=A14 zBB0~53g9AoC8-r9D!F-}zyyUZGvfjflYxQZ`?s7w4B)s%Rs-4&&&&wg1P)T|_FC75 zfdLd%$ZFU?eq#h}Oar+pqi@e*Of{f`IhYwiThTzQCn3{cV5;E&n*-Zw{ru_wN=)}~ zg4KYwm4jS1Ekdvp(;P0a8qh{8kk>rA6#rwI!wpsw!~l_bZWv$>VIljA2dqXEWC#NT zL)0FX3QRS;U^So(AD~bvaPum_RKo{W1KW(!`tfWjrW$^*8c-SpS!cig-#Sb+ARjR^ zYJ)6dU|{IZ$<@PDBM3G}k^vGbe|{bNg{cN~5C=0OXd4eGPQ6RTeq*W;2Ah+}%)kIr zbDJ-J0j3(zb}nW{P$+@ie03Awb4)d$&|_uU=kBovV26cjSdjIb^DtIrpl#8e{%whOdP z6I6aJ@QA@;jx<;eDDQ#XEco?#9Hu!ka5bPXnvw9~6s8)`hC*gWL#V%|r?Wr7^cTq2 z%#5J)4pJ7ZHv0jl8qn5aggFXqC2^Q)6u|C*nPdA&{xPN+MX(y!#$37LDYr4*tOQom z1tJ+37%p~ss$r@DmD7;21ym;VoHgHwsYV540;480trk386NRZp6|4qy3a#fvam}=C)YCxOdK;=*L&$nKfYCt6qGh-x3B?AM4UGa+xm})?-Wo86z z+6U!}xp#NQW2(^tnZO7-TmYoTG5Jm#rW$Rq8qg62ATxMKc}uW4_{(`v z`-M)EpGt^&L|UxIM}UY8!%D z6|Ob~t(a;&!FGWTjt7-v^R8Q{Vyf{1s{z%RAiFl-H7>(c;|*2=sxd)oy55CiDStpm z#WFL3Q!E1m!;~WTt(fNcg3ST7a6slT9`Q-RRO1I$11g_DYSt}1g2m1LM5qCs{J_kJ zKQ02n=76?JgZ%$UW_JRndqAZDGb8BKDp13h9TCjT z2+BdA`a=GRpb(}Sa1F@V$-n?QIgla7?s){J8gT8#2rkVS7#JG0oUy2h0=s850|V$b za)$de^13k1i3Y0y6Ej2BA9Ao!R9<=U|;~%1q+^> z#!`R9fz`m=V|=smKc+eHU^SS{AHPKCv( z(BD8zHECdTKrJp%IKS#;If$ty9jvAW+TV$KU33Fe4Y;q%I2o$u!X7s}Of{KcbB;mP zJbS2)B`;@z)xhjBzqF?V(;RS1gFrgW0h z$pf1MYA1lopOC+Ck1^HcgVo>ER*;OFwFtSCL<_(KxIPoxg;#@AJ8BS zA#;ks?!j+P30Mut&A8oD3RVMZ{et{n=(2Y+rkl&aYVex_YN;|a;x`90&O=B|CD<-d zNe)V@U)HInV!8*^a%N`4ucn#^b3nBuGb5-L1KE|Nd;BJ*UA16yKq^7y*rR)Xl9*~h ztq4NysRx^b-^~qRHTd1rNQ4?t?}M2Uznh!E=78Gjpt@Q6V;l#jzd$*ZnGsa3g4zl=>o?)5;9$8~N0~#A+ zU|>i++ntB022{HdvJ2cZ1=q`s_6l%0o4Vd@>%q56E{Z>COu^k9QLd2EUs@S7{KkYa`ek{B_eNup0b!Z6-p^7O)!pc7aP&0&Re8U~}-h2Q(r| z$nT($XF_Ur5@FXaBGl|ALJjCRT0;4KFW4OXX?`COYW9QG;LjJJ^QZ`g&q1&`_~YUb z5o!(-q2>q?YCu<+Ff)QK69LT_8NI!Rr7r_&r86^vRD$~WoW9esjE93pbqJ|B0d^1Q z;ucUF>3`vRY0NU=Bv=jTG7(V!LT5%bmVWmsuo}>%J0SP0jpdieH0Lx}4Jb#0+J5_| z{J_%J1GU7M8A0}d`VM)Co3XeDWD7GRsO1J~Zz$M*XvVY)GtHpYk_r?bj>>GInBfebbtcen0?mLB z3d37qyYQC@pcynm=G+0B19lYy1B08wVJveBpm}d*M(}770|UdRw*j%3{<;S?2h`dG z^>d@R7cRt9b04e*f1Ex5s{yqHKz%*zeHl>hBNXqT7ABfBKpTbKVf4 z<}FwasMiBZ8*frrSTV!-9as(iGT}W~4QK`j)JJP9yL1@S98m9-nGrPR35xeUPp!6K zssSCEM=1S(R(23l^BL?O{NW6`?ud{%U%}?!Ph;PRQ1hJ#H9v?@^OFcQppkGw{sN86 z6H@aBY#078{7ZzIe?+JO^|%T3i9icy2)P-w#)ps^CeUgg0{t&$up0b+2OaXu%m|uE z1f}^)ucWYyeX)Yg!LNpm2sP|PsNn#s0kwWXbzJR%tJ;|PixaE{e}9<^tOhj40rD4< z`#LORP26BLpxG->U9hjAq88IG9)jqWS|Y;lm47gWoPbBGiCZaxsI)!*RPA zbQvKtBj|z&P+PRK*1-hR&7hVcGb5{Ah$}jxdUvZ3lH% zV5$*8QPUdIEP<&8G%}BH&mo7XVoWt+DCTsZoz;h_MjS;A>r;(LOf{fuQxJApyqu3^ zOivQUoc}&=v9zP4P}J;*Fu~F`mPS$Ilks8+rh8;i)D)(~H({y)jd~&6T>sY%%N!YK zO&>yySwY)NOmpN>?BcF5JddeH0Y#0V{a-BWTNJ@+K(z)ajfGF2_5srzC9oRMniJ62 zGLufG2BsQiuo{>-j*pLF=@+Pg)xgZzwpa^G9t2$m$;=2dr~AQNF-*HaCNMLCMy^18 znYj-*u;f#9uw5{7ia38UVw$4?Rs%CfO}8Z(Q;jBynue}b(=pX(fz^QeR-im+a(-72 zrW(*HK4wOk8uO~tzL;usP|VR25y-$)16tk5%m^AO0@*b+ekqo91)#&$nHfQ?aZtaK zUBq4o(;R&iyRy!H#uD!aC~6j0x=LZ1V+d9QbI;l7Tv*C*BNR2;Yky-I?==Rifw{R% z!8jPxF3?>l%#14-85lrwv4UMHSlZa8U~@pFJ!l@kRA>HYOmje&GB7iOS|*_I@fMfi z!c+sA$wh?o>W?q5lu;I7yFhDpLF4NC4jN!tFJTE*1BzKt-r<_IVI8Jjpabj?cD;G` z5X(G>HP{@`Ob{r|^JKPSNk29yYCcbB6~?s77DbKwV=|aA^S2c>jAvMYG7sEiIyv+n0EPq)xgTA`9I&?!BpdmqUP|e>saE#53B|> zn+A&0ujPJznCAF{)xhk!>^A=irW#PrLX??Pw0N) zU=xBM-Sb{RFeoc2WHo*=X3U8ssYXRBI;A`w-#8|(7(fc%( ze36BsCc#Jn%i7&+6g5r)by(7K4vHF;vN>4FmRztJSbARSe99ct?|CR{9_dHqVXDbT zQKLQkT??if@Gcr~-wISFnBGw=#8d<7ogvcLt?QSrV5%ts+XaiSxH-94+6l!dYPK3! zHDH=kf}-Yf*bOXeu}V?YOczSQGT#lF!DnU!U2_gvzw4&%tc+<_IoKRfYaFyjaova8 zT9|4;v;54AFf|g4rcMJY>2OAP0+zD30Y%My>rYt57#mU4i1oW@V!8*kas$#I1f@gob+T@lYMR03z|vUO zi}_gm-h!gWUW#Bt;6E431Bs_I5jqN`HE@IM6eoI zUuJqaE0(s_B(NHoIiUf|0x->)3|0dgbpg3q?RpEA_A_WV1T!P3wF%0DqWeD|!!&0q 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 z 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�Ak)R5(EtPJDlJe4#loZ4@xO;MqzvY7 zt^f7^zfX6G3TR*j6idyA9R44K7TnPC-O<{Qhrh}3*Z=>Z#y_Zq?8Ubb<1&tJfS(8{87-GOIIryZRDk}p+IM@^|_@tRHTo%;DhxF4T;j-IV85ov= zO}PnI2dWdJz@|Kfs|#jhV1V@4*0RBRc3apO7-GTdcCx`H@J_>JK})G1wq1wIzGq`# zxD2+9i5+GNCuk}UYziMc%oJI628Oj@b;fXYR_qK6Hehx3aCK?y3=C(${qbCO28Q!s z7HF&%5;Irfdhf6^Fo1NiFg%3oW#oXlfP;g9!3k_KF9*y83UFC<4h9BSu&fST)($S~ z%E7?k0hR?#$wKUX$pH&1Z%&vUL7WT>$zXNioG?4GI2jnOfPL8oS2vLp)|Z|JSGNW( zyO|RkVI9~!kM1KboemO^#bMwsTJMU8zu@&@1& z&4Lv!{F)Oo{)2MUGEh11nS8(}^8=_6=+WHjJ@vB87csi`Ru|U zdjYf%B6Nm_^>6-OQ%TSYpfxI>^>V$=e?aSf9GF1{Xtt<;hTOqZSPBr4IVubg#uAke zpb>h|inRwIr_BH_7`gxv+oN&-B-Yuw15_2sJ1}@yU#L~_Om>+r;E{O(R7-j?pW&C_ z$OvkJV`=d9tu+zT?r z1Jd&27YKCl==5FS!3>FuPS+Km_yrsS_ys&1K(ky1pZEnF13;_*evL!?#}57H*FVAE z?jgaz0Gc&9cJM#H{ssPa3phWNU*9!=zg-vEnwRYzKX{x}f ziN_=J1IQJQ-EJHn%;p~5wI9HfDD5!&s&Q#`<8bWuI6Qy@C&#LxO9hu zrZnwbI(<}1T=)fj9b7tlEx?n(f_p&|b}pSEDitpLg1w;mV3*FRpo#TQ{DQR`KJg3g z1x?U_CJkK=fXG(R+;OSEw|1~)$H1wBp@bd6U~H~YNnqFk8qO?X0flF$vkjQ%+Ikx# z;KtMG?BLq+q=e0dU*p?r7FT|aPcHo1_JX3;6}*B-U@mB8-j!eD{bzoGR*==7`32U3 z7UhE0nJoob?#h4c;(yRGy_=w6_(v}MnxP7yLSrr{96s|4#(-v+1zSPOVm^b`I%Rz3 z7tB$~apgbu{=aL>{}S$o8kH0V{#HH#1_pk8@bW+YHbw!^ESL-bt_R}$&0qK#7*+`I zx4hv8_1J?$e0p6ITslK5JbI^sN(2{vP2U85jZdK9a4q0J_9@h(w-?lS_{<;qfnVd4 zOJ^^rRlhsj|8m)g)Xp)y#1L!_A|J!1xJJn zzd&#as9+Ir;TMo_=`N6fj(vRM7i^ut2wGMt&>ur#VcBl#tP!xg0`Sn2)CcU6mML9EgnVdi`sB8O~UtnqoXzoh?)MtLdUQjH9 zSAZszbAx;a7ZM2INaN3Y$glqh9APQtBA-F)(XqO!M zj~x#E>1EA1?!gBWPWl zBY)&Ee*MGzZEtu%1DOZ-^$x?;sl1Ffk0@R?s= zZwVvVr~{w*1(t$B)`ef=JSgRY4%7O~FEAT41oW9d@+iN?Y0xsaGoYk<4Ycm~KgbBs z)KBDTS5T}vf|86YI9ekQffmi41T8)WY2%Ol3Q@}+c?PsJ)ddp&zg+l3z}red2?Vq% z`7U(f@d3~}M(Db{5S0{=wa_St`1skCKN7Sk8RT~G5H@J3=pp_P(Av$2!{EiLkv=LZ zpZOz8R1!e89tSz)oC|-*0Y`of@Jc<%now~52TdSd02v8d84C+*5c?uX2DB~^+&pmM z*Z;)d*22TU017|QC^iGXKy(O*(+kP10yQcH{2GrzB3nVZ*^xgS%&Aey;MV}Jh(5=! z@tXhG>rj4;%Z~ivulbK%UggLiewqK+?f;{HY63!e@kpT56#G7F6xj>eKyQt)Ve0K-rMo@Z(`{yRep*N7d za00ZL+>t->Jio?YkVS7CK@Ns`|0GD{FVtTkCq$e8C6eDDC64@&PeIa0T=+GffxYC) zANk-jf6NasKk}I?zs8%-{1HDtgGO?4KJ!Oj1ZVt6NaBfj1=4)#Gk?SnP<{n*K$-R$ z$jM-bTyo^scmcKO#b^EqP&5nHsAPbA@XD26;|ECPBakdLQi?K6MG6Oj8r z1>S3r3@FJ(y#34{aR9XF-;rMfB=Z)OIf9O;nc_S^-uhroqD;ua`)E`h?$kzeBv$aA2m120)V z_ZjY|h=1TPtW;{v!LdnxoYzkt7rM{+fIeTPr?eb6Gq&-^idDxdfTLqUptR5Dus^Y?LqI&a38 zJi5I-T#XNSGN1ayFVF}X2Ac@lxZuLC@cpZOy}H9gqq2_P+?g5~08egWSNpauX$ zTEKM$Sf>kr%&pJ-0-*~&^GDnP7f_(hEfpYbk3cypw&gQ;_l1CqN(o5tAjl#2KwB8V z*|*>`f5Zcj_zCbz2T*Q4_8DR%sN&#{cnCGq2fPme5LKI0|HtLf(a6!1n?;IzY(;oL-Sa7vwuoAPQuvfFg!p zz(u9T5mdnOgHx?Qib@42aee}AKKjfr0LuuV1nvlm(qR+e5Kl*7CU*;PsGA^%dH^KU z6To2|0!r@S@Xvr1Dv%@(4tP*9JBS+kkYo)>rJ&$O4%JUkQoCi9vYx|f*+RX zVFd{&;6e5vrEYM9@C!haxFdh$2}scbDpnvx3aC^97c1b*aT8?z22cWkC2vp}g(y_` z!S!t9J)}Yflss}kUIO_+09r|a3o9%oO9ZG?0b2>FU65Q3YV1VZ1{XEhN*R6uNWJ6A z9}!sq4*nd+V~wDBSVqsrgWzhS`2{0?t1M`=+4>NFFX%vN&+G#h9?b`3JeW^-YF_{i zxq$ZTfgS1y+Lr|q0+&r74`sfA#T7W-AyEY{_K!JAFhYFk$RGItIer9UJ5VEYkQedCCn6%!q6EW-K8HK z|F?lw1Nd~;{;>Yx)1At|?{mEM0DoUCXtl%)$Nw#DOZXTVUgoiYMiB*k9bEV|z=H<@ zkii2Net{B|0!Mz0pZo%?I-s7bKrhH8{DRIQ{DR&opk@?kZ`5%|&{k1behpAYJqYTB zegrkh4mk23`?$)HUjr<^3OtAr^8u7D-a%G)Ya9S^j=|c6kf!ufP?ZQ0zxtU!<^i-j zdh|1Y40Hn`bR;9@p(ALI7N|W5Z6mz|wPK^cFku{nB{=_x*&gmoDc5i-UF!sbww|L8nWOn6{vXNj{()?!zE6KpSqag8J*A%IGT8%io|f;Ql_iiwEkl zMLzw^FE|@CpaBXYkWr5Ok&i)2kAd1DpvK8XQ0esyBn@JKw#ZHfl@g%TxERz&Z}fIy zU}#|IcK31Q7hvn23K}j37219Ro#4|9y4`#_TYrG3bzQ;B;{!Z;!JFOrHC-J*n+XoM zbkwMLfR>dwc=Wb{OP)>_6$_7U*A*Z^1CL(U1W@x7H1GpzAiID#AY~xU8$e1lJbGI} z3)DaH3%Y{Z(4cwj(hn}3z7-zb=X|<-7l0HgfZEalF8rF{z4|VlQ$Zt6F8rFcV8cP$ zLG56MU7$Q-863dx6CBWbsgBzt`IO`TmOfA>uwmqHUCF?};M($pzhycD1H*S`7X}6g z2L6_N;KLVKz$?^1W9eANXq&59#LFZdcYFj5Jz@@ndn7xvcx3-5=yv7+ZPBuBvD*OJ zR3_=T<1Ct z52y%&luO_u%A-3}z@xi$2PozIKLnce@UWf=ngZePseJzbKX`W|D6N5aH$o;WG}fqq zCNRKz5kb-50@~CFndH#uQGrZ*Xh0@HKpQU{m>C#AOPoNHC7|t80pRHnjVUSupb6&2 zBcSOb@I)zOFJrf}3;(Vcg8aKa2y{BTICkGX_?pepI*#bDyaf%*TQ3#=|Njq4NRWwD zke@`s(_>rV)2_!E7#LE)(^=<1yCuQPlwUG1FtmVK^Wj^umN7CgNPzXOhHu3J1q#IE z<4g<;(qLK8mL|vq-DCLl>?wwGlGcz!# zfMut^WkE~MA@+jSm#Bkf&%u}k75)2HW$yyeM$8cE# zNd^YcG%yQ;xg_jF4I4?A$u&|i*=8x2Y=;y~_A@_ZEjwr#dbbk~fA}HLB6QGtb4LDf z=vwyhL!FL1;Fav4h3fo*o*dv6?GUfRR~9RHbl(Io#pU1b$&zM$u&lx(^CD;+E_9`2 znv-P@6KJ7WX}m`=bFat$){_;XATiLaU7fc_<`2-Mg%|T4CeQ?FcY6Y8W#^CoC5F}q zK`Sv1JE*D(4zGi6$8kc-1x(w)4M^VnJf&b$C9i72QlJU z5|BRxKnvI;K$~1W8sC7YvbsNZciIRrFm$(pmb`iN&I3)-TA!#@%e)AgyybSuJOE;W z3v`c8W{AS>6P;}!ZJlivph?}{bs*iXC+kE!k`IE0Nw|HGbanUnz-_QTQEPMXA$bYV>(LlII@>^8kKTPC!yxuR9n{$dHsQo;W{>W_ooyZhpxLTA2_J}M zU{gE~K45{kvHQKN@i!OCQU?A$P&Ne};sBcbbnO1;+Wikv*6w0pU}Es-?E$aVhU(-7 z?H&+h;Rhe<0a|mzFW}M5+#SNM()^2~R?DN?hh3sqWzn}}h1xOll#sr#70!Rk3j}RmSYTH2VD}b6>aJ+>b)II^tkU?Dz4m-#H$2=O3 zgJPLqfWe7hkj2uo`M7|KbtwaX&((jR_U;}PP!Rg`&QSrS8qe-%4bSdu1JCZqpnb20 zK_LN}eqmt%orjYIE=lGxz?Kt$w!lHeqS%!T>8n*8*Edz^{L-T+(sJW$apA zjlX&Hvd#w`$JG7KqnCGn5+{R;g+U4b|DztPpwh&{`e4nw*R0Kd|Cbu=0!1S7?6l(! z&`y3Vo^k9x_VOWU`)B7Il>in72FQMu&OIs-ETB~kOH>lT^cocnFug|wbgbM5{)6DT zoBszb!8AYUj0^^bPA~`TM~~#A#+N)Ye}EkZSrG2H1I%Im;n97bfBXCnK?X=V-NnGc zz>wCt9#jH?vK6RRnbrxGKh_TNFC)an?(@4q2TOw*J|4Z0H4dQjVH!YQ|wE}iQ^${`Nz4Xg0zYzO5EkKTSzg9j?yU%>)eT74DDh2`W{b$a_P zKu!w(1}+B}!KIq<0guiW@IwDj{DPhWVEs@5h*g&D;1vWq*%u;Qx_?6!HQIpI*n+HZ z%|4D|rAz02u${?ZO`rG$AVzy+pNR121}`q)7c^ny7c5~E01*lx!T>~o7Mi#)2K)!> z>u!UD%Tbu`_<-3wLb(aG4|2hn*JYMUd!&)H57 z9w^VnvLFCd0Pur$>SN|&4@mfcjU2cjfVveD63sh6>qr?7kIa)pYLIik; zC3sylD9&8652M5xG?`EuWuOG@XpItO6ZbGMFj!xxb@b@&1hvHR#2om>lj#+i3aaeUtN^L#?gN?QXpQQ2kU7u@_32#(QqjBvw5o-v zM8R=~4EAIQRfx#9;N04N16GD~xBme%1^ZusN|Lx5=C}%I8RFBs4rGV*;aW|P?taiR zJC{uGYJp7f)>??OJKI4LzTIy+=YtH|4cd+d-}QLHqqChs09Fbj8Z00d#2p+0NM)G> zOlf!j8h!=_$Niw97IZcrsCt2@?*Kbi08;jWcGy5dy!nTKBfrma>l3wxnHNC~VUO;9 zP&?Qq^8knkb1F#4vD=lw(fT+@d1pJw8br{5x|pCE3}RIR%&PA87BEw=zX0Sa>*L^( zzJecA(jS17^eiwxcD94WT{`!Jv>;6Go_+;XR)UM~4Pe8dUN`{S#REx?uGYt0`F)Oq z5{j-zcmD~HT?Zeq9el(Fb3RB2q89D}kg?tEIs%~nF{IjU{=ouvp8{yJ$PQ3q%Dw3=G*9P457PTu6Y8Dg;5}ZTU2rf9APrTh9iH9CKqvEcpZDq9Q~H-FI5m5aFDP=&%*@7JeR}IR05V;?)@(?Zu>TE#dIAGBN+BX1dlc)VZ%D??A zL{b720o`s19-ZK$T0OemGCVrl4IrWg9-Zx=Q&#!6w}YD39^H^;{J{qT+y_B}YXYFX zR~ma%z@5y-BcRTYV_aP9VLb-e2n=YQ$uICScF^Tnpl&b=gFSp441A_3c;qJ-E|v+` z3(9AI!Fo&KV`x=yb!~9gB={KG61d*gpxP2V@Bj)wh`qZQ7#JV}6rlZN5Ef|sjTx*C zbRKFTxc~PXZVD440|OgamK{D4=fcRqAjiPKz{23e$iM(Pxs`<>7|x1>v*H*T7?yy| zD`bS(3mOB1^guydD?-7tZcMNqDCnfKAh1{g69WV2eg+nXdL{-2&?q1aLnjjh!+Nmn zWw;I&W|&@SW|*uCGXuj$u)1=%>=!sop9K~(W-JU0psfll4A$@wJ9ieC-YOQD$xq-q zUb8ST@PI?(16;=+xDIX5`DS2y*TGrr@J$w3aMm{XhKjvx3=I5W^A5o`R9t3*`2#dg z3F*t*vNJIFflc;dXJ9xEW`(gcFl+_0%GqH$&alJW{0=T=0N=Xc!2wg(4Hr8LXEAcZ z^n$h^L+nk4i%o&Ej>1{r;4F3chJ|Q2s|U{F;f9U+N^>(X^nz2nF*gH)FL-pqk(+@5 zw1}RC;TAUo10z@r)O}_Fvp#V%FtCDIOgsz>-e8s#4+Db_m}SeuzyR8g!@}Ut0~?9R zhO^q?ti^B^Xw^st2WV{3kzc@p1+*%TKhlfkGk+xb_zMBhS+Yu?g;UT;qXdu?Xq6Wy zWaXt7$7lY?C>D^4&-^iuKl2NM&&o*T0G}k{(S6XP`_%sn#-L658ix;oc0EKM;txN` zuXm!H^)r9qfy1Ee2HG_X9pwcb4FW!9L%}fwysM-6je@K36QACVpwT~%?whXNe_SjV zf`;Mv`)-5AF*rB0=F*v>p#Y^-JiGZI8;N0>L0k_HxLF{s1u8cIm0JMg zdK`Sl;=#NbJb(z@lzTVWwEY zSq`A%Aq+nJ2Up@?H+g{1FyU|c&j#AfpQGa7(R~6DW1#Kf8lY3+6^=E4Ds|X!OgE@f zbT$4DI+dFTY>rRoZjhKqZ#TT^^XTn{S9Kn}-H58Kw;NQGfwg(SIu*&yphl}lcQ>eA z>d`CW-~n%XCQk-6Iz76(K`l^_uutc1kSd?f-4U>=)TeVdNYD+w*=6H|f z%^DDQffV_4?tTC=wDlW*AE>|N+5I0hf|iCD&-UnU2K600l0(>eJi2+<5w<}jd^&B| zp=w+@YuGuE*gQ}+NUscNkXry#a}B!yR24|K1QJ^Y$_DAy0qIsi*X_|;Z{gEf!~O!Q z5@f;$B=#4N=68(ztyZAincNKODtUBwgFOT97$r{z4TgDicY{X8JPtl$_vzdXQsL9N z8x%_5@b^ew{R7k`>;CT3y&2T!1xfOQhCg?M&O`BNeOqVj+5L}S9yDge&}{#|MARes zm`C?%4@h^}lNoZU0C+SUWTHp&TgKAs9?6?QGdLdIyFr~(&~8x;kJfK>b{>t#LDSyN z^$Y%&sC;){1Dd`rk@iS-=ke&~_vjUA@HqI031kd|Pp7?4XFZ2U<8e^bfjnD!(xbbZ z0Up(@2S6h}x3G^7ps0lC+6C&vgN+7dMo20_n3miO>gRiOcZ0$J>NkkuD&zpJeWFg=eWBY)KvzhbVw+Ig8?}qJ9dBf>D&!f)Bx%zTkgI9 z>PGj0hN(Tf|D&c)Q12L&O5kY>5q3g%0~PQ3?9iYmY_6d zsQ@ikFicQ@ng%jK1BtDJWP*)Hw~wU(HWNI0>mB|d1Ep0^`h?m31Ie5})|2jL|HrMtDr)X~BsYTw(>=PoAqv150@5Jx=$ue5DgRd=R zmczx4Gcz!p0Q>SLe67F(W(Ec!aJ&C0d?mq0W(J1yV0BC^F!MxN7#OyJ#X!9lhzr8u zvJG(7F*u8Z6{gn<&YBKqU14QlSP3@oJ6ueVje%heSWK3UfdO>46$^tV8v_HVf5yUK z3THXOS-x;qB%GBBXH8~fU;rJP%fhgO4dxcmiUCj$k%i$F8v_Gq#*qblX9g%-Sr}N^ z85kg}&-?;H;4|$$^T#-X8rg!-6#_A!odPVAgGNV#POLw0<={A&|xGggkls$0-ijd`6In}KJ!OF%}KHKb_slJ4FBSb{F4qG?snuj_<;Fvw$et{slY&0lM-YGKv5i(SVE{fDTlKs0#7Lm zTKCOf=l^NVKfpl{i`}jK+b=_hm^?CXfdc~+3eG?NAF#dv3WRuE+L~X0j`Xs0Q85B3 z10D5-Ev%}4Gn6Pg?nuRM3b;3?5PjIAw>jbM|No%QMf71%06@I10vfOadtDPgNMj5i zXbFJ_LNo&d!)vfQkTW2G09q3QF$HwJ&pWUz=oC7L>@2um(DgNtOJ+c46GCJ`t4PpO z5NOgLiw8Y0Q&2ZJ1)T?l3h1ynY??qvD#8;8b&>=mUrYdJ3`li=JsIR;j|mt4?T4Z1 zz%%m(N;+_{1~=S53vaOag@1cDBq8`f5&~#o4T~}oV%P*8!=TCw5{IC~3yI+*cuJ^+ z$02AM4HCnha9L;^HogJnVn@*JEZv7Zx^Fsy=F36n(kgg#p8y@7so>fD195IGzW_VG zfWJV?ff5CfBJcs54xssy1dtlYiWJb;Cg?&OL1vHc!~BBY0-&K!a1qpa1Y}wKVbI;| zpnDHk7(hNa3$6k@8sC6su|Dw&aDf)PGVp7Fmii}g@N2w4oQ&$C0-8+cQQ-%j3<%k{ zCICJ%HAcmNU$aIffnVc?3&_<0puG(aJfHXlb5y{4B5$~W9OwXA4(Y}7*^xi;CI7Mi zq5Q|bg@X5uL|%cc8a@bKmdhUjTBCgVGru6H7LWm*=$fM<0J&cWyviB0;ZOx+3g|YS zBcJ&rp?k(6-+^~8nSi7~yOTgi3ZD4PAM+X{Z~`>;1KLgpvIDgF7QCb7;AeipK$g$^ z5idX&^nB(Q%mm-zApoj=Sok##ec~680-yKCuTi4{x?0Fb#o;r5w6W`vqEG4!Y?l@&tIDxyB=Yjq8s5#~y*! zrQZU16}-#Pk^k6B$hvg!YU*Pj|3mKL0B?5$Z6iPSnLh%w^%1ne^~h()mO=2c>;s=& z`6DlaM!0xDVdV(A3qS+3#S|3O=Ri9!pv%{Pd&l;lTnyT)@DjB29^_Tf1x?^B*r2Vu8uy^WATNOS=Yj5D zx&YbO`xkay6DU1^`~tp=#RYV66X^OF7ycMfSltHgm<0s}Xs>Mq=-Q_LARcJtyd-F4 z{jbmbf|VeD33_sX5?TN#p@DWKK<=vYQBiQ=*9cJ&aN*aGQSoR#l5yCh`-n&Ln+#Ak z1*JNH2o8RYm!PAJ`86JX;umyS!LM2Hf?wl<3%{nr4N#7A038hDal(aP;4gR(a$yg+&D4Jg<_sSLEED+aW?6eI(>MhujeL3tJw%rT&} z3`)DFK|4gii4L^24t$LmD3`v6rAtT9ZV^y&bO9Z10KG?yKN7SrBL-C5fdUegAVIr4 zKrL(VJz}7;G6Hnw1ZWqkpd_di0G$b)2?;vFi4$I}qaT`E0Xj4nGJ6O;J2wrq$R5-h zDUIn3{NH-1A`~P58p5da_DDYD(aUqdySs%0G_>K-D|4dT<%CZ2kN+k5)`vii7tpma zSZZ?q?amHqmM$u0pyj&FM-&hT=Yp=*1~uMT7(mBCLn>}i3ur00KL)CdQv9WLhqiw7R|Xg*?bm|p`-gNOV< z7X?8Uc7Ns<0L>W)fX>_o_d-A!2b3m2i5Ajh0XGXkF&J zJ53aH%Eu>u0cn2y!{8IkL7PfqR1C_Q`895U>rznV8=_*uuYU+62HJ}Zs^kQmIr#NY zfJ8urz4T{(!C+7#aRwcP4#Lqq{Q5UPfp&&Fd;;yC4*)3z)ssFdE?{FrpxQyZX`Mwr z^9uxUK&=3sa30O$(R_dhw9`o7Gru6n`JhvH1OEU2|A}9K*{3%~r2y>O0FQ3a5f;Zj z^9wRVjQz|X1L8Ter19r@gSHf%1DOJHk~63^0gXVQ_fSc){UeIkY3o)b4- zV-7$^M1XD&bOo24F$ckuToGrWQ(KJS8ZPE2^n$_n;A{`t{tr580bC@2Z^Cosj{$9M zj{qHlpU49)Rt+HM2nafY?!5!W1}FqU&A|vz{TFi?T#Z4D70i?Xt@j6A7AWW`VbfR<1)b~=JW7}U)NPryK|jsz{n z2OXOaK2Ra@GU(`y&-{^BKl8^uh4>njW)(qq@qy2)5Co4ffRddf%VE%%F2q6|1_p*M z@QfvB*8-@japaGOAD$nNdU$?3%HjE+`Qr~Bwm!x0f3X`p2Ehc;9SheTi>f;oO?NC< zw+~piBfmfb$djPyOAi*%+$4Xb7igFRJn*3f5|#k9<3M$?BY$KDXx8$`XMRESSxZnH zMZ5+DFDRpCavTPoF9bPTh!NtvNKeqM0Fj`ADT3)Ue*|c5Gy*i30=oSGGT9jjp6rwW zS(?BC(It?<0SV_I9_W;+1iz*UsBtb}0J<~^)PRa)`ph3$1adTJ{xkx-9gsgVN#rws zq>})oISIP&GE3nzf5aD13j=g8#SKXFKjQ0W@a=Jeg&h1ECZLP4B3V3|k4PMbbyytv z1wh%G333moKmrH9#tl3@7VtF+(5?!&fHHt|Qoz|S0MuG?0@eATR=z90fCsp*0&3Am zz97_Li6ExK0y-K8RMTVWuz)H6NQVV-!DP&9Pzbw1wq##`cUV9NV!%2q5ig18uz<@v z{2dk-{ut1iM1r26JnW<50PC@U4(QMTo!}D*%1@w{asW7ufT{p+ssK3zv{MI^MWCn1 zaPn*10y*4~KQa=m7?hx5Kv@n{?SWEl%wbTr2bGCVpb{FK^EeKJr&0qzg`j#G z0=2;mz^A?lRDqK9HE^L6c^n*7p!2>2z>UQdu&u`+2K+)!XK-nO?H@f;d&oM$uCs3e71c3@cP-8(b z5mXOGg5(`R8>Rw9K*b%jL?QAr53Hvd9tNLbeu!W1MiqA{C+w1@!yersDh{Al0i^Bi z1nvzpbUFz@8{2$lOSadoWeC8L7((vfyI_6}< zcvuj0zXG@jg*Iz)RFJOJ@J#;b*nI=sta%B##*f8Xsz%ar2d)E?+5eyDWVgOiBFDe} zm{0dH{`L1fk}r59pY~wB3Of|OoBcH__^dg|fM$0NXwz+piUVle4l?QiIbRfXxeVld zQ9Jl}3~1a2a*nG5=&~kI#RI+h1ayN1(C|)xN4ErMg9q4bO(Zjw zL1!v}M}QPSgFgkJAr}RZ7BOp)3AC_TYV1V3@;=#bc0J$F} zk^#0f3N%9b6x_H0C22^57_^@rGG26;fq?-sUIeNk^BmLNA*h3bQ~y#ah;-_eK*2`8GI3}Pj|6F>;DQ?>uL*-23$$P+2a3+ zPG<}2AN(yKhe4N%gHO)}T`14sk$l3bo5jeflf}@byV$}Je0=syP$Rh8*}x-NW(WA} z?0KLWRgg8HgL$z;lSd}>?Cg1<30q5TP~Srpd;6Qf(b+Bp&d#ppKu@^cH7XvU)aw{` z7<8C1q|X9cLkKxM*9D$@L9=I�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 Im;&~*YdIQa#A7r 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

5F``a&tw|D%ro4|x7R=wp4p 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

#lZzFa|AF@ffY#`F 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

xvBd4_Ib_mg^#m{U(9FBB!fA%91#H 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ई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�HtD(2_Otn30lAo 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 diff --git a/vendor/box3d/src/build.bat b/vendor/box3d/src/build.bat new file mode 100644 index 000000000..55145a77c --- /dev/null +++ b/vendor/box3d/src/build.bat @@ -0,0 +1,8 @@ +@echo off + +if not exist "..\lib" mkdir ..\lib + +cl -nologo -MT -TC -O2 -c -std:c17 -I "include" src\*.c +lib -nologo *.obj -out:..\lib\box3d.lib + +del *.obj diff --git a/vendor/box3d/src/src/CMakeLists.txt b/vendor/box3d/src/src/CMakeLists.txt new file mode 100644 index 000000000..0460f85ba --- /dev/null +++ b/vendor/box3d/src/src/CMakeLists.txt @@ -0,0 +1,256 @@ +include(GNUInstallDirs) + +set(BOX3D_SOURCE_FILES + aabb.c + aabb.h + algorithm.h + arena_allocator.c + arena_allocator.h + bitset.c + bitset.h + block_allocator.c + block_allocator.h + body.c + body.h + broad_phase.c + broad_phase.h + capsule.c + compound.c + compound.h + constraint_graph.c + constraint_graph.h + contact.c + contact.h + contact_solver.c + contact_solver.h + container.h + convex_manifold.c + core.c + core.h + ctz.h + distance.c + distance_joint.c + dynamic_tree.c + height_field.c + hull.c + id_pool.c + id_pool.h + island.c + island.h + joint.c + joint.h + manifold.c + manifold.h + math_functions.c + math_internal.h + mesh.c + mesh_contact.c + motor_joint.c + mover.c + name_cache.c + name_cache.h + parallel_for.c + parallel_for.h + parallel_joint.c + physics_world.c + physics_world.h + platform.h + prismatic_joint.c + qsort.h + recording.c + recording.h + recording_ops.inl + recording_replay.c + recording_replay.h + world_snapshot.c + world_snapshot.h + revolute_joint.c + scheduler.c + scheduler.h + sensor.c + sensor.h + shape.c + shape.h + simd.c + simd.h + solver.c + solver.h + solver_set.c + solver_set.h + sphere.c + spherical_joint.c + table.c + table.h + timer.c + triangle_manifold.c + types.c + verstable.h + weld_joint.c + wheel_joint.c + + box3d.natvis +) + +set(BOX3D_INCLUDE_FILES + ../include/box3d/base.h + ../include/box3d/box3d.h + ../include/box3d/collision.h + ../include/box3d/config.h + ../include/box3d/constants.h + ../include/box3d/id.h + ../include/box3d/math_functions.h + ../include/box3d/types.h +) + +# Hide internal functions +# https://gcc.gnu.org/wiki/Visibility +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) + +add_library(box3d ${BOX3D_SOURCE_FILES} ${BOX3D_INCLUDE_FILES}) + +# target_link_libraries(box3d PRIVATE eastl) + +target_include_directories(box3d + PUBLIC + $ + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +set(CMAKE_DEBUG_POSTFIX "d") + +# Box3D uses C17 for _Static_assert and anonymous unions +set_target_properties(box3d PROPERTIES + C_STANDARD 17 + C_STANDARD_REQUIRED YES + C_EXTENSIONS YES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX} +) + +if (BOX3D_COMPILE_WARNING_AS_ERROR) + set_target_properties(box3d PROPERTIES COMPILE_WARNING_AS_ERROR ON) +endif() + +if (BOX3D_PROFILE) + target_compile_definitions(box3d PRIVATE BOX3D_PROFILE) + set(TRACY_DELAYED_INIT ON CACHE BOOL "Enable delayed init for Tracy" FORCE) + set(TRACY_MANUAL_LIFETIME ON CACHE BOOL "Enable manual lifetime for Tracy" FORCE) + + FetchContent_Declare( + tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG v0.13.1 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + ) + FetchContent_MakeAvailable(tracy) + + target_link_libraries(box3d PUBLIC TracyClient) +endif() + +if(BOX3D_VALIDATE) + message(STATUS "Box3D validation ON") + target_compile_definitions(box3d PRIVATE BOX3D_VALIDATE) +endif() + +if (BOX3D_DISABLE_SIMD) + message(STATUS "Box3D SIMD disabled") + target_compile_definitions(box3d PRIVATE BOX3D_DISABLE_SIMD) +endif() + +if (BOX3D_DOUBLE_PRECISION) + message(STATUS "Box3D double precision ON") + # PUBLIC: consumers must see the same precision mode in the headers or they cannot match the ABI + target_compile_definitions(box3d PUBLIC BOX3D_DOUBLE_PRECISION) +endif() + +if (MSVC) + message(STATUS "Box3D on MSVC") + if (BUILD_SHARED_LIBS) + # this is needed by DLL users to import Box3D symbols + target_compile_definitions(box3d INTERFACE BOX3D_DLL) + endif() + + # Visual Studio won't load the natvis unless it is in the project + target_sources(box3d PRIVATE box3d.natvis) + + # Enable asserts in release with debug info + target_compile_definitions(box3d PUBLIC "$<$:B3_ENABLE_ASSERT>") + + # Warnings Wall is problematic in mixed C/C++, so using W4 + target_compile_options(box3d PRIVATE /W4) + # target_compile_options(box3d PRIVATE /wd4820 /wd5045 /wd4061 /wd4711 /wd4514 /wd4365 /wd5219 /wd5039) + target_compile_options(box3d PRIVATE /wd4820 /wd5045 /wd4061 /wd4711) + # 4710 - warn about inline functions that are not inlined + target_compile_options(box3d PRIVATE /wd4710 ) + + if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + target_compile_options(box3d PRIVATE -Wmissing-prototypes) + endif() + +elseif (MINGW) + message(STATUS "Box3D on MinGW") +elseif (APPLE) + message(STATUS "Box3D on Apple") + target_compile_options(box3d PRIVATE -Wmissing-prototypes -Wall -Wextra -pedantic) +elseif (EMSCRIPTEN) + message(STATUS "Box3D on Emscripten") + # SSE2/wasm SIMD flags are set directory-wide in the top-level CMakeLists so the + # tests, which include simd.h, get them too. +elseif (UNIX) + message(STATUS "Box3D using Unix") + target_compile_options(box3d PRIVATE -Wmissing-prototypes -Wall -Wextra -pedantic -Wno-unused-value) + if ("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "aarch64") + # raspberry pi + # -mfpu=neon + # target_compile_options(box3d PRIVATE) + else() + endif() +endif() + +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "src" FILES ${BOX3D_SOURCE_FILES}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include" PREFIX "include" FILES ${BOX3D_INCLUDE_FILES}) + +add_library(box3d::box3d ALIAS box3d) + +# libm is required on many Unix toolchains for sqrtf, etc. +if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) + target_link_libraries(box3d PUBLIC m) +endif() + +install( + TARGETS box3d + EXPORT box3dConfig + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install( + EXPORT box3dConfig + NAMESPACE box3d:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/box3d" +) + +install( + FILES ${BOX3D_INCLUDE_FILES} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/box3d +) + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/box3dConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) + +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/box3dConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/box3d" +) diff --git a/vendor/box3d/src/src/aabb.c b/vendor/box3d/src/src/aabb.c new file mode 100644 index 000000000..6045bd82f --- /dev/null +++ b/vendor/box3d/src/src/aabb.c @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "aabb.h" + +#include "math_internal.h" + +#include "box3d/math_functions.h" + +#include + +// Similar to Real-time Collision Detection, p179. +// todo try +// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection.html +bool b3RayCastAABB( b3AABB a, b3Vec3 p1, b3Vec3 p2, float* minFraction, float* maxFraction ) +{ + // Ray direction and length + b3Vec3 d = b3Sub( p2, p1 ); + float rayLength = b3Length( d ); + + // Handle degenerate ray + if ( rayLength < FLT_EPSILON ) + { + // Check if point is inside AABB + if ( p1.x >= a.lowerBound.x && p1.x <= a.upperBound.x && p1.y >= a.lowerBound.y && p1.y <= a.upperBound.y && + p1.z >= a.lowerBound.z && p1.z <= a.upperBound.z ) + { + *minFraction = 0.0f; + *maxFraction = 0.0f; + return true; + } + + return false; + } + + b3Vec3 rayDir = b3MulSV( 1.0f / rayLength, d ); + + // Slab method for ray-AABB intersection + float tMin = 0.0f; + float tMax = rayLength; + + // x-axis + { + float rayComponent = rayDir.x; + float rayStart = p1.x; + float boxMin = a.lowerBound.x; + float boxMax = a.upperBound.x; + + if ( b3AbsFloat( rayComponent ) < FLT_EPSILON ) + { + // Ray is parallel to slab, check if ray origin is within slab + if ( rayStart < boxMin || rayStart > boxMax ) + { + return false; + } + } + else + { + // Compute intersection distances + float t1 = ( boxMin - rayStart ) / rayComponent; + float t2 = ( boxMax - rayStart ) / rayComponent; + + // Ensure t1 <= t2 + if ( t1 > t2 ) + { + float temp = t1; + t1 = t2; + t2 = temp; + } + + // Update intersection interval + tMin = b3MaxFloat( tMin, t1 ); + tMax = b3MinFloat( tMax, t2 ); + + // Check for no intersection + if ( tMin > tMax ) + { + return false; + } + } + } + + // y-axis + { + float rayComponent = rayDir.y; + float rayStart = p1.y; + float boxMin = a.lowerBound.y; + float boxMax = a.upperBound.y; + + if ( b3AbsFloat( rayComponent ) < FLT_EPSILON ) + { + // Ray is parallel to slab, check if ray origin is within slab + if ( rayStart < boxMin || rayStart > boxMax ) + { + return false; + } + } + else + { + // Compute intersection distances + float t1 = ( boxMin - rayStart ) / rayComponent; + float t2 = ( boxMax - rayStart ) / rayComponent; + + // Ensure t1 <= t2 + if ( t1 > t2 ) + { + float temp = t1; + t1 = t2; + t2 = temp; + } + + // Update intersection interval + tMin = b3MaxFloat( tMin, t1 ); + tMax = b3MinFloat( tMax, t2 ); + + // Check for no intersection + if ( tMin > tMax ) + { + return false; + } + } + } + + // z-axis + { + float rayComponent = rayDir.z; + float rayStart = p1.z; + float boxMin = a.lowerBound.z; + float boxMax = a.upperBound.z; + + if ( b3AbsFloat( rayComponent ) < FLT_EPSILON ) + { + // Ray is parallel to slab, check if ray origin is within slab + if ( rayStart < boxMin || rayStart > boxMax ) + { + return false; + } + } + else + { + // Compute intersection distances + float t1 = ( boxMin - rayStart ) / rayComponent; + float t2 = ( boxMax - rayStart ) / rayComponent; + + // Ensure t1 <= t2 + if ( t1 > t2 ) + { + float temp = t1; + t1 = t2; + t2 = temp; + } + + // Update intersection interval + tMin = b3MaxFloat( tMin, t1 ); + tMax = b3MinFloat( tMax, t2 ); + + // Check for no intersection + if ( tMin > tMax ) + { + return false; + } + } + } + + // Check if intersection is behind ray start + if ( tMax < 0.0f ) + { + return false; + } + + // Convert distances to fractions + *minFraction = b3ClampFloat( tMin / rayLength, 0.0f, 1.0f ); + *maxFraction = b3ClampFloat( tMax / rayLength, 0.0f, 1.0f ); + + return true; +} diff --git a/vendor/box3d/src/src/aabb.h b/vendor/box3d/src/src/aabb.h new file mode 100644 index 000000000..97a00b189 --- /dev/null +++ b/vendor/box3d/src/src/aabb.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/types.h" + +// Ray cast an AABB. This is a custom function used by height fields. +bool b3RayCastAABB( b3AABB a, b3Vec3 p1, b3Vec3 p2, float* minFraction, float* maxFraction ); + +// Get the surface area (perimeter) +static inline float b3Perimeter( b3AABB a ) +{ + float wx = a.upperBound.x - a.lowerBound.x; + float wy = a.upperBound.y - a.lowerBound.y; + float wz = a.upperBound.z - a.lowerBound.z; + return 2.0f * ( wx * wz + wy * wx + wz * wy ); +} + +/// Enlarge a to contain b +/// @return true if the AABB grew +static inline bool b3EnlargeAABB( b3AABB* a, b3AABB b ) +{ + bool changed = false; + if ( b.lowerBound.x < a->lowerBound.x ) + { + a->lowerBound.x = b.lowerBound.x; + changed = true; + } + + if ( b.lowerBound.y < a->lowerBound.y ) + { + a->lowerBound.y = b.lowerBound.y; + changed = true; + } + + if ( b.lowerBound.z < a->lowerBound.z ) + { + a->lowerBound.z = b.lowerBound.z; + changed = true; + } + + if ( a->upperBound.x < b.upperBound.x ) + { + a->upperBound.x = b.upperBound.x; + changed = true; + } + + if ( a->upperBound.y < b.upperBound.y ) + { + a->upperBound.y = b.upperBound.y; + changed = true; + } + + if ( a->upperBound.z < b.upperBound.z ) + { + a->upperBound.z = b.upperBound.z; + changed = true; + } + + return changed; +} + +#if 0 +/// Do a and b overlap +inline bool b3OverlapAABBs( b3AABB a, b3AABB b ) +{ + return !( b.lowerBound.x > a.upperBound.x || b.lowerBound.y > a.upperBound.y || b.lowerBound.z > a.upperBound.z || + a.lowerBound.x > b.upperBound.x || a.lowerBound.y > b.upperBound.y || a.lowerBound.z > b.upperBound.z ); +} +#endif + +static inline b3Vec3 b3FarthestPointOnAABB( b3AABB b, b3Vec3 p ) +{ + return (b3Vec3){ + .x = ( p.x - b.lowerBound.x ) > ( b.upperBound.x - p.x ) ? b.lowerBound.x : b.upperBound.x, + .y = ( p.y - b.lowerBound.y ) > ( b.upperBound.y - p.y ) ? b.lowerBound.y : b.upperBound.y, + .z = ( p.z - b.lowerBound.z ) > ( b.upperBound.z - p.z ) ? b.lowerBound.z : b.upperBound.z, + }; +} diff --git a/vendor/box3d/src/src/algorithm.h b/vendor/box3d/src/src/algorithm.h new file mode 100644 index 000000000..155bb310d --- /dev/null +++ b/vendor/box3d/src/src/algorithm.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +// Swap two same-size lvalues through a byte buffer. Avoids __typeof__ so it builds on any C compiler. +#define B3_SWAP( x, y ) \ + do \ + { \ + _Static_assert( sizeof( x ) == sizeof( y ), "size mismatch" ); \ + char B3_SWAP_TEMP[sizeof( x )]; \ + memcpy( B3_SWAP_TEMP, &( x ), sizeof( x ) ); \ + memcpy( &( x ), &( y ), sizeof( x ) ); \ + memcpy( &( y ), B3_SWAP_TEMP, sizeof( x ) ); \ + } \ + while ( 0 ) diff --git a/vendor/box3d/src/src/arena_allocator.c b/vendor/box3d/src/src/arena_allocator.c new file mode 100644 index 000000000..8543e5782 --- /dev/null +++ b/vendor/box3d/src/src/arena_allocator.c @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "arena_allocator.h" + +#include "core.h" + +#include +#include + +b3Stack b3CreateStack( int capacity ) +{ + B3_ASSERT( capacity >= 0 ); + b3Stack stack = { 0 }; + stack.capacity = capacity; + stack.memory = (char*)b3Alloc( capacity ); + return stack; +} + +void b3DestroyStack( b3Stack* stack ) +{ + b3Free( stack->memory, stack->capacity ); +} + +void* b3StackAlloc( b3Stack* stack, int size, const char* name ) +{ + if ( stack->entryCount == B3_MAX_STACK_ENTRIES ) + { + B3_ASSERT( false ); + return NULL; + } + + int alignedSize = ( ( size - 1 ) | ( B3_ALIGNMENT - 1 ) ) + 1; + + b3StackEntry entry; + entry.size = alignedSize; + entry.name = name; + if ( stack->index + alignedSize > stack->capacity ) + { + // fall back to the heap (undesirable) + entry.data = (char*)b3Alloc( alignedSize ); + entry.usedMalloc = true; + + B3_ASSERT( ( (uintptr_t)entry.data & ( B3_ALIGNMENT - 1 ) ) == 0 ); + } + else + { + entry.data = stack->memory + stack->index; + entry.usedMalloc = false; + stack->index += alignedSize; + + B3_ASSERT( ( (uintptr_t)entry.data & ( B3_ALIGNMENT - 1 ) ) == 0 ); + } + + stack->allocation += alignedSize; + if ( stack->allocation > stack->maxAllocation ) + { + stack->maxAllocation = stack->allocation; + } + + stack->entries[stack->entryCount] = entry; + stack->entryCount += 1; + return entry.data; +} + +void b3StackFree( b3Stack* stack, void* mem ) +{ + int entryCount = stack->entryCount; + B3_ASSERT( entryCount > 0 ); + b3StackEntry* entry = stack->entries + ( entryCount - 1 ); + B3_ASSERT( mem == entry->data ); + if ( entry->usedMalloc ) + { + b3Free( mem, entry->size ); + } + else + { + stack->index -= entry->size; + } + stack->allocation -= entry->size; + stack->entryCount -= 1; +} + +void b3GrowStack( b3Stack* stack ) +{ + // Stack must not be in use + B3_ASSERT( stack->allocation == 0 ); + + if ( stack->maxAllocation > stack->capacity ) + { + b3Free( stack->memory, stack->capacity ); + stack->capacity = stack->maxAllocation + stack->maxAllocation / 2; + stack->memory = (char*)b3Alloc( stack->capacity ); + } +} + +int b3GetStackCapacity( b3Stack* stack ) +{ + return stack->capacity; +} + +int b3GetStackAllocation( b3Stack* stack ) +{ + return stack->allocation; +} + +int b3GetMaxStackAllocation( b3Stack* stack ) +{ + return stack->maxAllocation; +} + +b3Arena b3CreateArena( int capacity ) +{ + int c = capacity > 8 ? capacity : 8; + b3ArenaSharedState* shared = (b3ArenaSharedState*)b3Alloc( sizeof( b3ArenaSharedState ) ); + *shared = (b3ArenaSharedState){ 0 }; + + return (b3Arena){ + .memory = (char*)b3Alloc( c ), + .capacity = c, + .index = 0, + .shared = shared, + }; +} + +void b3DestroyArena( b3Arena* arena ) +{ + b3ArenaSharedState* shared = arena->shared; + if ( shared != NULL ) + { + for ( int i = 0; i < shared->overflows.count; ++i ) + { + b3Free( shared->overflows.data[i].data, shared->overflows.data[i].size ); + } + b3Array_Destroy( shared->overflows ); + b3Free( shared, sizeof( b3ArenaSharedState ) ); + } + b3Free( arena->memory, arena->capacity ); + *arena = (b3Arena){ 0 }; +} + +void* b3ArenaOverflowAlloc( b3Arena* arena, int size ) +{ + b3ArenaSharedState* shared = arena->shared; + char* data = (char*)b3Alloc( size ); + b3OverflowBlock block = { data, size }; + b3Array_Push( shared->overflows, block ); + shared->overflowBytes += size; + return data; +} + +void b3ArenaSync( b3Arena* arena ) +{ + b3ArenaSharedState* shared = arena->shared; + + for ( int i = 0; i < shared->overflows.count; ++i ) + { + b3Free( shared->overflows.data[i].data, shared->overflows.data[i].size ); + } + b3Array_Clear( shared->overflows ); + + int demand = shared->maxIndex + shared->overflowBytes; + if ( demand > shared->peakDemand ) + { + shared->peakDemand = demand; + } + if ( demand > arena->capacity ) + { + b3Free( arena->memory, arena->capacity ); + int newCapacity = demand + demand / 2; + arena->memory = (char*)b3Alloc( newCapacity ); + arena->capacity = newCapacity; + } + + arena->index = 0; + shared->maxIndex = 0; + shared->overflowBytes = 0; +} diff --git a/vendor/box3d/src/src/arena_allocator.h b/vendor/box3d/src/src/arena_allocator.h new file mode 100644 index 000000000..41422b91b --- /dev/null +++ b/vendor/box3d/src/src/arena_allocator.h @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once +#include "box3d/base.h" +#include "container.h" + +#include +#include + +#define B3_MAX_STACK_ENTRIES 32 + +typedef struct b3StackEntry +{ + char* data; + const char* name; + int size; + bool usedMalloc; +} b3StackEntry; + +// This is a stack-like arena allocator used for fast per step allocations. +// You must nest allocate/free pairs. The code will B3_ASSERT +// if you try to interleave multiple allocate/free pairs. +// This allocator uses the heap if space is insufficient. +// I could remove the need to free entries individually. +typedef struct b3Stack +{ + char* memory; + int capacity; + int index; + + int allocation; + int maxAllocation; + + b3StackEntry entries[B3_MAX_STACK_ENTRIES]; + int entryCount; +} b3Stack; + +// Heap-allocated fallback block tracked when an arena bump overflows. +typedef struct b3OverflowBlock +{ + char* data; + int size; +} b3OverflowBlock; + +b3DeclareArray( b3OverflowBlock ); + +// Shared, heap-allocated state co-owned by every copy of a b3Arena. +// b3Arena is passed by value so its bump pointer auto-restores on +// function return, but overflow tracking and watermarks must persist +// across copies -- hence this pointer-shared block. +typedef struct b3ArenaSharedState +{ + b3Array( b3OverflowBlock ) overflows; + int maxIndex; // high water mark of the bump pointer this step + int overflowBytes; // total bytes in overflow blocks this step + int peakDemand; // all-time peak of (maxIndex + overflowBytes), survives sync +} b3ArenaSharedState; + +typedef struct b3Arena +{ + char* memory; + int capacity; + int index; + b3ArenaSharedState* shared; +} b3Arena; + +// 16-byte alignment for SSE2 + typical struct alignment. +#define B3_ARENA_ALIGNMENT 16 + +b3Stack b3CreateStack( int capacity ); +void b3DestroyStack( b3Stack* stack ); + +void* b3StackAlloc( b3Stack* stack, int size, const char* name ); +void b3StackFree( b3Stack* stack, void* mem ); + +// Grow the stack based on usage +void b3GrowStack( b3Stack* stack ); + +int b3GetStackCapacity( b3Stack* stack ); +int b3GetStackAllocation( b3Stack* stack ); +int b3GetMaxStackAllocation( b3Stack* stack ); + +b3Arena b3CreateArena( int capacity ); +void b3DestroyArena( b3Arena* arena ); + +// Heap-allocate an overflow block, register it in the shared state, return it. +void* b3ArenaOverflowAlloc( b3Arena* arena, int size ); + +// Call between simulation steps. Frees this step's overflow blocks and grows +// the backing capacity if last step's demand (maxIndex + overflowBytes) exceeded it. +void b3ArenaSync( b3Arena* arena ); + +static inline void* b3Bump( b3Arena* arena, int size ) +{ + if ( size == 0 ) + { + return NULL; + } + + int aligned = ( arena->index + ( B3_ARENA_ALIGNMENT - 1 ) ) & ~( B3_ARENA_ALIGNMENT - 1 ); + + if ( aligned + size > arena->capacity ) + { + return b3ArenaOverflowAlloc( arena, size ); + } + + arena->index = aligned + size; + if ( arena->index > arena->shared->maxIndex ) + { + arena->shared->maxIndex = arena->index; + } + return arena->memory + aligned; +} diff --git a/vendor/box3d/src/src/bitset.c b/vendor/box3d/src/src/bitset.c new file mode 100644 index 000000000..319725bfc --- /dev/null +++ b/vendor/box3d/src/src/bitset.c @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "bitset.h" + +#include + +b3BitSet b3CreateBitSet( uint32_t bitCapacity ) +{ + b3BitSet bitSet = { 0 }; + bitSet.blockCapacity = ( bitCapacity + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 ); + bitSet.blockCount = 0; + bitSet.bits = (uint64_t*)b3Alloc( bitSet.blockCapacity * sizeof( uint64_t ) ); + memset( bitSet.bits, 0, bitSet.blockCapacity * sizeof( uint64_t ) ); + return bitSet; +} + +void b3DestroyBitSet( b3BitSet* bitSet ) +{ + b3Free( bitSet->bits, bitSet->blockCapacity * sizeof( uint64_t ) ); + bitSet->blockCapacity = 0; + bitSet->blockCount = 0; + bitSet->bits = NULL; +} + +void b3SetBitCountAndClear( b3BitSet* bitSet, uint32_t bitCount ) +{ + uint32_t blockCount = ( bitCount + sizeof( uint64_t ) * 8 - 1 ) / ( sizeof( uint64_t ) * 8 ); + if ( bitSet->blockCapacity < blockCount ) + { + b3DestroyBitSet( bitSet ); + uint32_t newBitCapacity = bitCount + ( bitCount >> 1 ); + *bitSet = b3CreateBitSet( newBitCapacity ); + } + + bitSet->blockCount = blockCount; + memset( bitSet->bits, 0, bitSet->blockCount * sizeof( uint64_t ) ); +} + +void b3GrowBitSet( b3BitSet* bitSet, uint32_t blockCount ) +{ + B3_ASSERT( blockCount > bitSet->blockCount ); + if ( blockCount > bitSet->blockCapacity ) + { + uint32_t oldCapacity = bitSet->blockCapacity; + bitSet->blockCapacity = blockCount + blockCount / 2; + uint64_t* newBits = (uint64_t*)b3Alloc( bitSet->blockCapacity * sizeof( uint64_t ) ); + memset( newBits, 0, bitSet->blockCapacity * sizeof( uint64_t ) ); + B3_ASSERT( bitSet->bits != NULL ); + memcpy( newBits, bitSet->bits, oldCapacity * sizeof( uint64_t ) ); + b3Free( bitSet->bits, oldCapacity * sizeof( uint64_t ) ); + bitSet->bits = newBits; + } + + bitSet->blockCount = blockCount; +} + +void b3InPlaceUnion( b3BitSet* __restrict setA, const b3BitSet* __restrict setB ) +{ + B3_ASSERT( setA->blockCount == setB->blockCount ); + uint32_t blockCount = setA->blockCount; + for ( uint32_t i = 0; i < blockCount; ++i ) + { + setA->bits[i] |= setB->bits[i]; + } +} diff --git a/vendor/box3d/src/src/bitset.h b/vendor/box3d/src/src/bitset.h new file mode 100644 index 000000000..ff771d967 --- /dev/null +++ b/vendor/box3d/src/src/bitset.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core.h" + +#include +#include + +// Bit set provides fast operations on large arrays of bits. +typedef struct b3BitSet +{ + uint64_t* bits; + uint32_t blockCapacity; + uint32_t blockCount; +} b3BitSet; + + +b3BitSet b3CreateBitSet( uint32_t bitCapacity ); +void b3DestroyBitSet( b3BitSet* bitSet ); +void b3SetBitCountAndClear( b3BitSet* bitSet, uint32_t bitCount ); +void b3InPlaceUnion( b3BitSet* setA, const b3BitSet* setB ); +void b3GrowBitSet( b3BitSet* bitSet, uint32_t blockCount ); +int b3CountSetBits( b3BitSet* bitSet ); + +static inline void b3SetBit( b3BitSet* bitSet, uint32_t bitIndex ) +{ + uint32_t blockIndex = bitIndex / 64; + B3_ASSERT( blockIndex < bitSet->blockCount ); + bitSet->bits[blockIndex] |= ( (uint64_t)1 << bitIndex % 64 ); +} + +static inline void b3SetBitGrow( b3BitSet* bitSet, uint32_t bitIndex ) +{ + uint32_t blockIndex = bitIndex / 64; + if ( blockIndex >= bitSet->blockCount ) + { + b3GrowBitSet( bitSet, blockIndex + 1 ); + } + bitSet->bits[blockIndex] |= ( (uint64_t)1 << bitIndex % 64 ); +} + +static inline void b3ClearBit( b3BitSet* bitSet, uint32_t bitIndex ) +{ + uint32_t blockIndex = bitIndex / 64; + if ( blockIndex >= bitSet->blockCount ) + { + return; + } + bitSet->bits[blockIndex] &= ~( (uint64_t)1 << bitIndex % 64 ); +} + +static inline bool b3GetBit( const b3BitSet* bitSet, uint32_t bitIndex ) +{ + uint32_t blockIndex = bitIndex / 64; + if ( blockIndex >= bitSet->blockCount ) + { + return false; + } + return ( bitSet->bits[blockIndex] & ( (uint64_t)1 << bitIndex % 64 ) ) != 0; +} + +static inline int b3GetBitSetBytes( b3BitSet* bitSet ) +{ + return bitSet->blockCapacity * sizeof( uint64_t ); +} diff --git a/vendor/box3d/src/src/block_allocator.c b/vendor/box3d/src/src/block_allocator.c new file mode 100644 index 000000000..4c1f1544e --- /dev/null +++ b/vendor/box3d/src/src/block_allocator.c @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#include "block_allocator.h" + +#include "core.h" + +b3BlockAllocator b3CreateBlockAllocator( int elementSize, int initialCount ) +{ + B3_ASSERT( elementSize >= (int)sizeof( void* ) ); + + b3BlockAllocator allocator = { 0 }; + + b3Array_Create( allocator.blocks ); + allocator.elementSize = elementSize; + allocator.freeList = NULL; + allocator.nextIndex = 0; + allocator.allocationCount = 0; + + if ( initialCount > 0 ) + { + int count = ( initialCount + B3_BLOCK_SIZE - 1 ) >> B3_BLOCK_EXPONENT; + b3Array_Resize( allocator.blocks, count ); + + for ( int i = 0; i < allocator.blocks.count; ++i ) + { + allocator.blocks.data[i].memory = b3Alloc( B3_BLOCK_SIZE * elementSize ); + } + } + + return allocator; +} + +void b3DestroyBlockAllocator( b3BlockAllocator* allocator ) +{ + for ( int i = 0; i < allocator->blocks.count; ++i ) + { + b3Free( allocator->blocks.data[i].memory, B3_BLOCK_SIZE * allocator->elementSize ); + } + + b3Array_Destroy( allocator->blocks ); +} + +void* b3AllocateElement( b3BlockAllocator* allocator ) +{ + B3_ASSERT( allocator != NULL ); + + allocator->allocationCount += 1; + + // Pop from free list first + if ( allocator->freeList != NULL ) + { + void* element = allocator->freeList; + allocator->freeList = *(void**)element; + return element; + } + + int index = allocator->nextIndex++; + + int requiredBlockCount = ( index >> B3_BLOCK_EXPONENT ) + 1; + + if ( requiredBlockCount > allocator->blocks.count ) + { + int oldCount = allocator->blocks.count; + b3Array_Resize( allocator->blocks, requiredBlockCount ); + + for ( int i = oldCount; i < requiredBlockCount; ++i ) + { + allocator->blocks.data[i].memory = b3Alloc( B3_BLOCK_SIZE * allocator->elementSize ); + } + } + + int blockIndex = index >> B3_BLOCK_EXPONENT; + int blockOffset = index & ( B3_BLOCK_SIZE - 1 ); + + return allocator->blocks.data[blockIndex].memory + blockOffset * allocator->elementSize; +} + +void b3FreeElement( b3BlockAllocator* allocator, void* element ) +{ + B3_ASSERT( allocator != NULL ); + B3_ASSERT( element != NULL ); + B3_ASSERT( allocator->allocationCount > 0 ); + + allocator->allocationCount -= 1; + + // Push onto free list + *(void**)element = allocator->freeList; + allocator->freeList = element; +} diff --git a/vendor/box3d/src/src/block_allocator.h b/vendor/box3d/src/src/block_allocator.h new file mode 100644 index 000000000..e9922d012 --- /dev/null +++ b/vendor/box3d/src/src/block_allocator.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "container.h" + +#include "box3d/base.h" + +#define B3_BLOCK_EXPONENT 8 +#define B3_BLOCK_SIZE ( 1 << B3_BLOCK_EXPONENT ) + +typedef struct b3Block +{ + char* memory; +} b3Block; + +b3DeclareArray( b3Block ); + +typedef struct b3BlockAllocator +{ + b3Array( b3Block ) blocks; + void* freeList; + int elementSize; + int nextIndex; + int allocationCount; +} b3BlockAllocator; + +// Element must be large enough to hold a pointer +b3BlockAllocator b3CreateBlockAllocator( int elementSize, int initialCount ); +void b3DestroyBlockAllocator( b3BlockAllocator* allocator ); + +// Returns one element of elementSize contiguous bytes. Address is stable until freed. +void* b3AllocateElement( b3BlockAllocator* allocator ); +void b3FreeElement( b3BlockAllocator* allocator, void* element ); diff --git a/vendor/box3d/src/src/body.c b/vendor/box3d/src/src/body.c new file mode 100644 index 000000000..b78642c6c --- /dev/null +++ b/vendor/box3d/src/src/body.c @@ -0,0 +1,2454 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" + +#include "aabb.h" +#include "contact.h" +#include "core.h" +#include "id_pool.h" +#include "island.h" +#include "joint.h" +#include "physics_world.h" +#include "recording.h" +#include "sensor.h" +#include "shape.h" +#include "solver_set.h" + +// needed for dll export +#include "box3d/box3d.h" +#include "box3d/id.h" + +#include + +// Get a validated body from a world using an id. +b3Body* b3GetBodyFullId( b3World* world, b3BodyId bodyId ) +{ + B3_ASSERT( b3Body_IsValid( bodyId ) ); + + // id index starts at one so that zero can represent null + // id index starts at one so that zero can represent null + return b3Array_Get( world->bodies, bodyId.index1 - 1 ); +} + +b3WorldTransform b3GetBodyTransformQuick( b3World* world, b3Body* body ) +{ + b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex ); + return bodySim->transform; +} + +b3WorldTransform b3GetBodyTransform( b3World* world, int bodyId ) +{ + b3Body* body = b3Array_Get( world->bodies, bodyId ); + return b3GetBodyTransformQuick( world, body ); +} + +// Create a b3BodyId from a raw id. +b3BodyId b3MakeBodyId( b3World* world, int bodyId ) +{ + b3Body* body = b3Array_Get( world->bodies, bodyId ); + return (b3BodyId){ bodyId + 1, world->worldId, body->generation }; +} + +b3BodySim* b3GetBodySim( b3World* world, b3Body* body ) +{ + b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex ); + return bodySim; +} + +b3BodyState* b3GetBodyState( b3World* world, b3Body* body ) +{ + if ( body->setIndex == b3_awakeSet ) + { + b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet ); + return b3Array_Get( set->bodyStates, body->localIndex ); + } + + return NULL; +} + +void b3SyncBodyFlags( b3World* world, b3Body* body ) +{ + // Never sync transient flags + uint32_t flags = body->flags & ~b3_bodyTransientFlags; + + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->flags = flags; + + b3BodyState* bodyState = b3GetBodyState( world, body ); + if ( bodyState != NULL ) + { + bodyState->flags = flags; + } +} + +static void b3CreateIslandForBody( b3World* world, int setIndex, b3Body* body ) +{ + B3_ASSERT( body->islandId == B3_NULL_INDEX ); + B3_ASSERT( setIndex != b3_disabledSet ); + + b3Island* island = b3CreateIsland( world, setIndex ); + b3Array_Push( island->bodies, body->id ); + body->islandId = island->islandId; + body->islandIndex = 0; + + b3ValidateIsland( world, island->islandId ); +} + +static void b3RemoveBodyFromIsland( b3World* world, b3Body* body ) +{ + if ( body->islandId == B3_NULL_INDEX ) + { + B3_ASSERT( body->islandIndex == B3_NULL_INDEX ); + return; + } + + int islandId = body->islandId; + b3Island* island = b3Array_Get( world->islands, islandId ); + { + int localIndex = body->islandIndex; + int movedBodyId = island->bodies.data[island->bodies.count - 1]; + island->bodies.data[localIndex] = movedBodyId; + B3_VALIDATE( world->bodies.data[movedBodyId].islandIndex == island->bodies.count - 1 ); + world->bodies.data[movedBodyId].islandIndex = localIndex; + island->bodies.count -= 1; + } + + if ( island->bodies.count == 0 ) + { + // Destroy empty island + B3_ASSERT( island->contacts.count == 0 ); + B3_ASSERT( island->joints.count == 0 ); + + // Free the island + b3DestroyIsland( world, island->islandId ); + } + else + { + b3ValidateIsland( world, islandId ); + } + + body->islandId = B3_NULL_INDEX; + body->islandIndex = B3_NULL_INDEX; +} + +static void b3DestroyBodyContacts( b3World* world, b3Body* body, bool wakeBodies ) +{ + // Destroy the attached contacts + int edgeKey = body->headContactKey; + while ( edgeKey != B3_NULL_INDEX ) + { + int contactId = edgeKey >> 1; + int edgeIndex = edgeKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + edgeKey = contact->edges[edgeIndex].nextKey; + b3DestroyContact( world, contact, wakeBodies ); + } + + b3ValidateSolverSets( world ); +} + +b3BodyId b3CreateBody( b3WorldId worldId, const b3BodyDef* def ) +{ + B3_CHECK_DEF( def ); + B3_ASSERT( b3IsValidPosition( def->position ) ); + B3_ASSERT( b3IsValidQuat( def->rotation ) ); + B3_ASSERT( b3IsValidVec3( def->linearVelocity ) ); + B3_ASSERT( b3IsValidVec3( def->angularVelocity ) ); + B3_ASSERT( b3IsValidFloat( def->linearDamping ) && def->linearDamping >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->angularDamping ) && def->angularDamping >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->sleepThreshold ) && def->sleepThreshold >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->gravityScale ) ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + + if ( world == NULL ) + { + return b3_nullBodyId; + } + + world->locked = true; + + bool isAwake = ( def->isAwake || def->enableSleep == false ) && def->isEnabled; + + // determine the solver set + int setId; + if ( def->isEnabled == false ) + { + // any body type can be disabled + setId = b3_disabledSet; + } + else if ( def->type == b3_staticBody ) + { + setId = b3_staticSet; + } + else if ( isAwake == true ) + { + setId = b3_awakeSet; + } + else + { + // new set for a sleeping body in its own island + setId = b3AllocId( &world->solverSetIdPool ); + if ( setId == world->solverSets.count ) + { + // Create a zero initialized solver set. All sub-arrays are also zero initialized. + b3Array_Push( world->solverSets, (b3SolverSet){ 0 } ); + } + else + { + B3_ASSERT( world->solverSets.data[setId].setIndex == B3_NULL_INDEX ); + } + + world->solverSets.data[setId].setIndex = setId; + } + + B3_ASSERT( 0 <= setId && setId < world->solverSets.count ); + + int bodyId = b3AllocId( &world->bodyIdPool ); + + uint32_t lockFlags = 0; + lockFlags |= def->motionLocks.linearX ? b3_lockLinearX : 0; + lockFlags |= def->motionLocks.linearY ? b3_lockLinearY : 0; + lockFlags |= def->motionLocks.linearZ ? b3_lockLinearZ : 0; + lockFlags |= def->motionLocks.angularX ? b3_lockAngularX : 0; + lockFlags |= def->motionLocks.angularY ? b3_lockAngularY : 0; + lockFlags |= def->motionLocks.angularZ ? b3_lockAngularZ : 0; + + b3SolverSet* set = b3Array_Get( world->solverSets, setId ); + b3BodySim* bodySim = b3Array_Emplace( set->bodySims ); + *bodySim = (b3BodySim){ 0 }; + bodySim->transform.p = def->position; + bodySim->transform.q = def->rotation; + bodySim->center = def->position; + bodySim->rotation0 = bodySim->transform.q; + bodySim->center0 = bodySim->center; + bodySim->localCenter = b3Vec3_zero; + bodySim->force = b3Vec3_zero; + bodySim->torque = b3Vec3_zero; + bodySim->invMass = 0.0f; + bodySim->invInertiaLocal = b3Mat3_zero; + bodySim->minExtent = B3_HUGE; + bodySim->maxExtent = b3Vec3_zero; + bodySim->linearDamping = def->linearDamping; + bodySim->angularDamping = def->angularDamping; + bodySim->gravityScale = def->gravityScale; + bodySim->bodyId = bodyId; + bodySim->flags = lockFlags; + bodySim->flags |= def->isBullet ? b3_isBullet : 0; + bodySim->flags |= def->allowFastRotation ? b3_allowFastRotation : 0; + bodySim->flags |= def->type == b3_dynamicBody ? b3_dynamicFlag : 0; + bodySim->flags |= def->enableSleep ? b3_enableSleep : 0; + bodySim->flags |= def->enableContactRecycling ? b3_bodyEnableContactRecycling : 0; + + if ( setId == b3_awakeSet ) + { + b3BodyState* bodyState = b3Array_Emplace( set->bodyStates ); + + *bodyState = (b3BodyState){ 0 }; + bodyState->linearVelocity = def->linearVelocity; + bodyState->angularVelocity = def->angularVelocity; + bodyState->deltaRotation = b3Quat_identity; + bodyState->flags = bodySim->flags; + + bodySim->maxAngularVelocity = b3Length( def->angularVelocity ) + 5.0f; + } + + if ( bodyId == world->bodies.count ) + { + b3Array_Push( world->bodies, (b3Body){ 0 } ); + } + else + { + B3_ASSERT( world->bodies.data[bodyId].id == B3_NULL_INDEX ); + } + + b3Body* body = b3Array_Get( world->bodies, bodyId ); + body->userData = def->userData; + body->setIndex = setId; + body->localIndex = set->bodySims.count - 1; + body->generation += 1; + body->headShapeId = B3_NULL_INDEX; + body->shapeCount = 0; + body->headChainId = B3_NULL_INDEX; + body->headContactKey = B3_NULL_INDEX; + body->contactCount = 0; + body->headJointKey = B3_NULL_INDEX; + body->jointCount = 0; + body->islandId = B3_NULL_INDEX; + body->islandIndex = B3_NULL_INDEX; + body->bodyMoveIndex = B3_NULL_INDEX; + body->id = bodyId; + body->sleepThreshold = def->sleepThreshold; + body->sleepTime = 0.0f; + body->sleepVelocity = 0.0f; + body->mass = 0.0f; + body->inertia = b3Mat3_zero; + body->nameId = b3AddName( &world->names, def->name ); + body->type = def->type; + body->flags = bodySim->flags; + + // dynamic and kinematic bodies that are enabled need a island + if ( setId >= b3_awakeSet ) + { + b3CreateIslandForBody( world, setId, body ); + } + + b3ValidateSolverSets( world ); + + b3BodyId id = { bodyId + 1, world->worldId, body->generation }; + + world->locked = false; + + B3_REC_CREATE( world, CreateBody, id, worldId, *def ); + + return id; +} + +bool b3IsBodyAwake( b3World* world, b3Body* body ) +{ + B3_UNUSED( world ); + return body->setIndex == b3_awakeSet; +} + +bool b3WakeBody( b3World* world, b3Body* body ) +{ + if ( body->setIndex >= b3_firstSleepingSet ) + { + b3WakeSolverSet( world, body->setIndex ); + b3ValidateSolverSets( world ); + return true; + } + + return false; +} + +bool b3WakeBodyWithLock( b3World* world, b3Body* body ) +{ + B3_ASSERT( world->locked == false ); + world->locked = true; + bool woke = b3WakeBody( world, body ); + world->locked = false; + return woke; +} + +void b3DestroyBody( b3BodyId bodyId ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, DestroyBody, bodyId ); + + world->locked = true; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + // Wake bodies attached to this body, even if this body is static. + bool wakeBodies = true; + + // Destroy the attached joints + int edgeKey = body->headJointKey; + while ( edgeKey != B3_NULL_INDEX ) + { + int jointId = edgeKey >> 1; + int edgeIndex = edgeKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + edgeKey = joint->edges[edgeIndex].nextKey; + + // Careful because this modifies the list being traversed + b3DestroyJointInternal( world, joint, wakeBodies ); + } + + // Destroy all contacts attached to this body. + b3DestroyBodyContacts( world, body, wakeBodies ); + + // Destroy the attached shapes and their broad-phase proxies. + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + if ( shape->sensorIndex != B3_NULL_INDEX ) + { + b3DestroySensor( world, shape ); + } + + b3DestroyShapeProxy( shape, &world->broadPhase ); + + b3DestroyShapeAllocations( world, shape ); + + // Return shape to free list. + b3FreeId( &world->shapeIdPool, shapeId ); + shape->id = B3_NULL_INDEX; + + shapeId = shape->nextShapeId; + } + + b3RemoveBodyFromIsland( world, body ); + + // Remove body sim from solver set that owns it + b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex ); + int movedIndex = b3Array_RemoveSwap( set->bodySims, body->localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix moved body index + b3BodySim* movedSim = set->bodySims.data + body->localIndex; + int movedId = movedSim->bodyId; + b3Body* movedBody = b3Array_Get( world->bodies, movedId ); + B3_ASSERT( movedBody->localIndex == movedIndex ); + movedBody->localIndex = body->localIndex; + } + + // Remove body state from awake set + if ( body->setIndex == b3_awakeSet ) + { + int result = b3Array_RemoveSwap( set->bodyStates, body->localIndex ); + B3_UNUSED( result ); + B3_ASSERT( result == movedIndex ); + } + else if ( set->setIndex >= b3_firstSleepingSet && set->bodySims.count == 0 ) + { + // Remove solver set if it's now an orphan. + b3DestroySolverSet( world, set->setIndex ); + } + + // Free body and id (preserve body revision) + b3FreeId( &world->bodyIdPool, body->id ); + + body->setIndex = B3_NULL_INDEX; + body->localIndex = B3_NULL_INDEX; + body->id = B3_NULL_INDEX; + + b3ValidateSolverSets( world ); + + world->locked = false; +} + +int b3Body_GetContactCapacity( b3BodyId bodyId ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return 0; + } + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + // Conservative and fast + return body->contactCount; +} + +int b3Body_GetContactData( b3BodyId bodyId, b3ContactData* contactData, int capacity ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return 0; + } + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + int contactKey = body->headContactKey; + int index = 0; + while ( contactKey != B3_NULL_INDEX && index < capacity ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + + // Is contact touching? + if ( contact->flags & b3_contactTouchingFlag ) + { + b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA ); + b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB ); + + contactData[index].contactId = (b3ContactId){ contact->contactId + 1, bodyId.world0, 0, contact->generation }; + contactData[index].shapeIdA = (b3ShapeId){ shapeA->id + 1, bodyId.world0, shapeA->generation }; + contactData[index].shapeIdB = (b3ShapeId){ shapeB->id + 1, bodyId.world0, shapeB->generation }; + contactData[index].manifolds = contact->manifolds; + contactData[index].manifoldCount = contact->manifoldCount; + index += 1; + } + + contactKey = contact->edges[edgeIndex].nextKey; + } + + B3_ASSERT( index <= capacity ); + + return index; +} + +b3AABB b3Body_ComputeAABB( b3BodyId bodyId ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return (b3AABB){ 0 }; + } + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( body->headShapeId == B3_NULL_INDEX ) + { + b3WorldTransform transform = b3GetBodyTransform( world, body->id ); + b3Vec3 p = b3ToVec3( transform.p ); + return (b3AABB){ p, p }; + } + + b3Shape* shape = b3Array_Get( world->shapes, body->headShapeId ); + b3AABB aabb = shape->aabb; + while ( shape->nextShapeId != B3_NULL_INDEX ) + { + shape = b3Array_Get( world->shapes, shape->nextShapeId ); + aabb = b3AABB_Union( aabb, shape->aabb ); + } + + return aabb; +} + +float b3Body_GetClosestPoint( b3BodyId bodyId, b3Vec3* result, b3Vec3 target ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + *result = (b3Vec3){ 0 }; + return 0.0f; + } + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform worldTransform = b3GetBodyTransform( world, body->id ); + b3Transform transform = b3ToRelativeTransform( worldTransform, b3Pos_zero ); + + float closestDistance = FLT_MAX; + b3Vec3 closestPoint = transform.p; + + b3DistanceInput input = { 0 }; + input.proxyA = (b3ShapeProxy){ &target, 1, 0.0f }; + + // Target rides in frame A at the origin, so the relative pose of the shape in A is the body transform + input.transform = transform; + input.useRadii = false; + + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + + b3ShapeType type = shape->type; + if ( type != b3_sphereShape && type != b3_capsuleShape && type != b3_hullShape ) + { + continue; + } + + input.proxyB = b3MakeShapeProxy( shape ); + + b3SimplexCache cache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + if ( output.distance < closestDistance ) + { + closestDistance = output.distance; + closestPoint = output.pointB; + } + } + + *result = closestPoint; + return closestDistance; +} + +b3BodyCastResult b3Body_CastRay( b3BodyId bodyId, b3Pos origin, b3Vec3 translation, b3QueryFilter filter, float maxFraction, + b3WorldTransform bodyTransform ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return (b3BodyCastResult){ 0 }; + } + + b3BodyCastResult result = { 0 }; + b3Body* body = b3GetBodyFullId( world, bodyId ); + + // The consistent framing is to center on the ray origin. + b3RayCastInput shapeInput = { 0 }; + shapeInput.origin = b3Vec3_zero; + shapeInput.translation = translation; + shapeInput.maxFraction = maxFraction; + + b3Transform transform = b3ToRelativeTransform( bodyTransform, origin ); + + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + + if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false ) + { + continue; + } + + b3CastOutput shapeOutput = b3RayCastShape( shape, transform, &shapeInput ); + + if ( shapeOutput.hit == false ) + { + continue; + } + + if ( shapeOutput.fraction > shapeInput.maxFraction ) + { + continue; + } + + // Careful with id, shapeId is the next shape. + b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation }; + + int materialIndex = b3ClampInt( shapeOutput.materialIndex, 0, shape->materialCount - 1 ); + uint64_t userMaterialId = b3GetShapeMaterials( shape )[materialIndex].userMaterialId; + + result = (b3BodyCastResult){ + .shapeId = id, + .point = b3OffsetPos( origin, shapeOutput.point ), + .normal = shapeOutput.normal, + .fraction = shapeOutput.fraction, + .triangleIndex = shapeOutput.triangleIndex, + .userMaterialId = userMaterialId, + .iterations = shapeOutput.iterations, + .hit = true, + }; + + shapeInput.maxFraction = shapeOutput.fraction; + } + + return result; +} + +b3BodyCastResult b3Body_CastShape( b3BodyId bodyId, b3Pos origin, const b3ShapeProxy* proxy, b3Vec3 translation, + b3QueryFilter filter, float maxFraction, bool canEncroach, b3WorldTransform bodyTransform ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return (b3BodyCastResult){ 0 }; + } + + b3BodyCastResult result = { 0 }; + b3Body* body = b3GetBodyFullId( world, bodyId ); + + b3Transform transform = b3ToRelativeTransform( bodyTransform, origin ); + + b3ShapeCastInput shapeInput = { 0 }; + shapeInput.proxy = *proxy; + shapeInput.translation = translation; + shapeInput.maxFraction = maxFraction; + shapeInput.canEncroach = canEncroach; + + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + + if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false ) + { + continue; + } + + b3CastOutput shapeOutput = b3ShapeCastShape( shape, transform, &shapeInput ); + + if ( shapeOutput.hit == false ) + { + continue; + } + + if ( shapeOutput.fraction > shapeInput.maxFraction ) + { + continue; + } + + // Careful with id, shapeId is the next shape. + b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation }; + int materialIndex = b3ClampInt( shapeOutput.materialIndex, 0, shape->materialCount - 1 ); + uint64_t userMaterialId = b3GetShapeMaterials( shape )[materialIndex].userMaterialId; + + result = (b3BodyCastResult){ + .shapeId = id, + .point = b3OffsetPos( origin, shapeOutput.point ), + .normal = shapeOutput.normal, + .fraction = shapeOutput.fraction, + .triangleIndex = shapeOutput.triangleIndex, + .userMaterialId = userMaterialId, + .iterations = shapeOutput.iterations, + .hit = true, + }; + + shapeInput.maxFraction = shapeOutput.fraction; + } + + return result; +} + +bool b3Body_OverlapShape( b3BodyId bodyId, b3Pos origin, const b3ShapeProxy* proxy, b3QueryFilter filter, + b3WorldTransform bodyTransform ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return false; + } + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3Transform transform = b3ToRelativeTransform( bodyTransform, origin ); + + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + + if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false ) + { + continue; + } + + bool overlaps = b3OverlapShape( shape, transform, proxy ); + if ( overlaps ) + { + return true; + } + } + + return false; +} + +int b3Body_CollideMover( b3BodyId bodyId, b3BodyPlaneResult* bodyPlanes, int planeCapacity, b3Pos origin, const b3Capsule* mover, + b3QueryFilter filter, b3WorldTransform bodyTransform ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return 0; + } + + if ( planeCapacity == 0 ) + { + return 0; + } + + int resultCount = 0; + b3Body* body = b3GetBodyFullId( world, bodyId ); + + b3Transform transform = b3ToRelativeTransform( bodyTransform, origin ); + + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + + if ( b3ShouldQueryCollide( &shape->filter, &filter ) == false ) + { + continue; + } + + b3ShapeType type = shape->type; + if ( type != b3_sphereShape && type != b3_capsuleShape && type != b3_hullShape ) + { + continue; + } + + b3PlaneResult plane; + int count = b3CollideMover( &plane, 1, shape, transform, mover ); + + if ( count > 0 ) + { + b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation }; + bodyPlanes[resultCount] = (b3BodyPlaneResult){ .shapeId = id, .result = plane }; + resultCount += 1; + if ( resultCount == planeCapacity ) + { + return resultCount; + } + } + } + + return resultCount; +} + +void b3UpdateBodyMassData( b3World* world, b3Body* body ) +{ + b3BodySim* bodySim = b3GetBodySim( world, body ); + + // Mass is no longer dirty + body->flags &= ~b3_dirtyMass; + b3SyncBodyFlags( world, body ); + + // Compute mass data from shapes. Each shape has its own density. + body->mass = 0.0f; + body->inertia = b3Mat3_zero; + + bodySim->invMass = 0.0f; + bodySim->invInertiaLocal = b3Mat3_zero; + bodySim->invInertiaWorld = b3Mat3_zero; + bodySim->localCenter = b3Vec3_zero; + bodySim->minExtent = B3_HUGE; + bodySim->maxExtent = b3Vec3_zero; + + if ( body->headShapeId == B3_NULL_INDEX ) + { + return; + } + + // Static and kinematic sims have zero mass. + if ( body->type != b3_dynamicBody ) + { + bodySim->center = bodySim->transform.p; + bodySim->center0 = bodySim->center; + + // Need extents for kinematic bodies for sleeping to work correctly. + if ( body->type == b3_kinematicBody ) + { + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + const b3Shape* s = b3Array_Get( world->shapes, shapeId ); + + b3ShapeExtent extent = b3ComputeShapeExtent( s, b3Vec3_zero ); + bodySim->minExtent = b3MinFloat( bodySim->minExtent, extent.minExtent ); + bodySim->maxExtent = b3Max( bodySim->maxExtent, extent.maxExtent ); + + shapeId = s->nextShapeId; + } + } + + return; + } + + int shapeCount = body->shapeCount; + b3MassData* masses = b3StackAlloc( &world->stack, shapeCount * sizeof( b3MassData ), "mass data" ); + + // Accumulate mass over all shapes. + b3Vec3 localCenter = b3Vec3_zero; + int shapeId = body->headShapeId; + int shapeIndex = 0; + while ( shapeId != B3_NULL_INDEX ) + { + const b3Shape* s = b3Array_Get( world->shapes, shapeId ); + shapeId = s->nextShapeId; + + if ( s->density == 0.0f ) + { + masses[shapeIndex] = (b3MassData){ 0 }; + shapeIndex += 1; + continue; + } + + b3MassData massData = b3ComputeShapeMass( s ); + body->mass += massData.mass; + localCenter = b3MulAdd( localCenter, massData.mass, massData.center ); + + masses[shapeIndex] = massData; + shapeIndex += 1; + } + + // Compute center of mass. + if ( body->mass > 0.0f ) + { + bodySim->invMass = 1.0f / body->mass; + localCenter = b3MulSV( bodySim->invMass, localCenter ); + } + + // Second loop to accumulate the rotational inertia about the center of mass + for ( shapeIndex = 0; shapeIndex < shapeCount; ++shapeIndex ) + { + b3MassData massData = masses[shapeIndex]; + if ( massData.mass == 0.0f ) + { + continue; + } + + // Shift to center of mass. This is safe because it can only increase. + b3Vec3 offset = b3Sub( localCenter, massData.center ); + b3Matrix3 inertia = b3AddMM( massData.inertia, b3Steiner( massData.mass, offset ) ); + body->inertia = b3AddMM( body->inertia, inertia ); + } + + b3StackFree( &world->stack, masses ); + masses = NULL; + + float det = b3Det( body->inertia ); + B3_ASSERT( det >= 0.0f ); + + if ( det > 0.0f ) + { + // This call is faster than b3Invert + bodySim->invInertiaLocal = b3InvertT( body->inertia ); + + b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( bodySim->transform.q ); + bodySim->invInertiaWorld = b3MulMM( b3MulMM( rotationMatrix, bodySim->invInertiaLocal ), b3Transpose( rotationMatrix ) ); + } + + // Move center of mass. + b3Pos oldCenter = bodySim->center; + bodySim->localCenter = localCenter; + bodySim->center = b3TransformWorldPoint( bodySim->transform, bodySim->localCenter ); + bodySim->center0 = bodySim->center; + + // Update center of mass velocity + b3BodyState* state = b3GetBodyState( world, body ); + if ( state != NULL ) + { + b3Vec3 deltaLinear = b3Cross( state->angularVelocity, b3SubPos( bodySim->center, oldCenter ) ); + state->linearVelocity = b3Add( state->linearVelocity, deltaLinear ); + } + + // Compute body extents relative to center of mass + shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* s = b3Array_Get( world->shapes, shapeId ); + + b3ShapeExtent extent = b3ComputeShapeExtent( s, localCenter ); + bodySim->minExtent = b3MinFloat( bodySim->minExtent, extent.minExtent ); + bodySim->maxExtent = b3Max( bodySim->maxExtent, extent.maxExtent ); + + shapeId = s->nextShapeId; + } + + // Apply fixed rotation + if ( ( bodySim->flags & b3_fixedRotation ) == b3_fixedRotation ) + { + body->inertia = b3Mat3_zero; + bodySim->invInertiaLocal = b3Mat3_zero; + bodySim->invInertiaWorld = b3Mat3_zero; + } +} + +b3Pos b3Body_GetPosition( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + return transform.p; +} + +b3Quat b3Body_GetRotation( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + return transform.q; +} + +b3WorldTransform b3Body_GetTransform( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return b3GetBodyTransformQuick( world, body ); +} + +b3Vec3 b3Body_GetLocalPoint( b3BodyId bodyId, b3Pos worldPoint ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + return b3InvTransformWorldPoint( transform, worldPoint ); +} + +b3Pos b3Body_GetWorldPoint( b3BodyId bodyId, b3Vec3 localPoint ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + return b3TransformWorldPoint( transform, localPoint ); +} + +b3Vec3 b3Body_GetLocalVector( b3BodyId bodyId, b3Vec3 worldVector ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + return b3InvRotateVector( transform.q, worldVector ); +} + +b3Vec3 b3Body_GetWorldVector( b3BodyId bodyId, b3Vec3 localVector ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + return b3RotateVector( transform.q, localVector ); +} + +void b3Body_SetTransform( b3BodyId bodyId, b3Pos position, b3Quat rotation ) +{ + B3_ASSERT( b3IsValidPosition( position ) ); + B3_ASSERT( b3IsValidQuat( rotation ) ); + B3_ASSERT( b3Body_IsValid( bodyId ) ); + b3World* world = b3GetWorld( bodyId.world0 ); + B3_ASSERT( world->locked == false ); + + B3_REC( world, BodySetTransform, bodyId, position, rotation ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + + bodySim->transform.p = position; + bodySim->transform.q = rotation; + bodySim->center = b3TransformWorldPoint( bodySim->transform, bodySim->localCenter ); + + b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( bodySim->transform.q ); + bodySim->invInertiaWorld = b3MulMM( b3MulMM( rotationMatrix, bodySim->invInertiaLocal ), b3Transpose( rotationMatrix ) ); + + bodySim->rotation0 = bodySim->transform.q; + bodySim->center0 = bodySim->center; + + b3BroadPhase* broadPhase = &world->broadPhase; + + b3WorldTransform transform = bodySim->transform; + const float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + b3AABB aabb = b3ComputeFatShapeAABB( shape, transform, speculativeDistance ); + shape->aabb = aabb; + + if ( b3AABB_Contains( shape->fatAABB, aabb ) == false ) + { + float margin = shape->aabbMargin; + b3AABB fatAABB; + fatAABB.lowerBound.x = aabb.lowerBound.x - margin; + fatAABB.lowerBound.y = aabb.lowerBound.y - margin; + fatAABB.lowerBound.z = aabb.lowerBound.z - margin; + fatAABB.upperBound.x = aabb.upperBound.x + margin; + fatAABB.upperBound.y = aabb.upperBound.y + margin; + fatAABB.upperBound.z = aabb.upperBound.z + margin; + shape->fatAABB = fatAABB; + + // The body could be disabled + if ( shape->proxyKey != B3_NULL_INDEX ) + { + b3BroadPhase_MoveProxy( broadPhase, shape->proxyKey, fatAABB ); + } + } + + shapeId = shape->nextShapeId; + } +} + +b3Vec3 b3Body_GetLinearVelocity( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodyState* state = b3GetBodyState( world, body ); + if ( state != NULL ) + { + return state->linearVelocity; + } + return b3Vec3_zero; +} + +b3Vec3 b3Body_GetAngularVelocity( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodyState* state = b3GetBodyState( world, body ); + if ( state != NULL ) + { + return state->angularVelocity; + } + return b3Vec3_zero; +} + +void b3Body_SetLinearVelocity( b3BodyId bodyId, b3Vec3 linearVelocity ) +{ + B3_ASSERT( b3IsValidVec3( linearVelocity ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodySetLinearVelocity, bodyId, linearVelocity ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( body->type == b3_staticBody ) + { + return; + } + + if ( b3LengthSquared( linearVelocity ) > 0.0f ) + { + b3WakeBodyWithLock( world, body ); + } + + b3BodyState* state = b3GetBodyState( world, body ); + if ( state == NULL ) + { + return; + } + + state->linearVelocity = linearVelocity; +} + +void b3Body_SetAngularVelocity( b3BodyId bodyId, b3Vec3 angularVelocity ) +{ + B3_ASSERT( b3IsValidVec3( angularVelocity ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodySetAngularVelocity, bodyId, angularVelocity ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( body->type == b3_staticBody ) + { + return; + } + + // Apply locks to avoid waking + b3Vec3 w; + w.x = ( body->flags & b3_lockAngularX ) ? 0.0f : angularVelocity.x; + w.y = ( body->flags & b3_lockAngularY ) ? 0.0f : angularVelocity.y; + w.z = ( body->flags & b3_lockAngularZ ) ? 0.0f : angularVelocity.z; + + if ( b3LengthSquared( w ) != 0.0f ) + { + b3WakeBodyWithLock( world, body ); + } + + b3BodyState* state = b3GetBodyState( world, body ); + if ( state == NULL ) + { + return; + } + + state->angularVelocity = w; +} + +void b3Body_SetTargetTransform( b3BodyId bodyId, b3WorldTransform target, float timeStep, bool wake ) +{ + B3_ASSERT( b3IsValidWorldTransform( target ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodySetTargetTransform, bodyId, target, timeStep, wake ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( body->setIndex == b3_disabledSet ) + { + return; + } + + if ( body->type == b3_staticBody || timeStep <= 0.0f ) + { + return; + } + + if ( body->setIndex != b3_awakeSet && wake == false ) + { + return; + } + + b3BodySim* sim = b3GetBodySim( world, body ); + + // Compute linear velocity. The center difference is taken in world precision then demoted. + b3Pos center1 = sim->center; + b3Pos center2 = b3TransformWorldPoint( target, sim->localCenter ); + float invTimeStep = 1.0f / timeStep; + b3Vec3 linearVelocity = b3MulSV( invTimeStep, b3SubPos( center2, center1 ) ); + + // Compute angular velocity: + // q' = 0.5 * w * q + // <~> ( q2 - q1 ) / dt = 0.5 * w * q1 + // <=> w = 2 * ( q2 - q1 ) * Conjugate( q1 ) / dt + b3Quat q1 = sim->transform.q; + b3Quat q2 = target.q; + + // Use the shortest arc quaternion + if ( b3DotQuat( q1, q2 ) < 0.0f ) + { + q2 = b3NegateQuat( q2 ); + } + + b3Quat dq = { b3Sub( q2.v, q1.v ), q2.s - q1.s }; + b3Quat omega = b3MulQuat( dq, b3Conjugate( q1 ) ); + b3Vec3 angularVelocity = b3MulSV( 2.0f * invTimeStep, omega.v ); + + // Early out if the body is asleep already and the desired movement is small + if ( body->setIndex != b3_awakeSet ) + { + float maxVelocity = b3Length( linearVelocity ) + b3Length( b3Mul( angularVelocity, sim->maxExtent ) ); + + // Return if velocity would be sleepy + if ( maxVelocity < body->sleepThreshold ) + { + return; + } + + // Must wake for state to exist + b3WakeBodyWithLock( world, body ); + } + + B3_ASSERT( body->setIndex == b3_awakeSet ); + + b3BodyState* state = b3GetBodyState( world, body ); + state->linearVelocity = linearVelocity; + state->angularVelocity = angularVelocity; +} + +b3Vec3 b3Body_GetLocalPointVelocity( b3BodyId bodyId, b3Vec3 localPoint ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodyState* state = b3GetBodyState( world, body ); + if ( state == NULL ) + { + return b3Vec3_zero; + } + + b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex ); + + b3Vec3 r = b3RotateVector( bodySim->transform.q, b3Sub( localPoint, bodySim->localCenter ) ); + b3Vec3 v = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, r ) ); + return v; +} + +b3Vec3 b3Body_GetWorldPointVelocity( b3BodyId bodyId, b3Pos worldPoint ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodyState* state = b3GetBodyState( world, body ); + if ( state == NULL ) + { + return b3Vec3_zero; + } + + b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, body->localIndex ); + + b3Vec3 r = b3SubPos( worldPoint, bodySim->center ); + b3Vec3 v = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, r ) ); + return v; +} + +void b3Body_ApplyForce( b3BodyId bodyId, b3Vec3 force, b3Pos point, bool wake ) +{ + B3_ASSERT( b3IsValidVec3( force ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyApplyForce, bodyId, force, point, wake ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( wake && body->setIndex >= b3_firstSleepingSet ) + { + b3WakeBodyWithLock( world, body ); + } + + if ( body->setIndex == b3_awakeSet ) + { + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->force = b3Add( bodySim->force, force ); + bodySim->torque = b3Add( bodySim->torque, b3Cross( b3SubPos( point, bodySim->center ), force ) ); + } +} + +void b3Body_ApplyForceToCenter( b3BodyId bodyId, b3Vec3 force, bool wake ) +{ + B3_ASSERT( b3IsValidVec3( force ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyApplyForceToCenter, bodyId, force, wake ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( wake && body->setIndex >= b3_firstSleepingSet ) + { + b3WakeBodyWithLock( world, body ); + } + + if ( body->setIndex == b3_awakeSet ) + { + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->force = b3Add( bodySim->force, force ); + } +} + +void b3Body_ApplyTorque( b3BodyId bodyId, b3Vec3 torque, bool wake ) +{ + B3_ASSERT( b3IsValidVec3( torque ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyApplyTorque, bodyId, torque, wake ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( wake && body->setIndex >= b3_firstSleepingSet ) + { + b3WakeBodyWithLock( world, body ); + } + + if ( body->setIndex == b3_awakeSet ) + { + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->torque = b3Add( bodySim->torque, torque ); + } +} + +void b3Body_ApplyLinearImpulse( b3BodyId bodyId, b3Vec3 impulse, b3Pos point, bool wake ) +{ + B3_ASSERT( b3IsValidVec3( impulse ) ); + B3_ASSERT( b3IsValidPosition( point ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyApplyLinearImpulse, bodyId, impulse, point, wake ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( wake && body->setIndex >= b3_firstSleepingSet ) + { + b3WakeBodyWithLock( world, body ); + } + + if ( body->setIndex == b3_awakeSet ) + { + int localIndex = body->localIndex; + b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet ); + b3BodyState* state = b3Array_Get( set->bodyStates, localIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex ); + + state->linearVelocity = b3MulAdd( state->linearVelocity, bodySim->invMass, impulse ); + + float maxLinearSpeed = world->maxLinearSpeed; + if ( b3LengthSquared( state->linearVelocity ) > maxLinearSpeed * maxLinearSpeed ) + { + state->linearVelocity = b3MulSV( maxLinearSpeed, b3Normalize( state->linearVelocity ) ); + } + + b3Vec3 delta = b3MulMV( bodySim->invInertiaWorld, b3Cross( b3SubPos( point, bodySim->center ), impulse ) ); + state->angularVelocity = b3Add( state->angularVelocity, delta ); + } +} + +void b3Body_ApplyLinearImpulseToCenter( b3BodyId bodyId, b3Vec3 impulse, bool wake ) +{ + B3_ASSERT( b3IsValidVec3( impulse ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyApplyLinearImpulseToCenter, bodyId, impulse, wake ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( wake && body->setIndex >= b3_firstSleepingSet ) + { + b3WakeBodyWithLock( world, body ); + } + + if ( body->setIndex == b3_awakeSet ) + { + int localIndex = body->localIndex; + b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet ); + b3BodyState* state = b3Array_Get( set->bodyStates, localIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex ); + state->linearVelocity = b3MulAdd( state->linearVelocity, bodySim->invMass, impulse ); + + float maxLinearSpeed = world->maxLinearSpeed; + if ( b3LengthSquared( state->linearVelocity ) > maxLinearSpeed * maxLinearSpeed ) + { + state->linearVelocity = b3MulSV( maxLinearSpeed, b3Normalize( state->linearVelocity ) ); + } + } +} + +void b3Body_ApplyAngularImpulse( b3BodyId bodyId, b3Vec3 impulse, bool wake ) +{ + B3_ASSERT( b3IsValidVec3( impulse ) ); + B3_ASSERT( b3Body_IsValid( bodyId ) ); + + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyApplyAngularImpulse, bodyId, impulse, wake ); + + int id = bodyId.index1 - 1; + b3Body* body = b3Array_Get( world->bodies, id ); + B3_ASSERT( body->generation == bodyId.generation ); + + if ( wake && body->setIndex >= b3_firstSleepingSet ) + { + // this will not invalidate body pointer + b3WakeBodyWithLock( world, body ); + } + + if ( body->setIndex == b3_awakeSet ) + { + int localIndex = body->localIndex; + b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet ); + b3BodyState* state = b3Array_Get( set->bodyStates, localIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex ); + + b3Vec3 localImpulse = b3InvRotateVector( bodySim->transform.q, impulse ); + b3Vec3 localAngularVelocityDelta = b3MulMV( bodySim->invInertiaLocal, localImpulse ); + state->angularVelocity = + b3Add( state->angularVelocity, b3RotateVector( bodySim->transform.q, localAngularVelocityDelta ) ); + } +} + +b3BodyType b3Body_GetType( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->type; +} + +// This should follow similar steps as you would get destroying and recreating the body, shapes, and joints. +// Contacts are difficult to preserve because the broad-phase pairs change, so I just destroy them. +// todo with a bit more effort I could support an option to let the body sleep +// +// Revised steps: +// 1 Skip disabled bodies +// 2 Destroy all contacts on the body +// 3 Wake the body +// 4 For all joints attached to the body +// - wake attached bodies +// - remove from island +// - move to static set temporarily +// 5 Change the body type and transfer the body +// 6 If the body was static +// - create an island for the body +// Else if the body is becoming static +// - remove it from the island +// 7 For all joints +// - if either body is non-static +// - link into island +// - transfer to constraint graph +// 8 For all shapes +// - Destroy proxy in old tree +// - Create proxy in new tree +// Notes: +// - the implementation below tries to minimize the number of predicates, so some +// operations may have no effect, such as transferring a joint to the same set +void b3Body_SetType( b3BodyId bodyId, b3BodyType type ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetType, bodyId, (int32_t)type ); + + world->locked = true; + b3Body* body = b3GetBodyFullId( world, bodyId ); + + b3BodyType originalType = body->type; + if ( originalType == type ) + { + world->locked = false; + return; + } + + if ( type != b3_staticBody ) + { + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + if ( shape->type == b3_compoundShape || shape->type == b3_heightShape ) + { + // Setting the body type is not supported for bodies with compound shapes + return; + } + + shapeId = shape->nextShapeId; + } + } + + // Stage 1: skip disabled bodies + if ( body->setIndex == b3_disabledSet ) + { + // Disabled bodies don't change solver sets or islands when they change type. + body->type = type; + + if ( type == b3_dynamicBody ) + { + body->flags |= b3_dynamicFlag; + } + else + { + body->flags &= ~b3_dynamicFlag; + } + + b3SyncBodyFlags( world, body ); + + // Body type affects the mass properties + b3UpdateBodyMassData( world, body ); + world->locked = false; + return; + } + + // Stage 2: destroy all contacts but don't wake bodies (because we don't need to) + bool wakeBodies = false; + b3DestroyBodyContacts( world, body, wakeBodies ); + + // Stage 3: wake this body (does nothing if body is static), otherwise it will also wake + // all bodies in the same sleeping solver set. + b3WakeBody( world, body ); + + // Stage 4: move joints to temporary storage + b3SolverSet* staticSet = b3Array_Get( world->solverSets, b3_staticSet ); + + int jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + jointKey = joint->edges[edgeIndex].nextKey; + + // Joint may be disabled by other body + if ( joint->setIndex == b3_disabledSet ) + { + continue; + } + + // Wake attached bodies. The b3WakeBody call above does not wake bodies + // attached to a static body. But it is necessary because the body may have + // no joints. + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + b3WakeBody( world, bodyA ); + b3WakeBody( world, bodyB ); + + // Remove joint from island + b3UnlinkJoint( world, joint ); + + // It is necessary to transfer all joints to the static set + // so they can be added to the constraint graph below and acquire consistent colors. + b3SolverSet* jointSourceSet = b3Array_Get( world->solverSets, joint->setIndex ); + b3TransferJoint( world, staticSet, jointSourceSet, joint ); + } + + // Stage 5: change the body type and transfer body + body->type = type; + + if ( type == b3_dynamicBody ) + { + body->flags |= b3_dynamicFlag; + } + else + { + body->flags &= ~b3_dynamicFlag; + } + + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + b3SolverSet* sourceSet = b3Array_Get( world->solverSets, body->setIndex ); + b3SolverSet* targetSet = type == b3_staticBody ? staticSet : awakeSet; + + // Transfer body + b3TransferBody( world, targetSet, sourceSet, body ); + + // Stage 6: update island participation for the body + if ( originalType == b3_staticBody ) + { + // Create island for body + b3CreateIslandForBody( world, b3_awakeSet, body ); + } + else if ( type == b3_staticBody ) + { + // Remove body from island. + b3RemoveBodyFromIsland( world, body ); + } + + // Stage 7: Transfer joints to the target set + jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + + jointKey = joint->edges[edgeIndex].nextKey; + + // Joint may be disabled by other body + if ( joint->setIndex == b3_disabledSet ) + { + continue; + } + + // All joints were transferred to the static set in an earlier stage + B3_ASSERT( joint->setIndex == b3_staticSet ); + + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + B3_ASSERT( bodyA->setIndex == b3_staticSet || bodyA->setIndex == b3_awakeSet ); + B3_ASSERT( bodyB->setIndex == b3_staticSet || bodyB->setIndex == b3_awakeSet ); + + if ( bodyA->type == b3_dynamicBody || bodyB->type == b3_dynamicBody ) + { + b3TransferJoint( world, awakeSet, staticSet, joint ); + } + } + + // Recreate shape proxies in broadphase + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + // Setting the body type is not supported for bodies with compound shapes + B3_ASSERT( shape->type != b3_compoundShape ); + + shapeId = shape->nextShapeId; + b3DestroyShapeProxy( shape, &world->broadPhase ); + bool forcePairCreation = true; + b3CreateShapeProxy( shape, &world->broadPhase, type, transform, forcePairCreation ); + } + + // Relink all joints + jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + jointKey = joint->edges[edgeIndex].nextKey; + + int otherEdgeIndex = edgeIndex ^ 1; + int otherBodyId = joint->edges[otherEdgeIndex].bodyId; + b3Body* otherBody = b3Array_Get( world->bodies, otherBodyId ); + + if ( otherBody->setIndex == b3_disabledSet ) + { + continue; + } + + if ( body->type != b3_dynamicBody && otherBody->type != b3_dynamicBody ) + { + continue; + } + + b3LinkJoint( world, joint ); + } + + b3SyncBodyFlags( world, body ); + + // Body type affects the mass + b3UpdateBodyMassData( world, body ); + + b3ValidateSolverSets( world ); + b3ValidateIsland( world, body->islandId ); + + world->locked = false; +} + +void b3Body_SetName( b3BodyId bodyId, const char* name ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodySetName, bodyId, name ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + body->nameId = b3AddName( &world->names, name ); +} + +const char* b3Body_GetName( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return b3FindNameWithDefault( &world->names, body->nameId, "" ); +} + +void b3Body_SetUserData( b3BodyId bodyId, void* userData ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + body->userData = userData; +} + +void* b3Body_GetUserData( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->userData; +} + +float b3Body_GetMass( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->mass; +} + +b3Matrix3 b3Body_GetLocalRotationalInertia( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->inertia; +} + +float b3Body_GetInverseMass( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* sim = b3GetBodySim( world, body ); + return sim->invMass; +} + +b3Matrix3 b3Body_GetWorldInverseRotationalInertia( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* sim = b3GetBodySim( world, body ); + return sim->invInertiaWorld; +} + +b3Vec3 b3Body_GetLocalCenter( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + return bodySim->localCenter; +} + +b3Pos b3Body_GetWorldCenter( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + return bodySim->center; +} + +void b3Body_SetMassData( b3BodyId bodyId, b3MassData massData ) +{ + B3_ASSERT( b3IsValidFloat( massData.mass ) && massData.mass >= 0.0f ); + B3_ASSERT( b3IsValidMatrix3( massData.inertia ) ); + B3_ASSERT( b3IsValidVec3( massData.center ) ); + + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetMassData, bodyId, massData ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + + // Mass is no longer dirty + body->flags &= ~b3_dirtyMass; + b3SyncBodyFlags( world, body ); + + body->mass = massData.mass; + body->inertia = massData.inertia; + bodySim->localCenter = massData.center; + + b3Pos oldCenter = bodySim->center; + b3Pos center = b3TransformWorldPoint( bodySim->transform, massData.center ); + bodySim->center = center; + bodySim->center0 = center; + bodySim->invMass = body->mass > 0.0f ? 1.0f / body->mass : 0.0f; + + // Update center of mass velocity + b3BodyState* state = b3GetBodyState( world, body ); + if ( state != NULL ) + { + b3Vec3 deltaLinear = b3Cross( state->angularVelocity, b3SubPos( bodySim->center, oldCenter ) ); + state->linearVelocity = b3Add( state->linearVelocity, deltaLinear ); + } + + float det = b3Det( body->inertia ); + B3_ASSERT( det >= 0.0f ); + + if ( det > 0.0f ) + { + // This call is faster than b3Invert + bodySim->invInertiaLocal = b3InvertT( body->inertia ); + + b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( bodySim->transform.q ); + bodySim->invInertiaWorld = b3MulMM( b3MulMM( rotationMatrix, bodySim->invInertiaLocal ), b3Transpose( rotationMatrix ) ); + } + else + { + bodySim->invInertiaLocal = b3Mat3_zero; + bodySim->invInertiaWorld = b3Mat3_zero; + } + + // Apply fixed rotation + if ( ( bodySim->flags & b3_fixedRotation ) == b3_fixedRotation ) + { + body->inertia = b3Mat3_zero; + bodySim->invInertiaLocal = b3Mat3_zero; + bodySim->invInertiaWorld = b3Mat3_zero; + } + + // Update extents using supplied mass center. + bodySim->minExtent = B3_HUGE; + bodySim->maxExtent = b3Vec3_zero; + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + const b3Shape* s = b3Array_Get( world->shapes, shapeId ); + b3ShapeExtent extent = b3ComputeShapeExtent( s, massData.center ); + bodySim->minExtent = b3MinFloat( bodySim->minExtent, extent.minExtent ); + bodySim->maxExtent = b3Max( bodySim->maxExtent, extent.maxExtent ); + shapeId = s->nextShapeId; + } +} + +b3MassData b3Body_GetMassData( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + b3MassData massData = { body->mass, bodySim->localCenter, body->inertia }; + return massData; +} + +void b3Body_ApplyMassFromShapes( b3BodyId bodyId ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodyApplyMassFromShapes, bodyId ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3UpdateBodyMassData( world, body ); +} + +void b3Body_SetLinearDamping( b3BodyId bodyId, float linearDamping ) +{ + B3_ASSERT( b3IsValidFloat( linearDamping ) && linearDamping >= 0.0f ); + + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetLinearDamping, bodyId, linearDamping ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->linearDamping = linearDamping; +} + +float b3Body_GetLinearDamping( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + return bodySim->linearDamping; +} + +void b3Body_SetAngularDamping( b3BodyId bodyId, float angularDamping ) +{ + B3_ASSERT( b3IsValidFloat( angularDamping ) && angularDamping >= 0.0f ); + + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetAngularDamping, bodyId, angularDamping ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->angularDamping = angularDamping; +} + +float b3Body_GetAngularDamping( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + return bodySim->angularDamping; +} + +void b3Body_SetGravityScale( b3BodyId bodyId, float gravityScale ) +{ + B3_ASSERT( b3Body_IsValid( bodyId ) ); + B3_ASSERT( b3IsValidFloat( gravityScale ) ); + + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetGravityScale, bodyId, gravityScale ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + bodySim->gravityScale = gravityScale; +} + +float b3Body_GetGravityScale( b3BodyId bodyId ) +{ + B3_ASSERT( b3Body_IsValid( bodyId ) ); + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + return bodySim->gravityScale; +} + +bool b3Body_IsAwake( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->setIndex == b3_awakeSet; +} + +void b3Body_SetAwake( b3BodyId bodyId, bool awake ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetAwake, bodyId, awake ); + + world->locked = true; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + if ( awake && body->setIndex >= b3_firstSleepingSet ) + { + b3WakeBody( world, body ); + } + else if ( awake == false && body->setIndex == b3_awakeSet ) + { + b3Island* island = b3Array_Get( world->islands, body->islandId ); + if ( island->constraintRemoveCount > 0 ) + { + // Must split the island before sleeping. This is expensive. + b3SplitIsland( world, body->islandId ); + } + + b3TrySleepIsland( world, body->islandId ); + } + + world->locked = false; +} + +bool b3Body_IsEnabled( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->setIndex != b3_disabledSet; +} + +bool b3Body_IsSleepEnabled( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return ( body->flags & b3_enableSleep ) == b3_enableSleep; +} + +void b3Body_SetSleepThreshold( b3BodyId bodyId, float sleepThreshold ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodySetSleepThreshold, bodyId, sleepThreshold ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + body->sleepThreshold = sleepThreshold; +} + +float b3Body_GetSleepThreshold( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->sleepThreshold; +} + +void b3Body_EnableSleep( b3BodyId bodyId, bool enableSleep ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodyEnableSleep, bodyId, enableSleep ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + + bool flag = ( body->flags & b3_enableSleep ) == b3_enableSleep; + if ( enableSleep == flag ) + { + return; + } + + world->locked = true; + + body->flags = enableSleep ? body->flags | b3_enableSleep : body->flags & ~b3_enableSleep; + b3SyncBodyFlags( world, body ); + + if ( enableSleep == false ) + { + b3WakeBody( world, body ); + } + + world->locked = false; +} + +// Disabling a body requires a lot of detailed bookkeeping, but it is a valuable feature. +// The most challenging aspect that joints may connect to bodies that are not disabled. +void b3Body_Disable( b3BodyId bodyId ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodyDisable, bodyId ); + + world->locked = true; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( body->setIndex == b3_disabledSet ) + { + world->locked = false; + return; + } + + // Destroy contacts and wake bodies touching this body. This avoid floating bodies. + // This is necessary even for static bodies. + bool wakeBodies = true; + b3DestroyBodyContacts( world, body, wakeBodies ); + + // The current solver set of the body + b3SolverSet* set = b3Array_Get( world->solverSets, body->setIndex ); + + // Disabled bodies and connected joints are moved to the disabled set + b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet ); + + // Unlink joints and transfer them to the disabled set + int jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + jointKey = joint->edges[edgeIndex].nextKey; + + // joint may already be disabled by other body + if ( joint->setIndex == b3_disabledSet ) + { + continue; + } + + B3_ASSERT( joint->setIndex == set->setIndex || set->setIndex == b3_staticSet ); + + // Remove joint from island + b3UnlinkJoint( world, joint ); + + // Transfer joint to disabled set + b3SolverSet* jointSet = b3Array_Get( world->solverSets, joint->setIndex ); + b3TransferJoint( world, disabledSet, jointSet, joint ); + } + + // Remove shapes from broad-phase + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + b3DestroyShapeProxy( shape, &world->broadPhase ); + } + + // Disabled bodies are not in an island. If the island becomes empty it will be destroyed. + b3RemoveBodyFromIsland( world, body ); + + // Transfer body sim + b3TransferBody( world, disabledSet, set, body ); + + b3ValidateConnectivity( world ); + b3ValidateSolverSets( world ); + + world->locked = false; +} + +void b3Body_Enable( b3BodyId bodyId ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodyEnable, bodyId ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( body->setIndex != b3_disabledSet ) + { + return; + } + + b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet ); + int setId = body->type == b3_staticBody ? b3_staticSet : b3_awakeSet; + b3SolverSet* targetSet = b3Array_Get( world->solverSets, setId ); + + b3TransferBody( world, targetSet, disabledSet, body ); + + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + + // Add shapes to broad-phase + b3BodyType proxyType = body->type; + bool forcePairCreation = true; + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shapeId = shape->nextShapeId; + + b3CreateShapeProxy( shape, &world->broadPhase, proxyType, transform, forcePairCreation ); + } + + if ( setId != b3_staticSet ) + { + b3CreateIslandForBody( world, setId, body ); + } + + // Transfer joints. If the other body is disabled, don't transfer. + // If the other body is sleeping, wake it. + int jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + B3_ASSERT( joint->setIndex == b3_disabledSet ); + B3_ASSERT( joint->islandId == B3_NULL_INDEX ); + + jointKey = joint->edges[edgeIndex].nextKey; + + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + + if ( bodyA->setIndex == b3_disabledSet || bodyB->setIndex == b3_disabledSet ) + { + // one body is still disabled + continue; + } + + // Transfer joint first + int jointSetId; + if ( bodyA->setIndex == b3_staticSet && bodyB->setIndex == b3_staticSet ) + { + jointSetId = b3_staticSet; + } + else if ( bodyA->setIndex == b3_staticSet ) + { + jointSetId = bodyB->setIndex; + } + else + { + jointSetId = bodyA->setIndex; + } + + b3SolverSet* jointSet = b3Array_Get( world->solverSets, jointSetId ); + b3TransferJoint( world, jointSet, disabledSet, joint ); + + // Now that the joint is in the correct set, I can link the joint in the island. + if ( jointSetId != b3_staticSet ) + { + b3LinkJoint( world, joint ); + } + } + + b3ValidateSolverSets( world ); +} + +void b3Body_SetMotionLocks( b3BodyId bodyId, b3MotionLocks locks ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetMotionLocks, bodyId, locks ); + + uint32_t newLocks = 0; + newLocks |= locks.linearX ? b3_lockLinearX : 0; + newLocks |= locks.linearY ? b3_lockLinearY : 0; + newLocks |= locks.linearZ ? b3_lockLinearZ : 0; + newLocks |= locks.angularX ? b3_lockAngularX : 0; + newLocks |= locks.angularY ? b3_lockAngularY : 0; + newLocks |= locks.angularZ ? b3_lockAngularZ : 0; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( ( body->flags & b3_allLocks ) == newLocks ) + { + return; + } + + bool fixedRotation1 = ( body->flags & b3_fixedRotation ) == b3_fixedRotation; + bool fixedRotation2 = ( newLocks & b3_fixedRotation ) == b3_fixedRotation; + + body->flags &= ~b3_allLocks; + body->flags |= newLocks; + + b3SyncBodyFlags( world, body ); + + b3BodyState* state = b3GetBodyState( world, body ); + + if ( state != NULL ) + { + if ( locks.linearX ) + { + state->linearVelocity.x = 0.0f; + } + + if ( locks.linearY ) + { + state->linearVelocity.y = 0.0f; + } + + if ( locks.linearZ ) + { + state->linearVelocity.z = 0.0f; + } + + if ( locks.angularX ) + { + state->angularVelocity.x = 0.0f; + } + + if ( locks.angularY ) + { + state->angularVelocity.y = 0.0f; + } + + if ( locks.angularZ ) + { + state->angularVelocity.z = 0.0f; + } + } + + if ( fixedRotation1 != fixedRotation2 ) + { + b3UpdateBodyMassData( world, body ); + } +} + +b3MotionLocks b3Body_GetMotionLocks( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + + b3MotionLocks locks; + locks.linearX = ( body->flags & b3_lockLinearX ); + locks.linearY = ( body->flags & b3_lockLinearY ); + locks.linearZ = ( body->flags & b3_lockLinearZ ); + locks.angularX = ( body->flags & b3_lockAngularX ); + locks.angularY = ( body->flags & b3_lockAngularY ); + locks.angularZ = ( body->flags & b3_lockAngularZ ); + return locks; +} + +void b3Body_SetBullet( b3BodyId bodyId, bool flag ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodySetBullet, bodyId, flag ); + + uint32_t newFlag = flag ? b3_isBullet : 0; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( ( body->flags & b3_isBullet ) == newFlag ) + { + return; + } + + body->flags &= ~b3_isBullet; + body->flags |= newFlag; + + b3SyncBodyFlags( world, body ); +} + +bool b3Body_IsBullet( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return ( body->flags & b3_isBullet ) != 0; +} + +void b3Body_EnableContactRecycling( b3BodyId bodyId, bool flag ) +{ + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, BodyEnableContactRecycling, bodyId, flag ); + + uint32_t newFlag = flag ? b3_bodyEnableContactRecycling : 0; + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( ( body->flags & b3_bodyEnableContactRecycling ) == newFlag ) + { + return; + } + + body->flags &= ~b3_bodyEnableContactRecycling; + body->flags |= newFlag; + + b3SyncBodyFlags( world, body ); +} + +bool b3Body_IsContactRecyclingEnabled( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return ( body->flags & b3_bodyEnableContactRecycling ) != 0; +} + +void b3Body_EnableHitEvents( b3BodyId bodyId, bool flag ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + + B3_REC( world, BodyEnableHitEvents, bodyId, flag ); + + b3Body* body = b3GetBodyFullId( world, bodyId ); + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + shape->flags = flag ? shape->flags | b3_enableHitEvents : shape->flags & ~b3_enableHitEvents; + shapeId = shape->nextShapeId; + } +} + +b3WorldId b3Body_GetWorld( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + return (b3WorldId){ (uint16_t)( bodyId.world0 + 1 ), world->generation }; +} + +int b3Body_GetShapeCount( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->shapeCount; +} + +int b3Body_GetShapes( b3BodyId bodyId, b3ShapeId* shapeArray, int capacity ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + int shapeId = body->headShapeId; + int shapeCount = 0; + while ( shapeId != B3_NULL_INDEX && shapeCount < capacity ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation }; + shapeArray[shapeCount] = id; + shapeCount += 1; + + shapeId = shape->nextShapeId; + } + + return shapeCount; +} + +int b3Body_GetJointCount( b3BodyId bodyId ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + return body->jointCount; +} + +int b3Body_GetJoints( b3BodyId bodyId, b3JointId* jointArray, int capacity ) +{ + b3World* world = b3GetWorld( bodyId.world0 ); + b3Body* body = b3GetBodyFullId( world, bodyId ); + int jointKey = body->headJointKey; + + int jointCount = 0; + while ( jointKey != B3_NULL_INDEX && jointCount < capacity ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + + b3JointId id = { jointId + 1, bodyId.world0, joint->generation }; + jointArray[jointCount] = id; + jointCount += 1; + + jointKey = joint->edges[edgeIndex].nextKey; + } + + return jointCount; +} + +bool b3ShouldBodiesCollide( b3World* world, b3Body* bodyA, b3Body* bodyB ) +{ + if ( bodyA->type != b3_dynamicBody && bodyB->type != b3_dynamicBody ) + { + return false; + } + + int jointKey; + int otherBodyId; + if ( bodyA->jointCount < bodyB->jointCount ) + { + jointKey = bodyA->headJointKey; + otherBodyId = bodyB->id; + } + else + { + jointKey = bodyB->headJointKey; + otherBodyId = bodyA->id; + } + + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + int otherEdgeIndex = edgeIndex ^ 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + if ( joint->collideConnected == false && joint->edges[otherEdgeIndex].bodyId == otherBodyId ) + { + return false; + } + + jointKey = joint->edges[edgeIndex].nextKey; + } + + return true; +} diff --git a/vendor/box3d/src/src/body.h b/vendor/box3d/src/src/body.h new file mode 100644 index 000000000..5eebf67ab --- /dev/null +++ b/vendor/box3d/src/src/body.h @@ -0,0 +1,256 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/constants.h" +#include "box3d/math_functions.h" +#include "box3d/types.h" + +typedef struct b3World b3World; + +enum b3BodyFlags +{ + // This body has fixed translation along the x-axis + b3_lockLinearX = 0x00000001, + + // This body has fixed translation along the y-axis + b3_lockLinearY = 0x00000002, + + // This body has fixed translation along the z-axis + b3_lockLinearZ = 0x00000004, + + // This body has fixed rotation around the x-axis + b3_lockAngularX = 0x00000008, + + // This body has fixed rotation around the y-axis + b3_lockAngularY = 0x00000010, + + // This body has fixed rotation around the z-axis + b3_lockAngularZ = 0x00000020, + + // This flag is used for debug draw + b3_isFast = 0x00000040, + + // This dynamic body does a final CCD pass against all body types, but not other bullets + b3_isBullet = 0x00000080, + + // This body was speed capped in the current time step + b3_isSpeedCapped = 0x00000100, + + // This body had a time of impact event in the current time step + b3_hadTimeOfImpact = 0x00000200, + + // This body has no limit on angular velocity + b3_allowFastRotation = 0x00000400, + + // This body need's to have its AABB increased + b3_enlargeBounds = 0x00000800, + + // This body is dynamic so the solver should write to it. + // This prevents writing to kinematic bodies that causes a multithreaded sharing + // cache coherence problem even when the values are not changing. + // Used for b3BodyState flags. + b3_dynamicFlag = 0x00001000, + + b3_enableSleep = 0x00002000, + + b3_bodyEnableContactRecycling = 0x00004000, + + // The user deferred mass computation via the updateBodyMass shape option and mass + // data still hasn't been set. + b3_dirtyMass = 0x00008000, + + // All lock flags + b3_allLocks = b3_lockLinearX | b3_lockLinearY | b3_lockLinearZ | b3_lockAngularX | b3_lockAngularY | b3_lockAngularZ, + + // If all these flags are set then the body has fixed rotation + b3_fixedRotation = b3_lockAngularX | b3_lockAngularY | b3_lockAngularZ, + + // These flags are transient per time step. These may be different across b3Body, b3BodySim, and b3BodyState. + b3_bodyTransientFlags = b3_isFast | b3_isSpeedCapped | b3_hadTimeOfImpact, +}; + +// Body organizational details that are not used in the solver. +typedef struct b3Body +{ + void* userData; + + // index of solver set stored in b3World + // may be B3_NULL_INDEX + int setIndex; + + // body sim and state index within set + // may be B3_NULL_INDEX + int localIndex; + + // [31 : contactId | 1 : edgeIndex] + int headContactKey; + int contactCount; + + // todo maybe move this to the body sim + int headShapeId; + int shapeCount; + + int headChainId; + + // [31 : jointId | 1 : edgeIndex] + int headJointKey; + int jointCount; + + // All enabled dynamic and kinematic bodies are in an island. + int islandId; + + // Index into the island's bodies array for O(1) swap-removal. + // B3_NULL_INDEX when not in an island. + int islandIndex; + + float sleepThreshold; + float sleepTime; + float sleepVelocity; + float mass; + + // local space inertia + b3Matrix3 inertia; + + // this is used to adjust the fellAsleep flag in the body move array + int bodyMoveIndex; + + int id; + + // b3BodyFlags + uint32_t flags; + uint32_t nameId; + + b3BodyType type; + + // This is monotonically advanced when a body is allocated in this slot + // Used to check for invalid b3BodyId + uint16_t generation; +} b3Body; + +// Body State +// The body state is designed for fast conversion to and from SIMD via scatter-gather. +// Only awake dynamic and kinematic bodies have a body state. +// This is used in the performance critical constraint solver +// +// The solver operates on the body state. The body state array does not hold static bodies. Static bodies are shared +// across worker threads. It would be okay to read their states, but writing to them would cause cache thrashing across +// workers, even if the values don't change. +// This causes some trouble when computing anchors. I rotate joint anchors using the body rotation every sub-step. For static +// bodies the anchor doesn't rotate. Body A or B could be static and this can lead to lots of branching. This branching +// should be minimized. +// +// Solution 1: +// Use delta rotations. This means anchors need to be prepared in world space. The delta rotation for static bodies will be +// identity using a dummy state. Base separation and angles need to be computed. Manifolds will be behind a frame, but that +// is probably best if bodies move fast. +// +// Solution 2: +// Use full rotation. The anchors for static bodies will be in world space while the anchors for dynamic bodies will be in local +// space. Potentially confusing and bug prone. +// +// Note: +// I rotate joint anchors each sub-step but not contact anchors. Joint stability improves a lot by rotating joint anchors +// according to substep progress. Contacts have reduced stability when anchors are rotated during substeps, especially for +// round shapes. +// +// 56 bytes +// todo_erin measure perf padding to 64 bytes +typedef struct b3BodyState +{ + b3Vec3 linearVelocity; // 12 + b3Vec3 angularVelocity; // 12 + + // Using delta position reduces round-off error far from the origin + b3Vec3 deltaPosition; // 12 + + // Using delta rotation because I cannot access the full rotation on static bodies in + // the solver and must use zero delta rotation for static bodies (c,s) = (1,0) + b3Quat deltaRotation; // 16 + + // b3BodyFlags + // Important flags: locking, dynamic + uint32_t flags; // 4 +} b3BodyState; + +// Identity body state, notice the deltaRotation is identity +static const b3BodyState b3_identityBodyState = { + { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f }, { { 0.0f, 0.0f, 0.0f }, 1.0f }, 0, +}; + +// Body simulation data used for integration of position and velocity +// Transform data used for collision and solver preparation. +typedef struct b3BodySim +{ + // transform for body origin, double translation in large world mode + b3WorldTransform transform; + + // center of mass position in world space + b3Pos center; + + // previous rotation and COM for TOI + b3Quat rotation0; + b3Pos center0; + + // location of center of mass relative to the body origin + b3Vec3 localCenter; + + b3Vec3 force; + b3Vec3 torque; + + float invMass; + + // Rotational inertia about the center of mass. The world space inverse inertia tensor + // must be updated whenever the body rotation is modified. + b3Matrix3 invInertiaLocal; + b3Matrix3 invInertiaWorld; + + float minExtent; + b3Vec3 maxExtent; + float maxAngularVelocity; + float linearDamping; + float angularDamping; + float gravityScale; + + // Index of b3Body + int bodyId; + + // b3BodyFlags + uint32_t flags; +} b3BodySim; + +// Get a validated body from a world using an id. +b3Body* b3GetBodyFullId( b3World* world, b3BodyId bodyId ); + +b3WorldTransform b3GetBodyTransformQuick( b3World* world, b3Body* body ); +b3WorldTransform b3GetBodyTransform( b3World* world, int bodyId ); + +// Create a b3BodyId from a raw id. +b3BodyId b3MakeBodyId( b3World* world, int bodyId ); + +bool b3ShouldBodiesCollide( b3World* world, b3Body* bodyA, b3Body* bodyB ); +bool b3IsBodyAwake( b3World* world, b3Body* body ); + +b3BodySim* b3GetBodySim( b3World* world, b3Body* body ); +b3BodyState* b3GetBodyState( b3World* world, b3Body* body ); + +// careful calling this because it can invalidate body, state, joint, and contact pointers +bool b3WakeBody( b3World* world, b3Body* body ); +bool b3WakeBodyWithLock( b3World* world, b3Body* body ); + +void b3UpdateBodyMassData( b3World* world, b3Body* body ); +void b3SyncBodyFlags( b3World* world, b3Body* body ); + +// Make a sweep relative to a base position to keep TOI in float precision far from the origin. +static inline b3Sweep b3MakeRelativeSweep( const b3BodySim* bodySim, b3Pos base ) +{ + b3Sweep s; + s.c1 = b3SubPos( bodySim->center0, base ); + s.c2 = b3SubPos( bodySim->center, base ); + s.q1 = bodySim->rotation0; + s.q2 = bodySim->transform.q; + s.localCenter = bodySim->localCenter; + return s; +} + diff --git a/vendor/box3d/src/src/box3d.natvis b/vendor/box3d/src/src/box3d.natvis new file mode 100644 index 000000000..68d36e1c2 --- /dev/null +++ b/vendor/box3d/src/src/box3d.natvis @@ -0,0 +1,147 @@ + + + + + + [ {x}, {y} ] + + + + [ {x}, {y}, {z} ] + + + + [ {v}, {s} ] + + + + + [ {x}, {y}, {z} ] + + + + [ {p}, {q} ] + + + + index: {index1} + + b3_worlds[ index1 - 1 ] + + + + + + {b3_worlds[world0].bodies.data[index1-1].name,na}, + {b3_worlds[world0].bodies.data[index1-1].type} + + + b3_worlds[ world0 ].bodies.data[index1-1] + + + + + + {b3_worlds[world0].shapes.data[index1-1].type} + + + + + + {point}, sep = {separation} + + + + + + {int(owner1)}:{int(index1)}, {int(owner2)}:{int(index2)} + + + + + count: { pointCount }, triangle: { triangleIndex }, cluster: {clusterIndex} + + normal + feature + triangleFlags + + pointCount + points + + + + + + {{{count} : {radius}}} + + count + radius + + count + points + + + + + + type: {type} + + witnessA + witnessB + sweepA + sweepB + proxyA + proxyB + + + + + {{{count} / {capacity}}} + + count + capacity + + count + data + + + + + + + {{{count} / {capacity}}} + + count + capacity + + count + data + + + + + + {{{count} / {capacity}}} + + count + capacity + + count + data + + + + + + {{{count} / {capacity}}} + + count + capacity + + count + data + + + + + diff --git a/vendor/box3d/src/src/broad_phase.c b/vendor/box3d/src/src/broad_phase.c new file mode 100644 index 000000000..a369ca62a --- /dev/null +++ b/vendor/box3d/src/src/broad_phase.c @@ -0,0 +1,535 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "broad_phase.h" + +#include "aabb.h" +#include "arena_allocator.h" +#include "body.h" +#include "contact.h" +#include "core.h" +#include "parallel_for.h" +#include "physics_world.h" +#include "platform.h" +#include "shape.h" + +#include + +void b3CreateBroadPhase( b3BroadPhase* bp, const b3Capacity* capacity ) +{ + _Static_assert( b3_bodyTypeCount == 3, "must be three body types" ); + + bp->movedProxies[b3_staticBody] = b3CreateBitSet( b3MaxInt( 16, capacity->staticShapeCount ) ); + bp->movedProxies[b3_kinematicBody] = b3CreateBitSet( 16 ); + bp->movedProxies[b3_dynamicBody] = b3CreateBitSet( b3MaxInt( 16, capacity->dynamicShapeCount ) ); + b3Array_Reserve( bp->moveArray, capacity->dynamicShapeCount ); + bp->moveResults = NULL; + bp->movePairs = NULL; + bp->movePairCapacity = 0; + b3AtomicStoreInt( &bp->movePairIndex, 0 ); + bp->pairSet = b3CreateSet( 2 * capacity->contactCount ); + + int staticCapacity = b3MaxInt( 16, capacity->staticShapeCount ); + bp->trees[b3_staticBody] = b3DynamicTree_Create( staticCapacity ); + + int kinematicCapacity = 16; + bp->trees[b3_kinematicBody] = b3DynamicTree_Create( kinematicCapacity ); + + int dynamicCapacity = b3MaxInt( 16, capacity->dynamicShapeCount ); + bp->trees[b3_dynamicBody] = b3DynamicTree_Create( dynamicCapacity ); +} + +void b3DestroyBroadPhase( b3BroadPhase* bp ) +{ + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DynamicTree_Destroy( bp->trees + i ); + } + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DestroyBitSet( &bp->movedProxies[i] ); + } + b3Array_Destroy( bp->moveArray ); + b3DestroySet( &bp->pairSet ); + + *bp = (b3BroadPhase){ 0 }; + + memset( bp, 0, sizeof( b3BroadPhase ) ); +} + +static void b3UnBufferMove( b3BroadPhase* bp, int proxyKey ) +{ + b3BodyType proxyType = B3_PROXY_TYPE( proxyKey ); + int proxyId = B3_PROXY_ID( proxyKey ); + b3BitSet* set = &bp->movedProxies[proxyType]; + + if ( b3GetBit( set, proxyId ) ) + { + b3ClearBit( set, proxyId ); + + // Purge from move buffer. Linear search. + // todo if I can iterate the move set then I don't need the moveArray + int count = bp->moveArray.count; + for ( int i = 0; i < count; ++i ) + { + if ( bp->moveArray.data[i] == proxyKey ) + { + b3Array_RemoveSwap( bp->moveArray, i ); + break; + } + } + } +} + +int b3BroadPhase_CreateProxy( b3BroadPhase* bp, b3BodyType proxyType, b3AABB aabb, uint64_t categoryBits, int shapeIndex, + bool forcePairCreation ) +{ + B3_ASSERT( 0 <= proxyType && proxyType < b3_bodyTypeCount ); + int proxyId = b3DynamicTree_CreateProxy( bp->trees + proxyType, aabb, categoryBits, shapeIndex ); + int proxyKey = B3_PROXY_KEY( proxyId, proxyType ); + if ( proxyType != b3_staticBody || forcePairCreation ) + { + b3BufferMove( bp, proxyKey ); + } + return proxyKey; +} + +void b3BroadPhase_DestroyProxy( b3BroadPhase* bp, int proxyKey ) +{ + b3UnBufferMove( bp, proxyKey ); + + b3BodyType proxyType = B3_PROXY_TYPE( proxyKey ); + int proxyId = B3_PROXY_ID( proxyKey ); + + B3_ASSERT( 0 <= proxyType && proxyType <= b3_bodyTypeCount ); + b3DynamicTree_DestroyProxy( bp->trees + proxyType, proxyId ); +} + +void b3BroadPhase_MoveProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb ) +{ + b3BodyType proxyType = B3_PROXY_TYPE( proxyKey ); + int proxyId = B3_PROXY_ID( proxyKey ); + + b3DynamicTree_MoveProxy( bp->trees + proxyType, proxyId, aabb ); + b3BufferMove( bp, proxyKey ); +} + +void b3BroadPhase_EnlargeProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb ) +{ + B3_ASSERT( proxyKey != B3_NULL_INDEX ); + int typeIndex = B3_PROXY_TYPE( proxyKey ); + int proxyId = B3_PROXY_ID( proxyKey ); + + B3_ASSERT( typeIndex != b3_staticBody ); + + b3DynamicTree_EnlargeProxy( bp->trees + typeIndex, proxyId, aabb ); + b3BufferMove( bp, proxyKey ); +} + +typedef struct b3MovePair +{ + int shapeIndexA; + int shapeIndexB; + int childIndex; + b3MovePair* next; + bool heap; +} b3MovePair; + +typedef struct b3MoveResult +{ + b3MovePair* pairList; +} b3MoveResult; + +typedef struct b3QueryPairContext +{ + b3World* world; + b3MoveResult* moveResult; + b3AABB aabb; + b3BodyType queryTreeType; + int queryProxyKey; + int queryShapeIndex; + + int compoundProxyId; + int compoundShapeIndex; +} b3QueryPairContext; + +// This is called from b3DynamicTree::Query when we are gathering pairs. +static bool b3PairQueryCallback( int proxyId, uint64_t userData, void* context ) +{ + b3QueryPairContext* queryContext = (b3QueryPairContext*)context; + b3World* world = queryContext->world; + int shapeIndex; + int childIndex = 0; + + if ( queryContext->compoundShapeIndex == B3_NULL_INDEX ) + { + // Outer query: userData is a shape index. + shapeIndex = (int)userData; + + // A proxy cannot form a pair with itself. + if ( shapeIndex == queryContext->queryShapeIndex ) + { + return true; + } + + b3Shape* shape = b3Array_Get( world->shapes, shapeIndex ); + if ( shape->type == b3_compoundShape ) + { + // Query bounds are float world space, so the demoted transform is the matching float frame + b3Transform compoundTransform = b3ToRelativeTransform( b3GetBodyTransform( world, shape->bodyId ), b3Pos_zero ); + b3AABB localAABB = b3AABB_Transform( b3InvertTransform( compoundTransform ), queryContext->aabb ); + + // recurse + queryContext->compoundShapeIndex = shapeIndex; + queryContext->compoundProxyId = proxyId; + + b3DynamicTree_Query( &shape->compound->tree, localAABB, B3_DEFAULT_MASK_BITS, false, b3PairQueryCallback, context ); + queryContext->compoundShapeIndex = B3_NULL_INDEX; + queryContext->compoundProxyId = B3_NULL_INDEX; + return true; + } + } + else + { + // Inner query into a compound shape: userData is the compound child index, not a shape + // index, so do not compare it against queryShapeIndex. + shapeIndex = queryContext->compoundShapeIndex; + proxyId = queryContext->compoundProxyId; + childIndex = (int)userData; + } + + b3BroadPhase* broadPhase = &queryContext->world->broadPhase; + + int proxyKey = B3_PROXY_KEY( proxyId, queryContext->queryTreeType ); + int queryProxyKey = queryContext->queryProxyKey; + + // A proxy cannot form a pair with itself. + B3_ASSERT( proxyKey != queryContext->queryProxyKey ); + + b3BodyType treeType = queryContext->queryTreeType; + b3BodyType queryProxyType = B3_PROXY_TYPE( queryProxyKey ); + + // De-duplication + // It is important to prevent duplicate contacts from being created. Ideally I can prevent duplicates + // early and in the worker. Most of the time the movedProxies bit sets contain dynamic and kinematic + // proxies, but sometimes static proxies are in there too (b3ShapeDef::invokeContactCreation or a + // modified static shape), so we always have to check. + + // Is this proxy also moving? + if ( queryProxyType == b3_dynamicBody ) + { + if ( treeType == b3_dynamicBody && proxyKey < queryProxyKey ) + { + bool moved = b3GetBit( &broadPhase->movedProxies[treeType], proxyId ); + if ( moved ) + { + // Both proxies are moving. Avoid duplicate pairs. + return true; + } + } + } + else + { + B3_ASSERT( treeType == b3_dynamicBody ); + bool moved = b3GetBit( &broadPhase->movedProxies[treeType], proxyId ); + if ( moved ) + { + // Both proxies are moving. Avoid duplicate pairs. + return true; + } + } + + uint64_t pairKey = b3ShapePairKey( shapeIndex, queryContext->queryShapeIndex, childIndex ); + if ( b3ContainsKey( &broadPhase->pairSet, pairKey ) ) + { + // contact exists + return true; + } + + // Order shapes so that B3_SHAPE_PAIR_KEY works correctly + int shapeIdA = shapeIndex; + int shapeIdB = queryContext->queryShapeIndex; + b3Shape* shapeA = b3Array_Get( world->shapes, shapeIdA ); + b3Shape* shapeB = b3Array_Get( world->shapes, shapeIdB ); + int bodyIdA = shapeA->bodyId; + int bodyIdB = shapeB->bodyId; + + // Are the shapes on the same body? + if ( bodyIdA == bodyIdB ) + { + return true; + } + + // Sensors are handled elsewhere + if ( shapeA->sensorIndex != B3_NULL_INDEX || shapeB->sensorIndex != B3_NULL_INDEX ) + { + return true; + } + + if ( b3ShouldShapesCollide( shapeA->filter, shapeB->filter ) == false ) + { + return true; + } + + // Does a joint override collision? + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + if ( b3ShouldBodiesCollide( world, bodyA, bodyB ) == false ) + { + return true; + } + + // Custom user filter + if ( ( shapeA->flags & b3_enableCustomFiltering ) || ( shapeB->flags & b3_enableCustomFiltering ) ) + { + b3CustomFilterFcn* customFilterFcn = queryContext->world->customFilterFcn; + if ( customFilterFcn != NULL ) + { + b3ShapeId idA = { shapeIdA + 1, world->worldId, shapeA->generation }; + b3ShapeId idB = { shapeIdB + 1, world->worldId, shapeB->generation }; + bool shouldCollide = customFilterFcn( idA, idB, queryContext->world->customFilterContext ); + if ( shouldCollide == false ) + { + return true; + } + } + } + + // todo per thread to eliminate atomic? + int pairIndex = b3AtomicFetchAddInt( &broadPhase->movePairIndex, 1 ); + + b3MovePair* pair; + if ( pairIndex < broadPhase->movePairCapacity ) + { + pair = broadPhase->movePairs + pairIndex; + pair->heap = false; + } + else + { + // todo experimenting with ignoring this pair if we ran out of space + return true; + // pair = (b3MovePair*)b3Alloc( sizeof( b3MovePair ) ); + // pair->heap = true; + } + + pair->shapeIndexA = shapeIdA; + pair->shapeIndexB = shapeIdB; + pair->childIndex = childIndex; + pair->next = queryContext->moveResult->pairList; + queryContext->moveResult->pairList = pair; + + // continue the query + return true; +} + +static void b3FindPairsTask( int startIndex, int endIndex, int workerIndex, void* context ) +{ + b3TracyCZoneNC( pair_task, "Pair Task", b3_colorAquamarine, true ); + + B3_UNUSED( workerIndex ); + + b3World* world = (b3World*)context; + b3BroadPhase* bp = &world->broadPhase; + + b3QueryPairContext queryContext = { 0 }; + queryContext.world = world; + queryContext.compoundShapeIndex = B3_NULL_INDEX; + + for ( int i = startIndex; i < endIndex; ++i ) + { + // Initialize move result for this moved proxy + queryContext.moveResult = bp->moveResults + i; + queryContext.moveResult->pairList = NULL; + + int proxyKey = bp->moveArray.data[i]; + b3BodyType proxyType = B3_PROXY_TYPE( proxyKey ); + + int proxyId = B3_PROXY_ID( proxyKey ); + queryContext.queryProxyKey = proxyKey; + + const b3DynamicTree* baseTree = bp->trees + proxyType; + + // We have to query the tree with the fat AABB so that + // we don't fail to create a contact that may touch later. + b3AABB fatAABB = b3DynamicTree_GetAABB( baseTree, proxyId ); + queryContext.queryShapeIndex = (int)b3DynamicTree_GetUserData( baseTree, proxyId ); + queryContext.aabb = fatAABB; + + // Compound shape collision invocation is not supported + B3_VALIDATE( world->shapes.data[queryContext.queryShapeIndex].type != b3_compoundShape ); + + // Query trees. Only dynamic proxies collide with kinematic and static proxies. + // Using B3_DEFAULT_MASK_BITS so that b3Filter::groupIndex works. + // consider using bits = groupIndex > 0 ? B3_DEFAULT_MASK_BITS : maskBits + bool requireAllBits = false; + if ( proxyType == b3_dynamicBody ) + { + queryContext.queryTreeType = b3_kinematicBody; + b3DynamicTree_Query( bp->trees + b3_kinematicBody, fatAABB, B3_DEFAULT_MASK_BITS, requireAllBits, b3PairQueryCallback, + &queryContext ); + + queryContext.queryTreeType = b3_staticBody; + b3DynamicTree_Query( bp->trees + b3_staticBody, fatAABB, B3_DEFAULT_MASK_BITS, requireAllBits, b3PairQueryCallback, + &queryContext ); + } + + // All proxies collide with dynamic proxies + // Using B3_DEFAULT_MASK_BITS so that b3Filter::groupIndex works. + queryContext.queryTreeType = b3_dynamicBody; + b3DynamicTree_Query( bp->trees + b3_dynamicBody, fatAABB, B3_DEFAULT_MASK_BITS, requireAllBits, b3PairQueryCallback, + &queryContext ); + } + + b3TracyCZoneEnd( pair_task ); +} + +static void b3UpdateTreesTask( void* context ) +{ + b3TracyCZoneNC( tree_task, "Rebuild Trees", b3_colorFireBrick, true ); + + b3World* world = (b3World*)context; + b3DynamicTree_Rebuild( world->broadPhase.trees + b3_dynamicBody, false ); + b3DynamicTree_Rebuild( world->broadPhase.trees + b3_kinematicBody, false ); + + b3TracyCZoneEnd( tree_task ); +} + +void b3UpdateBroadPhasePairs( b3World* world ) +{ + b3BroadPhase* bp = &world->broadPhase; + + int moveCount = bp->moveArray.count; + + if ( moveCount == 0 ) + { + return; + } + + b3TracyCZoneNC( update_pairs, "Pairs", b3_colorMediumSlateBlue, true ); + + b3Stack* alloc = &world->stack; + + // todo these could be in the step context + bp->moveResults = (b3MoveResult*)b3StackAlloc( alloc, moveCount * sizeof( b3MoveResult ), "move results" ); + bp->movePairCapacity = 16 * moveCount; + bp->movePairs = (b3MovePair*)b3StackAlloc( alloc, bp->movePairCapacity * sizeof( b3MovePair ), "move pairs" ); + + b3AtomicStoreInt( &bp->movePairIndex, 0 ); + +#ifndef NDEBUG + extern b3AtomicInt b3_probeCount; + b3AtomicStoreInt( &b3_probeCount, 0 ); +#endif + + int minRange = 64; + b3ParallelFor( world, b3FindPairsTask, moveCount, minRange, world, "pairs" ); + + b3TracyCZoneNC( create_contacts, "Create Contacts", b3_colorCoral, true ); + + // Task that can be done in parallel with the narrow-phase + // - rebuild the collision tree for dynamic and kinematic bodies to keep their query performance good + if ( world->taskCount < B3_MAX_TASKS ) + { + world->userTreeTask = world->enqueueTaskFcn( &b3UpdateTreesTask, world, world->userTaskContext, "rebuild tree" ); + world->taskCount += 1; + world->activeTaskCount += world->userTreeTask == NULL ? 0 : 1; + } + else + { + world->userTreeTask = NULL; + b3UpdateTreesTask( world ); + } + + // Single-threaded work + // - Clear move flags + // - Create contacts in deterministic order + for ( int i = 0; i < moveCount; ++i ) + { + b3MoveResult* result = bp->moveResults + i; + b3MovePair* pair = result->pairList; + while ( pair != NULL ) + { + int shapeIdA = pair->shapeIndexA; + int shapeIdB = pair->shapeIndexB; + int childIndex = pair->childIndex; + + b3Shape* shapeA = b3Array_Get( world->shapes, shapeIdA ); + b3Shape* shapeB = b3Array_Get( world->shapes, shapeIdB ); + + b3CreateContact( world, shapeA, shapeB, childIndex ); + + if ( pair->heap ) + { + b3MovePair* temp = pair; + pair = pair->next; + b3Free( temp, sizeof( b3MovePair ) ); + } + else + { + pair = pair->next; + } + } + } + + // Reset move buffer: clear only the bits that were set this step. + // Invariant: bit set in movedProxies[type] iff proxyKey is present in moveArray. + for ( int i = 0; i < bp->moveArray.count; ++i ) + { + int proxyKey = bp->moveArray.data[i]; + b3ClearBit( &bp->movedProxies[B3_PROXY_TYPE( proxyKey )], B3_PROXY_ID( proxyKey ) ); + } + b3Array_Clear( bp->moveArray ); + + b3StackFree( alloc, bp->movePairs ); + bp->movePairs = NULL; + b3StackFree( alloc, bp->moveResults ); + bp->moveResults = NULL; + + b3ValidateSolverSets( world ); + + b3TracyCZoneEnd( create_contacts ); + + b3TracyCZoneEnd( update_pairs ); +} + +bool b3BroadPhase_TestOverlap( const b3BroadPhase* bp, int proxyKeyA, int proxyKeyB ) +{ + int typeIndexA = B3_PROXY_TYPE( proxyKeyA ); + int proxyIdA = B3_PROXY_ID( proxyKeyA ); + int typeIndexB = B3_PROXY_TYPE( proxyKeyB ); + int proxyIdB = B3_PROXY_ID( proxyKeyB ); + + b3AABB aabbA = b3DynamicTree_GetAABB( bp->trees + typeIndexA, proxyIdA ); + b3AABB aabbB = b3DynamicTree_GetAABB( bp->trees + typeIndexB, proxyIdB ); + return b3AABB_Overlaps( aabbA, aabbB ); +} + +int b3BroadPhase_GetShapeIndex( b3BroadPhase* bp, int proxyKey ) +{ + int typeIndex = B3_PROXY_TYPE( proxyKey ); + int proxyId = B3_PROXY_ID( proxyKey ); + + return (int)b3DynamicTree_GetUserData( bp->trees + typeIndex, proxyId ); +} + +void b3ValidateBroadPhase( const b3BroadPhase* bp ) +{ + b3DynamicTree_Validate( bp->trees + b3_dynamicBody ); + b3DynamicTree_Validate( bp->trees + b3_kinematicBody ); + + // todo validate every shape AABB is contained in tree AABB +} + +void b3ValidateNoEnlarged( const b3BroadPhase* bp ) +{ +#if B3_ENABLE_VALIDATION == 1 + for ( int j = 0; j < b3_bodyTypeCount; ++j ) + { + const b3DynamicTree* tree = bp->trees + j; + b3DynamicTree_ValidateNoEnlarged( tree ); + } +#else + B3_UNUSED( bp ); +#endif +} diff --git a/vendor/box3d/src/src/broad_phase.h b/vendor/box3d/src/src/broad_phase.h new file mode 100644 index 000000000..dcd626b3a --- /dev/null +++ b/vendor/box3d/src/src/broad_phase.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "bitset.h" +#include "container.h" +#include "table.h" + +#include "box3d/collision.h" +#include "box3d/types.h" + +typedef struct b3Shape b3Shape; +typedef struct b3MovePair b3MovePair; +typedef struct b3MoveResult b3MoveResult; +typedef struct b3Stack b3Stack; +typedef struct b3World b3World; + +// Store the proxy type in the lower 2 bits of the proxy key. This leaves 30 bits for the id. +#define B3_PROXY_TYPE( KEY ) ( (b3BodyType)( ( KEY ) & 3 ) ) +#define B3_PROXY_ID( KEY ) ( ( KEY ) >> 2 ) +#define B3_PROXY_KEY( ID, TYPE ) ( ( ( ID ) << 2 ) | ( TYPE ) ) + +/// The broad-phase is used for computing pairs and performing volume queries and ray casts. +/// This broad-phase does not persist pairs. Instead, this reports potentially new pairs. +/// It is up to the client to consume the new pairs and to track subsequent overlap. +typedef struct b3BroadPhase +{ + b3DynamicTree trees[b3_bodyTypeCount]; + + // Per body-type bit sets indexed by proxyId, marking proxies moved this step. + // Paired with moveArray which preserves deterministic insertion order for pair queries. + b3BitSet movedProxies[b3_bodyTypeCount]; + b3Array( int ) moveArray; + + // These are the results from the pair query and are used to create new contacts + // in deterministic order. + // todo these could be in the step context + b3MoveResult* moveResults; + b3MovePair* movePairs; + int movePairCapacity; + b3AtomicInt movePairIndex; + + // Tracks shape pairs that have a b3Contact + // todo pairSet can grow quite large on the first time step and remain large + b3HashSet pairSet; +} b3BroadPhase; + +void b3CreateBroadPhase( b3BroadPhase* bp, const b3Capacity* capacity ); +void b3DestroyBroadPhase( b3BroadPhase* bp ); + +int b3BroadPhase_CreateProxy( b3BroadPhase* bp, b3BodyType proxyType, b3AABB aabb, uint64_t categoryBits, int shapeIndex, + bool forcePairCreation ); +void b3BroadPhase_DestroyProxy( b3BroadPhase* bp, int proxyKey ); + +void b3BroadPhase_MoveProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb ); +void b3BroadPhase_EnlargeProxy( b3BroadPhase* bp, int proxyKey, b3AABB aabb ); + +int b3BroadPhase_GetShapeIndex( b3BroadPhase* bp, int proxyKey ); + +void b3UpdateBroadPhasePairs( b3World* world ); +bool b3BroadPhase_TestOverlap( const b3BroadPhase* bp, int proxyKeyA, int proxyKeyB ); + +void b3ValidateBroadPhase( const b3BroadPhase* bp ); +void b3ValidateNoEnlarged( const b3BroadPhase* bp ); + +// This is what triggers new contact pairs to be created +// Warning: this must be called in deterministic order +static inline void b3BufferMove( b3BroadPhase* bp, int queryProxy ) +{ + b3BodyType proxyType = B3_PROXY_TYPE( queryProxy ); + int proxyId = B3_PROXY_ID( queryProxy ); + b3BitSet* set = &bp->movedProxies[proxyType]; + if ( b3GetBit( set, proxyId ) == false ) + { + b3SetBitGrow( set, proxyId ); + b3Array_Push( bp->moveArray, queryProxy ); + } +} diff --git a/vendor/box3d/src/src/capsule.c b/vendor/box3d/src/src/capsule.c new file mode 100644 index 000000000..249e8cbf2 --- /dev/null +++ b/vendor/box3d/src/src/capsule.c @@ -0,0 +1,385 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "math_internal.h" +#include "shape.h" + +#include "box3d/base.h" +#include "box3d/collision.h" +#include "box3d/constants.h" + +b3MassData b3ComputeCapsuleMass( const b3Capsule* shape, float density ) +{ + b3Vec3 c1 = shape->center1; + b3Vec3 c2 = shape->center2; + float r = shape->radius; + + // Cylinder + float cylinderHeight = b3Distance( c1, c2 ); + float cylinderVolume = B3_PI * r * r * cylinderHeight; + float cylinderMass = cylinderVolume * density; + + // Sphere + float sphereVolume = ( 4.0f / 3.0f ) * B3_PI * r * r * r; + float sphereMass = sphereVolume * density; + + // Local accumulated inertia + b3Matrix3 inertia = b3AddMM( b3CylinderInertia( cylinderMass, r, cylinderHeight ), b3SphereInertia( sphereMass, r ) ); + + float steiner = 0.125f * sphereMass * ( 3.0f * r + 2.0f * cylinderHeight ) * cylinderHeight; + inertia.cx.x += steiner; + inertia.cz.z += steiner; + + // Align capsule axis with chosen up-axis + b3Matrix3 rotation = b3Mat3_identity; + if ( cylinderHeight * cylinderHeight > 1000.0f * FLT_MIN ) + { + b3Vec3 direction = b3Normalize( b3Sub( c2, c1 ) ); + b3Quat q = b3ComputeQuatBetweenUnitVectors( b3Vec3_axisY, direction ); + rotation = b3MakeMatrixFromQuat( q ); + } + + float mass = sphereMass + cylinderMass; + b3Vec3 center = b3MulSV( 0.5f, b3Add( c1, c2 ) ); + + b3MassData out; + out.mass = mass; + out.center = center; + + // Rotate the central inertia into the shape frame + out.inertia = b3MulMM( rotation, b3MulMM( inertia, b3Transpose( rotation ) ) ); + + return out; +} + +b3AABB b3ComputeCapsuleAABB( const b3Capsule* shape, b3Transform transform ) +{ + float r = shape->radius; + + b3Vec3 center1 = b3TransformPoint( transform, shape->center1 ); + b3Vec3 center2 = b3TransformPoint( transform, shape->center2 ); + b3Vec3 extent = { r, r, r }; + + b3AABB aabb; + aabb.lowerBound = b3Sub( b3Min( center1, center2 ), extent ); + aabb.upperBound = b3Add( b3Max( center1, center2 ), extent ); + return aabb; +} + +b3AABB b3ComputeSweptCapsuleAABB( const b3Capsule* shape, b3Transform xf1, b3Transform xf2 ) +{ + b3Vec3 r = { shape->radius, shape->radius, shape->radius }; + b3Vec3 a = b3TransformPoint( xf1, shape->center1 ); + b3Vec3 b = b3TransformPoint( xf1, shape->center2 ); + b3Vec3 c = b3TransformPoint( xf2, shape->center1 ); + b3Vec3 d = b3TransformPoint( xf2, shape->center2 ); + + b3AABB aabb = { + .lowerBound = b3Sub( b3Min( b3Min( a, b ), b3Min( c, d ) ), r ), + .upperBound = b3Add( b3Max( b3Max( a, b ), b3Max( c, d ) ), r ), + }; + return aabb; +} + +bool b3OverlapCapsule( const b3Capsule* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy ) +{ + b3DistanceInput input; + input.proxyA = (b3ShapeProxy){ &shape->center1, 2, shape->radius }; + input.proxyB = *proxy; + input.transform = b3InvMulTransforms( shapeTransform, b3Transform_identity ); + input.useRadii = true; + + b3SimplexCache cache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + return output.distance < B3_OVERLAP_SLOP; +} + +// Precision Improvements for Ray / Sphere Intersection - Ray Tracing Gems 2019 +// http://www.codercorner.com/blog/?p=321 +b3CastOutput b3RayCastCapsule( const b3Capsule* shape, const b3RayCastInput* input ) +{ + B3_ASSERT( b3IsValidRay( input ) ); + + b3Vec3 c1 = shape->center1; + b3Vec3 c2 = shape->center2; + float r = shape->radius; + + // Initialize result structure + b3CastOutput output = { 0 }; + + b3Vec3 d = b3Sub( c2, c1 ); + + // Fall back to sphere if the capsule is short + float tol = 0.01f * B3_LINEAR_SLOP; + float lengthSquared = b3LengthSquared( d ); + if ( lengthSquared < tol * tol ) + { + b3Vec3 sphereCenter = b3MulSV( 0.5f, b3Add( shape->center1, shape->center2 ) ); + b3Sphere sphere = { sphereCenter, shape->radius }; + return b3RayCastSphere( &sphere, input ); + } + + // Vector from first center to ray origin. + b3Vec3 s = b3Sub( input->origin, c1 ); + + // Capsule axis + float length = sqrtf( lengthSquared ); + b3Vec3 axis = b3MulSV( 1.0f / length, d ); + + // Project ray origin onto capsule axis. + float u = b3Dot( s, axis ); + + // Closest point on infinite capsule axis, relative to c1. + b3Vec3 c = b3MulSV( u, axis ); + + // Vector from closest point to ray origin + b3Vec3 sc = b3Sub( s, c ); + + // Squared distance from ray origin to capsule axis + float sc2 = b3LengthSquared( sc ); + + // Is the ray origin within the infinite cylinder along the capsule axis? + if ( sc2 < r * r ) + { + // Clamped barycentric coordinate of ray origin projected onto capsule axis. + float uClamped = b3ClampFloat( u, 0.0f, length ); + + // The closest point on the bounded capsule segment, relative to c1. + b3Vec3 cp = b3MulSV( uClamped, axis ); + + // Vector from ray origin to closest point on segment. + b3Vec3 scp = b3Sub( s, cp ); + + // Squared distance of ray origin from capsule segment. + float scp2 = b3LengthSquared( scp ); + + // Is the ray origin within the capsule? + if ( scp2 < r * r ) + { + output.hit = true; + output.point = input->origin; + return output; + } + + // The ray can hit an endcap. + b3Sphere sphere = { + .center = b3Add( c1, cp ), + .radius = r, + }; + + return b3RayCastSphere( &sphere, input ); + } + + // Ray axis. A zero length ray reaching here starts outside the capsule, so it misses. + // Same zero length convention as b3RayCastSphere. + b3Vec3 dr = input->translation; + float rayLength; + b3Vec3 rayAxis = b3GetLengthAndNormalize( &rayLength, dr ); + if ( rayLength == 0.0f ) + { + return output; + } + + // Barycentric coordinate of ray end point. + float v = u + input->maxFraction * b3Dot( dr, axis ); + + // Early out: does the projected ray fall outside the capsule? + if ( ( u < -r && v < -r ) || ( length + r < u && length + r < v ) ) + { + return output; + } + + // Compute the closest point between the ray segment and the capsule segment. + // See Real-Time Collision Detection, section 5.1.9 + + // Closest point on capsule : a1 = segment unit axis, t1 = unknown fraction + // p1 = t1 * a1 + + // Closet point on ray : a2 = ray unit axis, t2 = unknown fraction + // p2 = s + t2 * a2 + + // Closest point perpendicularity conditions. + // dot(p2 - p1, a1) = 0 + // dot(p2 - p1, a2) = 0 + + // Expand + // dot(t1 * a1 - s - t2 * a2, a1) = 0 + // dot(t1 * a1 - s - t2 * a2, a2) = 0 + + // Expand + // t1 - dot(s, a1) - t2 * dot(a1, a2) = 0 + // t1 * dot(a1, a2) - dot(s, a2) - t2 = 0 + + // Group : a12 = dot(a1, a2), sa1 = dot(s, a1), sa2 = dot(s, a2) + // t1 - a12 * t2 = sa1 + // a12 * t1 - t2 = sa2 + + // Solve + // https://en.wikipedia.org/wiki/Cramer%27s_rule + // I've flipped the signs of the numerator and denominator to give a positive determinant. + // det = 1 - a12 * a12 + // t1 = (sa1 - a12 * sa2) / det + // t2 = (a12 * sa1 - sa2) / det + + b3Vec3 a1 = axis; + b3Vec3 a2 = rayAxis; + float a12 = b3Dot( a1, a2 ); + + // Ray distance to the near intersection with the infinite cylinder. Length units. + float tr; + + float det = 1.0f - a12 * a12; + if ( det < FLT_EPSILON ) + { + // Solve the 2D problem of ray versus circle starting at the ray origin, where the circle is + // the axial view of the infinite capsule cylinder. This works well when the ray origin is + // not too far from the capsule axis. + + // Instead of a cross product, subtract the parallel part to get a perpendicular vector. Non-dimensional. + b3Vec3 perp = b3MulSub( a2, a12, a1 ); + float perp2 = b3LengthSquared( perp ); + + // Project to origin to infinite capsule axis vector onto the perpendicular vector. beta has length units. + float beta = b3Dot( sc, perp ); + + // Setup quadratic root finder. + float gamma = sc2 - r * r; + + // Discriminant + float disc = beta * beta - perp2 * gamma; + + // Casting away from the axis, or the perpendicular gap never closes to the radius. + if ( beta >= 0.0f || disc < 0.0f ) + { + return output; + } + + // Quadratic near root. Expressed in an alternate form to avoid the (-beta - sqrt) cancellation as + // the ray nears parallel. + tr = gamma / ( -beta + sqrtf( disc ) ); + } + else + { + // Ray and capsules axes are not parallel. + + // Closest points between the infinite ray and the infinite capsule axis. + float invDet = 1.0f / det; + float sa1 = u; + float sa2 = b3Dot( s, a2 ); + + float t1 = ( sa1 - a12 * sa2 ) * invDet; + float t2 = ( a12 * sa1 - sa2 ) * invDet; + + // Closest points + b3Vec3 p1 = b3MulSV( t1, a1 ); + b3Vec3 p2 = b3MulAdd( s, t2, a2 ); + + // Vector from closest point on infinite capsule to infinite ray. + b3Vec3 g = b3Sub( p2, p1 ); + + float g2 = b3LengthSquared( g ); + if ( g2 > r * r ) + { + // Early out: closest point on infinite ray is outside infinite cylinder. + return output; + } + + // Intersect the infinite ray with the infinite cylinder. Like ray versus sphere this is done + // relative to the closest point to avoid round-off errors. Length units, not a fraction. + // https://en.wikipedia.org/wiki/Line-cylinder_intersection + float h = sqrtf( ( r * r - g2 ) * invDet ); + + tr = t2 - h; + } + + // Outside ray? + if ( tr < 0.0f || input->maxFraction * rayLength < tr ) + { + return output; + } + + // The corresponding distance on the capsule axis. Length units. + float tc = u + tr * a12; + + // Outside c1 end? + if ( tc < 0.0f ) + { + // Ray cast sphere 1. + b3Sphere sphere = { + .center = c1, + .radius = r, + }; + + return b3RayCastSphere( &sphere, input ); + } + + // Outside c2 end? + if (length < tc) + { + // Ray cast sphere 2. + b3Sphere sphere = { + .center = c2, + .radius = r, + }; + + return b3RayCastSphere( &sphere, input ); + } + + // Hit point on capsule side, relative to c1. + b3Vec3 p = b3MulAdd( s, tr, rayAxis ); + + // Hit normal. + b3Vec3 normal = b3MulSub( p, tc, axis ); + normal = b3Normalize( normal ); + + output.point = b3Add( c1, p ); + output.normal = normal; + output.fraction = b3ClampFloat( tr / rayLength, 0.0f, input->maxFraction ); + output.hit = true; + return output; +} + +b3CastOutput b3ShapeCastCapsule( const b3Capsule* capsule, const b3ShapeCastInput* input ) +{ + b3ShapeCastPairInput pairInput; + pairInput.proxyA = (b3ShapeProxy){ &capsule->center1, 2, capsule->radius }; + pairInput.proxyB = input->proxy; + pairInput.transform = b3Transform_identity; + pairInput.translationB = input->translation; + pairInput.maxFraction = input->maxFraction; + pairInput.canEncroach = input->canEncroach; + + b3CastOutput output = b3ShapeCast( &pairInput ); + return output; +} + +int b3CollideMoverAndCapsule( b3PlaneResult* result, const b3Capsule* shape, const b3Capsule* mover ) +{ + float totalRadius = mover->radius + shape->radius; + + b3SegmentDistanceResult approach = b3SegmentDistance( shape->center1, shape->center2, mover->center1, mover->center2 ); + + // The normal points from the shape toward the mover. + float distance; + b3Vec3 normal = b3GetLengthAndNormalize( &distance, b3Sub( approach.point2, approach.point1 ) ); + + if ( distance > totalRadius ) + { + return 0; + } + + float linearSlop = B3_LINEAR_SLOP; + if ( distance < linearSlop ) + { + // Deep overlap: the core segments intersect. Pick an arbitrary direction perpendicular + // the to capsule axis. + float moverLength; + b3Vec3 moverAxis = b3GetLengthAndNormalize( &moverLength, b3Sub( mover->center2, mover->center1 ) ); + normal = moverLength > linearSlop ? b3Perp( moverAxis ) : b3Vec3_axisY; + distance = 0.0f; + } + + b3Plane plane = { normal, totalRadius - distance }; + *result = (b3PlaneResult){ plane, approach.point1 }; + return 1; +} diff --git a/vendor/box3d/src/src/compound.c b/vendor/box3d/src/src/compound.c new file mode 100644 index 000000000..29da3c662 --- /dev/null +++ b/vendor/box3d/src/src/compound.c @@ -0,0 +1,1196 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "compound.h" + +#include "hull_map.h" +#include "math_internal.h" +#include "shape.h" + +#include "box3d/base.h" +#include "box3d/collision.h" +#include "box3d/constants.h" +#include "box3d/types.h" + +#include +#include + +typedef struct b3SharedHull +{ + const b3HullData* hull; + int hullOffset; +} b3SharedHull; + +typedef struct b3SharedMesh +{ + const b3MeshData* meshData; + int meshOffset; +} b3SharedMesh; + +typedef struct b3HullInstance +{ + b3Transform transform; + uint32_t hullOffset; + uint32_t materialIndex; +} b3HullInstance; + +typedef struct b3MeshInstance +{ + b3Transform transform; + b3Vec3 scale; + uint32_t meshOffset; + uint32_t materialIndices[B3_MAX_COMPOUND_MESH_MATERIALS]; +} b3MeshInstance; + +static inline b3TreeNode* b3GetCompoundNodes( b3CompoundData* compound ) +{ + if ( compound->nodeOffset == 0 ) + { + return NULL; + } + + return (b3TreeNode*)( (intptr_t)compound + compound->nodeOffset ); +} + +const b3SurfaceMaterial* b3GetCompoundMaterials( const b3CompoundData* compound ) +{ + if ( compound->materialOffset == 0 ) + { + return NULL; + } + + return (b3SurfaceMaterial*)( (intptr_t)compound + compound->materialOffset ); +} + +b3CompoundCapsule b3GetCompoundCapsule( const b3CompoundData* compound, int index ) +{ + B3_ASSERT( 0 <= index && index < compound->capsuleCount && compound->capsuleOffset > 0 ); + + b3CompoundCapsule result = { 0 }; + if ( compound->capsuleOffset == 0 ) + { + return result; + } + + const b3CompoundCapsule* capsules = (const b3CompoundCapsule*)( (intptr_t)compound + compound->capsuleOffset ); + return capsules[index]; +} + +b3CompoundHull b3GetCompoundHull( const b3CompoundData* compound, int index ) +{ + B3_ASSERT( 0 <= index && index < compound->hullCount && compound->hullOffset > 0 ); + + b3CompoundHull result = { 0 }; + if ( compound->hullOffset == 0 ) + { + return result; + } + + const b3HullInstance* hullInstances = (const b3HullInstance*)( (intptr_t)compound + compound->hullOffset ); + uint32_t hullOffset = hullInstances[index].hullOffset; + B3_ASSERT( hullOffset >= compound->hullOffset + compound->hullCount * sizeof( b3HullInstance ) ); + result.hull = (const b3HullData*)( (intptr_t)compound + hullOffset ); + result.transform = hullInstances[index].transform; + result.materialIndex = hullInstances[index].materialIndex; + return result; +} + +b3CompoundMesh b3GetCompoundMesh( const b3CompoundData* compound, int index ) +{ + B3_ASSERT( 0 <= index && index < compound->meshCount && compound->meshOffset > 0 ); + + b3CompoundMesh result = { 0 }; + if ( compound->meshOffset == 0 ) + { + return result; + } + + const b3MeshInstance* meshInstances = (const b3MeshInstance*)( (intptr_t)compound + compound->meshOffset ); + uint32_t meshOffset = meshInstances[index].meshOffset; + B3_ASSERT( meshOffset >= compound->meshOffset + compound->meshCount * sizeof( b3HullInstance ) ); + result.meshData = (const b3MeshData*)( (intptr_t)compound + meshOffset ); + result.transform = meshInstances[index].transform; + result.scale = meshInstances[index].scale; + for ( int i = 0; i < B3_MAX_COMPOUND_MESH_MATERIALS; ++i ) + { + result.materialIndices[i] = meshInstances[index].materialIndices[i]; + } + return result; +} + +b3CompoundSphere b3GetCompoundSphere( const b3CompoundData* compound, int index ) +{ + B3_ASSERT( 0 <= index && index < compound->sphereCount && compound->sphereOffset > 0 ); + + b3CompoundSphere result = { 0 }; + if ( compound->sphereOffset == 0 ) + { + return result; + } + + const b3CompoundSphere* spheres = (const b3CompoundSphere*)( (intptr_t)compound + compound->sphereOffset ); + return spheres[index]; +} + +b3ChildShape b3GetCompoundChild( const b3CompoundData* compound, int childIndex ) +{ + // Capsule? + if ( 0 <= childIndex && childIndex < compound->capsuleCount ) + { + b3CompoundCapsule compoundCapsule = b3GetCompoundCapsule( compound, childIndex ); + return (b3ChildShape){ + .capsule = compoundCapsule.capsule, + .transform = b3Transform_identity, + .materialIndices = { compoundCapsule.materialIndex }, + .type = b3_capsuleShape, + }; + } + childIndex -= compound->capsuleCount; + + // Hull? + if ( 0 <= childIndex && childIndex < compound->hullCount ) + { + b3CompoundHull compoundHull = b3GetCompoundHull( compound, childIndex ); + return (b3ChildShape){ + .hull = compoundHull.hull, + .transform = compoundHull.transform, + .materialIndices = { compoundHull.materialIndex }, + .type = b3_hullShape, + }; + } + childIndex -= compound->hullCount; + + // Mesh? + if ( 0 <= childIndex && childIndex < compound->meshCount ) + { + b3CompoundMesh compoundMesh = b3GetCompoundMesh( compound, childIndex ); + const int* m = compoundMesh.materialIndices; + _Static_assert( B3_MAX_COMPOUND_MESH_MATERIALS == 4, "too many materials in compound mesh" ); + + return (b3ChildShape){ + .mesh = + { + .data = compoundMesh.meshData, + .scale = compoundMesh.scale, + }, + .transform = compoundMesh.transform, + .materialIndices = { m[0], m[1], m[2], m[3] }, + .type = b3_meshShape, + }; + } + childIndex -= compound->meshCount; + + B3_ASSERT( 0 <= childIndex && childIndex < compound->sphereCount ); + + // Sphere? + { + b3CompoundSphere compoundSphere = b3GetCompoundSphere( compound, childIndex ); + return (b3ChildShape){ + .sphere = compoundSphere.sphere, + .transform = b3Transform_identity, + .materialIndices = { compoundSphere.materialIndex }, + .type = b3_sphereShape, + }; + } +} + +static inline size_t vt_wyhash( const void* key, size_t len ); + +static inline uint64_t b3HashMesh( const b3MeshData* mesh ) +{ + return vt_wyhash( mesh, mesh->byteCount ); +} + +static bool b3CompareMeshes( const b3MeshData* mesh1, const b3MeshData* mesh2 ) +{ + if ( mesh1 == mesh2 ) + { + return true; + } + + if ( mesh1->byteCount != mesh2->byteCount ) + { + return false; + } + + int result = memcmp( mesh1, mesh2, mesh1->byteCount ); + return result == 0; +} + +#define NAME b3MeshMap +#define KEY_TY const b3MeshData* +#define VAL_TY int +#define HASH_FN b3HashMesh +#define CMPR_FN b3CompareMeshes +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +static inline uint64_t b3HashMaterial( const b3SurfaceMaterial* material ) +{ + return vt_wyhash( material, sizeof( b3SurfaceMaterial ) ); +} + +static bool b3CompareMaterials( const b3SurfaceMaterial* mat1, const b3SurfaceMaterial* mat2 ) +{ + if ( mat1 == mat2 ) + { + return true; + } + + int result = memcmp( mat1, mat2, sizeof( b3SurfaceMaterial ) ); + return result == 0; +} + +#define NAME b3MaterialMap +#define KEY_TY const b3SurfaceMaterial* +#define VAL_TY int +#define HASH_FN b3HashMaterial +#define CMPR_FN b3CompareMaterials +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +b3CompoundData* b3CreateCompound( const b3CompoundDef* def ) +{ + int convexCount = def->capsuleCount + def->hullCount + def->sphereCount; + int shapeCount = convexCount + def->meshCount; + + if ( shapeCount >= B3_MAX_CHILD_SHAPES ) + { + B3_ASSERT( false ); + return NULL; + } + + b3DynamicTree tree = b3DynamicTree_Create( shapeCount ); + + int childIndex = 0; + + // Instances + int capsuleCount = def->capsuleCount; + b3CompoundCapsule* capsuleInstances = b3AllocZeroed( capsuleCount * sizeof( b3CompoundCapsule ) ); + int hullCount = def->hullCount; + b3HullInstance* hullInstances = b3AllocZeroed( hullCount * sizeof( b3HullInstance ) ); + int meshCount = def->meshCount; + b3MeshInstance* meshInstances = b3AllocZeroed( meshCount * sizeof( b3MeshInstance ) ); + int sphereCount = def->sphereCount; + b3CompoundSphere* sphereInstances = b3AllocZeroed( sphereCount * sizeof( b3CompoundSphere ) ); + + // Determine material capacity + int materialCapacity = convexCount; + for ( int i = 0; i < def->meshCount; ++i ) + { + B3_ASSERT( def->meshes[i].materialCount > 0 ); + materialCapacity += def->meshes[i].materialCount; + } + + // Material map for convex material sharing. Mesh materials are not shared for simplicity. + b3MaterialMap materialMap; + b3MaterialMap_init( &materialMap ); + b3MaterialMap_reserve( &materialMap, materialCapacity ); + b3SurfaceMaterial* materials = b3AllocZeroed( materialCapacity * sizeof( b3SurfaceMaterial ) ); + int materialCount = 0; + + for ( int i = 0; i < def->capsuleCount; ++i ) + { + const b3CompoundCapsuleDef* capsuleDef = def->capsules + i; + capsuleInstances[i].capsule = capsuleDef->capsule; + + // Look for an existing material + b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &capsuleDef->material, materialCount ); + + // Get the shared material index + int materialIndex = materialItr.data->val; + capsuleInstances[i].materialIndex = materialIndex; + + // Is this a new material? + if ( materialIndex == materialCount ) + { + materials[materialIndex] = capsuleDef->material; + materialCount += 1; + } + + b3AABB aabb = b3ComputeCapsuleAABB( &capsuleDef->capsule, b3Transform_identity ); + b3DynamicTree_CreateProxy( &tree, aabb, ~0ull, childIndex ); + childIndex += 1; + } + + // Hulls + b3SharedHull* sharedHulls = b3AllocZeroed( hullCount * sizeof( b3SharedHull ) ); + int sharedHullCount = 0; + + if ( hullCount > 0 ) + { + b3HullMap hullMap; + b3HullMap_init( &hullMap ); + b3HullMap_reserve( &hullMap, hullCount ); + + for ( int i = 0; i < hullCount; ++i ) + { + const b3CompoundHullDef* hullDef = def->hulls + i; + const b3HullData* hull = hullDef->hull; + b3AABB aabb = b3ComputeHullAABB( hull, hullDef->transform ); + b3DynamicTree_CreateProxy( &tree, aabb, ~0ull, childIndex ); + childIndex += 1; + + // Look for an existing material + b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &hullDef->material, materialCount ); + + // Get the shared material index + int materialIndex = materialItr.data->val; + hullInstances[i].materialIndex = materialIndex; + + // Is this a new material? + if ( materialIndex == materialCount ) + { + materials[materialIndex] = hullDef->material; + materialCount += 1; + } + + hullInstances[i].transform = hullDef->transform; + + // Look for an existing matching hull + b3HullMap_itr itr = b3HullMap_get_or_insert( &hullMap, hull, sharedHullCount ); + + // Get the unique index for this hull + int sharedHullIndex = itr.data->val; + + // The offset isn't known yet, so store the index of the shared hull + hullInstances[i].hullOffset = sharedHullIndex; + + // Is this a new hull? + if ( sharedHullIndex == sharedHullCount ) + { + // Create a shared hull. The offset is determined below. + sharedHulls[sharedHullIndex].hull = hull; + sharedHulls[sharedHullIndex].hullOffset = B3_NULL_INDEX; + sharedHullCount += 1; + } + } + + b3HullMap_cleanup( &hullMap ); + } + + // Meshes + b3SharedMesh* sharedMeshes = b3AllocZeroed( meshCount * sizeof( b3SharedMesh ) ); + int sharedMeshCount = 0; + + if ( meshCount > 0 ) + { + b3MeshMap meshMap; + b3MeshMap_init( &meshMap ); + b3MeshMap_reserve( &meshMap, meshCount ); + + for ( int i = 0; i < meshCount; ++i ) + { + const b3CompoundMeshDef* meshDef = def->meshes + i; + + const b3MeshData* meshData = meshDef->meshData; + b3AABB aabb = b3ComputeMeshAABB( meshData, meshDef->transform, meshDef->scale ); + b3DynamicTree_CreateProxy( &tree, aabb, ~0ull, childIndex ); + childIndex += 1; + + // No effort to share mesh materials. It would be easier to do if the number of materials was limited. + B3_ASSERT( meshData->materialCount == meshDef->materialCount ); + + for ( int j = 0; j < meshDef->materialCount; ++j ) + { + // Look for an existing material + b3MaterialMap_itr materialItr = + b3MaterialMap_get_or_insert( &materialMap, &meshDef->materials[j], materialCount ); + + // Get the shared material index + int materialIndex = materialItr.data->val; + meshInstances[i].materialIndices[j] = materialIndex; + + // Is this a new material? + if ( materialIndex == materialCount ) + { + materials[materialIndex] = meshDef->materials[j]; + materialCount += 1; + } + } + + // Look for an existing matching mesh + b3MeshMap_itr itr = b3MeshMap_get_or_insert( &meshMap, meshData, sharedMeshCount ); + + // Get the shared mesh index + int sharedMeshIndex = itr.data->val; + + // Create mesh instance + meshInstances[i].transform = def->meshes[i].transform; + meshInstances[i].scale = def->meshes[i].scale; + + // The offset isn't known yet, so store the index of the shared mesh + meshInstances[i].meshOffset = sharedMeshIndex; + + // Is this a new mesh? + if ( sharedMeshIndex == sharedMeshCount ) + { + // Create a shared mesh. The offset is determined below. + sharedMeshes[sharedMeshIndex].meshData = meshData; + sharedMeshes[sharedMeshIndex].meshOffset = B3_NULL_INDEX; + sharedMeshCount += 1; + } + } + + b3MeshMap_cleanup( &meshMap ); + } + + // Spheres + for ( int i = 0; i < def->sphereCount; ++i ) + { + const b3CompoundSphereDef* sphereDef = def->spheres + i; + sphereInstances[i].sphere = sphereDef->sphere; + + // Look for an existing material + b3MaterialMap_itr materialItr = b3MaterialMap_get_or_insert( &materialMap, &sphereDef->material, materialCount ); + + // Get the shared material index + int materialIndex = materialItr.data->val; + sphereInstances[i].materialIndex = materialIndex; + + // Is this a new material? + if ( materialIndex == materialCount ) + { + materials[materialIndex] = sphereDef->material; + materialCount += 1; + } + + b3AABB aabb = b3ComputeSphereAABB( &sphereDef->sphere, b3Transform_identity ); + b3DynamicTree_CreateProxy( &tree, aabb, ~0ull, childIndex ); + childIndex += 1; + } + + B3_ASSERT( materialCount <= materialCapacity ); + B3_ASSERT( tree.nodeCount > 0 ); + + b3DynamicTree_Rebuild( &tree, true ); + + int byteCount = sizeof( b3CompoundData ); + + // Tree nodes - todo 64 byte alignment + int nodeOffset = byteCount; + byteCount += tree.nodeCapacity * sizeof( b3TreeNode ); + + int materialOffset = byteCount; + byteCount += materialCount * sizeof( b3SurfaceMaterial ); + + int capsuleOffset = byteCount; + byteCount += def->capsuleCount * sizeof( b3CompoundCapsule ); + + // Hull data layout has another level of indirection to allow for tight data packing + // 1. hull instance array : hull count array of b3HullInstance with individual hull transforms and offsets + // 2. heterogeneous array of shared hull data : each shared hull can have a different byte count, so direct indexing is not + // possible + int hullArrayOffset = byteCount; + + // Array of hull instances + byteCount += hullCount * sizeof( b3HullInstance ); + + // Packed shared hull blobs + for ( int i = 0; i < sharedHullCount; ++i ) + { + sharedHulls[i].hullOffset = byteCount; + byteCount += sharedHulls[i].hull->byteCount; + } + + // Mesh data layout has another level of indirection to allow for tight data packing + // 1. mesh instance array : mesh count array of b3MeshInstance with individual mesh transform, scale, and offset + // 2. heterogeneous array of shared mesh data : each shared mesh can have a different byte count, so direct indexing is not + // possible + int meshArrayOffset = byteCount; + + // Array of mesh instances + byteCount += meshCount * sizeof( b3MeshInstance ); + + // Packed shared mesh blobs + for ( int i = 0; i < sharedMeshCount; ++i ) + { + sharedMeshes[i].meshOffset = byteCount; + byteCount += sharedMeshes[i].meshData->byteCount; + } + + int sphereOffset = byteCount; + byteCount += def->sphereCount * sizeof( b3CompoundSphere ); + + b3CompoundData* compound = b3Alloc( byteCount ); + memset( compound, 0, byteCount ); + + compound->version = B3_COMPOUND_VERSION; + compound->byteCount = byteCount; + compound->nodeOffset = nodeOffset; + memcpy( &compound->tree, &tree, sizeof( b3DynamicTree ) ); + + // todo clean up this mess + compound->tree.freeList = 0; + compound->tree.leafIndices = NULL; + compound->tree.leafBoxes = NULL; + compound->tree.leafCenters = NULL; + compound->tree.binIndices = NULL; + compound->tree.rebuildCapacity = 0; + + compound->tree.nodes = NULL; + compound->materialOffset = materialOffset; + compound->materialCount = materialCount; + compound->capsuleOffset = capsuleOffset; + compound->capsuleCount = capsuleCount; + compound->hullOffset = hullArrayOffset; + compound->hullCount = hullCount; + compound->meshOffset = meshArrayOffset; + compound->meshCount = meshCount; + compound->sphereOffset = sphereOffset; + compound->sphereCount = sphereCount; + + // Tree nodes + b3TreeNode* nodes = b3GetCompoundNodes( compound ); + memcpy( nodes, tree.nodes, tree.nodeCapacity * sizeof( b3TreeNode ) ); + compound->tree.nodes = nodes; + + // Materials + B3_ASSERT( materialCount > 0 ); + b3SurfaceMaterial* destinationMaterials = (b3SurfaceMaterial*)( (intptr_t)compound + compound->materialOffset ); + if ( materials != NULL ) + { + memcpy( destinationMaterials, materials, materialCount * sizeof( b3SurfaceMaterial ) ); + } + + // Capsules + if ( def->capsuleCount > 0 ) + { + B3_ASSERT( compound->capsuleOffset > 0 ); + b3CompoundCapsule* capsules = (b3CompoundCapsule*)( (intptr_t)compound + compound->capsuleOffset ); + memcpy( capsules, capsuleInstances, capsuleCount * sizeof( b3CompoundCapsule ) ); + } + + // Hulls + for ( int i = 0; i < hullCount; ++i ) + { + // Fix up offsets + int sharedIndex = hullInstances[i].hullOffset; + B3_ASSERT( 0 <= sharedIndex && sharedIndex < sharedHullCount ); + hullInstances[i].hullOffset = sharedHulls[sharedIndex].hullOffset; + } + + b3HullInstance* destinationHullInstances = (b3HullInstance*)( (intptr_t)compound + hullArrayOffset ); + memcpy( destinationHullInstances, hullInstances, hullCount * sizeof( b3HullInstance ) ); + + for ( int i = 0; i < sharedHullCount; ++i ) + { + int offset = sharedHulls[i].hullOffset; + b3HullData* destinationHull = (b3HullData*)( (intptr_t)compound + offset ); + memcpy( destinationHull, sharedHulls[i].hull, sharedHulls[i].hull->byteCount ); + } + + compound->sharedHullCount = sharedHullCount; + + // Meshes + for ( int i = 0; i < meshCount; ++i ) + { + // Fix up offsets + int sharedIndex = meshInstances[i].meshOffset; + B3_ASSERT( 0 <= sharedIndex && sharedIndex < sharedMeshCount ); + meshInstances[i].meshOffset = sharedMeshes[sharedIndex].meshOffset; + } + + b3MeshInstance* destinationMeshInstances = (b3MeshInstance*)( (intptr_t)compound + meshArrayOffset ); + memcpy( destinationMeshInstances, meshInstances, meshCount * sizeof( b3MeshInstance ) ); + + for ( int i = 0; i < sharedMeshCount; ++i ) + { + int offset = sharedMeshes[i].meshOffset; + b3MeshData* destinationMesh = (b3MeshData*)( (intptr_t)compound + offset ); + memcpy( destinationMesh, sharedMeshes[i].meshData, sharedMeshes[i].meshData->byteCount ); + } + + compound->sharedMeshCount = sharedMeshCount; + + // Spheres + if ( def->sphereCount > 0 ) + { + B3_ASSERT( compound->sphereOffset > 0 ); + b3CompoundSphere* spheres = (b3CompoundSphere*)( (intptr_t)compound + compound->sphereOffset ); + memcpy( spheres, sphereInstances, sphereCount * sizeof( b3CompoundSphere ) ); + } + + b3MaterialMap_cleanup( &materialMap ); + b3Free( sharedHulls, hullCount * sizeof( b3SharedHull ) ); + b3Free( sharedMeshes, meshCount * sizeof( b3SharedMesh ) ); + b3Free( capsuleInstances, capsuleCount * sizeof( b3CompoundCapsule ) ); + b3Free( hullInstances, hullCount * sizeof( b3HullInstance ) ); + b3Free( meshInstances, meshCount * sizeof( b3MeshInstance ) ); + b3Free( sphereInstances, sphereCount * sizeof( b3CompoundSphere ) ); + b3Free( materials, materialCapacity * sizeof( b3SurfaceMaterial ) ); + b3DynamicTree_Destroy( &tree ); + + return compound; +} + +void b3DestroyCompound( b3CompoundData* compound ) +{ + b3Free( compound, compound->byteCount ); +} + +uint8_t* b3ConvertCompoundToBytes( b3CompoundData* compound ) +{ + // scrub this pointer before serialization + compound->tree.nodes = NULL; + return (uint8_t*)compound; +} + +b3CompoundData* b3ConvertBytesToCompound( uint8_t* bytes, int byteCount ) +{ + b3CompoundData* compound = (b3CompoundData*)bytes; + if ( compound->version != B3_COMPOUND_VERSION ) + { + return NULL; + } + + if ( compound->byteCount < (int)sizeof( b3CompoundData ) ) + { + return NULL; + } + + if ( byteCount != compound->byteCount ) + { + return NULL; + } + + if ( compound->nodeOffset <= 0 ) + { + return NULL; + } + + // this mutates the input bytes + compound->tree.nodes = (b3TreeNode*)( (intptr_t)compound + compound->nodeOffset ); + return compound; +} + +b3AABB b3ComputeCompoundAABB( const b3CompoundData* shape, b3Transform transform ) +{ + B3_ASSERT( shape->nodeOffset > 0 ); + + const b3TreeNode* nodes = (const b3TreeNode*)( (intptr_t)shape + shape->nodeOffset ); + int root = shape->tree.root; + b3AABB aabb = nodes[root].aabb; + return b3AABB_Transform( transform, aabb ); +} + +struct b3CompoundOverlapContext +{ + const b3CompoundData* compound; + // transform of the compound + b3Transform transform; + b3ShapeProxy proxy; + bool overlap; +}; + +static bool b3CompoundOverlapCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int childIndex = (int)userData; + struct b3CompoundOverlapContext* overlapContext = context; + b3ChildShape child = b3GetCompoundChild( overlapContext->compound, childIndex ); + + b3Transform transform = b3MulTransforms( overlapContext->transform, child.transform ); + + bool overlap = false; + switch ( child.type ) + { + case b3_capsuleShape: + overlap = b3OverlapCapsule( &child.capsule, transform, &overlapContext->proxy ); + break; + + case b3_hullShape: + overlap = b3OverlapHull( child.hull, transform, &overlapContext->proxy ); + break; + + case b3_meshShape: + overlap = b3OverlapMesh( &child.mesh, transform, &overlapContext->proxy ); + break; + + case b3_sphereShape: + overlap = b3OverlapSphere( &child.sphere, transform, &overlapContext->proxy ); + break; + + default: + B3_ASSERT( false ); + break; + } + + if ( overlap ) + { + // Done + overlapContext->overlap = true; + return false; + } + + // Continue the query if there is no overlap + return true; +} + +bool b3OverlapCompound( const b3CompoundData* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy ) +{ + struct b3CompoundOverlapContext context = { + .compound = shape, + .transform = shapeTransform, + .proxy = *proxy, + .overlap = false, + }; + + b3AABB aabb = { proxy->points[0], proxy->points[0] }; + for ( int i = 1; i < proxy->count; ++i ) + { + aabb.lowerBound = b3Min( aabb.lowerBound, proxy->points[i] ); + aabb.upperBound = b3Max( aabb.upperBound, proxy->points[i] ); + } + + b3Vec3 r = { proxy->radius, proxy->radius, proxy->radius }; + aabb.lowerBound = b3Sub( aabb.lowerBound, r ); + aabb.upperBound = b3Add( aabb.upperBound, r ); + + (void)b3DynamicTree_Query( &shape->tree, aabb, ~0ull, false, b3CompoundOverlapCallback, &context ); + + return context.overlap; +} + +struct b3CompoundCastContext +{ + const b3CompoundData* compound; + b3CastOutput* output; + // origin of the shape cast, the box cast callback only carries the advancing fraction + const b3ShapeCastInput* shapeInput; +}; + +static float b3CompoundRayCastCallback( const b3RayCastInput* input, int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + struct b3CompoundCastContext* castContext = context; + const b3CompoundData* compound = castContext->compound; + + int childIndex = (int)userData; + + b3ChildShape child = b3GetCompoundChild( compound, childIndex ); + + b3RayCastInput localInput = *input; + localInput.origin = b3InvTransformPoint( child.transform, input->origin ); + localInput.translation = b3InvRotateVector( child.transform.q, input->translation ); + + b3CastOutput output = { 0 }; + + switch ( child.type ) + { + case b3_capsuleShape: + output = b3RayCastCapsule( &child.capsule, &localInput ); + output.materialIndex = child.materialIndices[0]; + break; + + case b3_hullShape: + output = b3RayCastHull( child.hull, &localInput ); + output.materialIndex = child.materialIndices[0]; + break; + + case b3_meshShape: + { + output = b3RayCastMesh( &child.mesh, &localInput ); + B3_ASSERT( 0 <= output.materialIndex ); + int childMaterialIndex = b3MinInt( output.materialIndex, B3_MAX_COMPOUND_MESH_MATERIALS - 1 ); + output.materialIndex = child.materialIndices[childMaterialIndex]; + } + break; + + case b3_sphereShape: + output = b3RayCastSphere( &child.sphere, &localInput ); + output.materialIndex = child.materialIndices[0]; + break; + + default: + B3_ASSERT( false ); + break; + } + + if ( output.hit ) + { + output.point = b3TransformPoint( child.transform, output.point ); + output.normal = b3RotateVector( child.transform.q, output.normal ); + output.childIndex = childIndex; + *castContext->output = output; + return output.fraction; + } + + return input->maxFraction; +} + +b3CastOutput b3RayCastCompound( const b3CompoundData* shape, const b3RayCastInput* input ) +{ + b3CastOutput result = { 0 }; + + struct b3CompoundCastContext context = { + .compound = shape, + .output = &result, + }; + (void)b3DynamicTree_RayCast( &shape->tree, input, ~0ull, false, b3CompoundRayCastCallback, &context ); + return result; +} + +static float b3CompoundShapeCastCallback( const b3BoxCastInput* input, int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + struct b3CompoundCastContext* castContext = context; + const b3CompoundData* compound = castContext->compound; + const b3ShapeCastInput* shapeInput = castContext->shapeInput; + + int childIndex = (int)userData; + + b3ChildShape child = b3GetCompoundChild( compound, childIndex ); + + // Rebuild from the carried shape cast input, taking only the advancing fraction from the tree + b3ShapeCastInput localInput = *shapeInput; + localInput.maxFraction = input->maxFraction; + b3Vec3 localPoints[B3_MAX_SHAPE_CAST_POINTS]; + + localInput.proxy.count = b3MinInt( shapeInput->proxy.count, B3_MAX_SHAPE_CAST_POINTS ); + + b3Transform invTransform = b3InvertTransform( child.transform ); + b3Matrix3 R = b3MakeMatrixFromQuat( invTransform.q ); + + for ( int i = 0; i < localInput.proxy.count; ++i ) + { + localPoints[i] = b3Add( b3MulMV( R, shapeInput->proxy.points[i] ), invTransform.p ); + } + + localInput.proxy.points = localPoints; + localInput.translation = b3MulMV( R, shapeInput->translation ); + + b3CastOutput output = { 0 }; + + switch ( child.type ) + { + case b3_capsuleShape: + output = b3ShapeCastCapsule( &child.capsule, &localInput ); + output.materialIndex = child.materialIndices[0]; + break; + + case b3_hullShape: + output = b3ShapeCastHull( child.hull, &localInput ); + output.materialIndex = child.materialIndices[0]; + break; + + case b3_meshShape: + { + output = b3ShapeCastMesh( &child.mesh, &localInput ); + B3_ASSERT( 0 <= output.materialIndex ); + int childMaterialIndex = b3MinInt( output.materialIndex, B3_MAX_COMPOUND_MESH_MATERIALS - 1 ); + output.materialIndex = child.materialIndices[childMaterialIndex]; + } + break; + + case b3_sphereShape: + output = b3ShapeCastSphere( &child.sphere, &localInput ); + output.materialIndex = child.materialIndices[0]; + break; + + default: + B3_ASSERT( false ); + break; + } + + if ( output.hit ) + { + output.point = b3TransformPoint( child.transform, output.point ); + output.normal = b3RotateVector( child.transform.q, output.normal ); + output.childIndex = childIndex; + *castContext->output = output; + return output.fraction; + } + + return input->maxFraction; +} + +b3CastOutput b3ShapeCastCompound( const b3CompoundData* shape, const b3ShapeCastInput* input ) +{ + b3CastOutput result = { 0 }; + + if ( input->proxy.count == 0 ) + { + return result; + } + + struct b3CompoundCastContext context = { + .compound = shape, + .output = &result, + .shapeInput = input, + }; + + // The compound tree is in the compound local frame, so the proxy box needs no origin offset + b3AABB box = b3MakeAABB( input->proxy.points, input->proxy.count, input->proxy.radius ); + b3BoxCastInput treeInput = { box, input->translation, input->maxFraction }; + (void)b3DynamicTree_BoxCast( &shape->tree, &treeInput, ~0ull, false, b3CompoundShapeCastCallback, &context ); + return result; +} + +struct b3CompoundQueryContext +{ + const b3CompoundData* compound; + b3CompoundQueryFcn* fcn; + void* userContext; +}; + +static bool TreeQueryCallbackFcn( int proxyId, uint64_t userData, void* treeContext ) +{ + B3_UNUSED( proxyId ); + struct b3CompoundQueryContext* context = treeContext; + return context->fcn( context->compound, (int)userData, context->userContext ); +} + +void b3QueryCompound( const b3CompoundData* compound, b3AABB aabb, b3CompoundQueryFcn* fcn, void* context ) +{ + struct b3CompoundQueryContext compoundContext = { + .compound = compound, + .fcn = fcn, + .userContext = context, + }; + + b3DynamicTree_Query( &compound->tree, aabb, B3_DEFAULT_MASK_BITS, false, TreeQueryCallbackFcn, &compoundContext ); +} + +#if 0 +struct b3CompoundImpactContext +{ + b3TOIInput toiInput; + b3TOIOutput toiOutput; + b3Transform compoundTransform; + + // Bounds local to compound + b3AABB localSweepBoundsB; + + // Centroid of shape in body B local space + b3Vec3 localCentroidB; + float fallbackRadius; +}; + +static bool b3CompoundTimeOfImpactFcn( const b3CompoundData* compound, int childIndex, void* context ) +{ + b3CompoundImpactContext* toiContext = (b3CompoundImpactContext*)context; + + b3ChildShape child = b3GetCompoundChild( compound, childIndex ); + + b3TOIOutput output = {0 }; + toiContext->toiInput.sweepA = b3MakeCompoundChildSweep( toiContext->compoundTransform, child.transform ); + + switch ( child.type ) + { + case b3_capsuleShape: + { + toiContext->toiInput.proxyA.points = &child.capsule.center1; + toiContext->toiInput.proxyA.count = 2; + toiContext->toiInput.proxyA.radius = child.capsule.radius; + output = b3TimeOfImpact( &toiContext->toiInput ); + } + break; + + case b3_hullShape: + { + toiContext->toiInput.proxyA.points = b3GetHullPoints( child.hull ); + toiContext->toiInput.proxyA.count = child.hull->vertexCount; + toiContext->toiInput.proxyA.radius = 0.0f; + output = b3TimeOfImpact( &toiContext->toiInput ); + } + break; + + case b3_meshShape: + { + b3MeshImpactContext meshContext = {0}; + meshContext.toiInput = toiContext->toiInput; + meshContext.isSensor = false; + meshContext.localCentroidB = toiContext->localCentroidB; + meshContext.fallbackRadius = toiContext->fallbackRadius; + + b3Transform meshWorldTransform = b3MulTransforms( toiContext->compoundTransform, child.transform ); + + const b3Sweep* sweepB = &toiContext->toiInput.sweepB; + b3Transform xfB1 = { + .p = sweepB->c1 - b3RotateVector( sweepB->q1, sweepB->localCenter ), + .q = sweepB->q1, + }; + + b3Transform xfB2 = { + .p = sweepB->c2 - b3RotateVector( sweepB->q2, sweepB->localCenter ), + .q = sweepB->q2, + }; + + meshContext.meshLocalCentroidB1 = + b3InvTransformPoint( meshWorldTransform, b3TransformPoint( xfB1, meshContext.localCentroidB ) ); + meshContext.meshLocalCentroidB2 = + b3InvTransformPoint( meshWorldTransform, b3TransformPoint( xfB2, meshContext.localCentroidB ) ); + + // Bounds local to mesh + b3AABB localBounds = b3AABB_Transform( b3InvertTransform( child.transform ), toiContext->localSweepBoundsB ); + + b3QueryMesh( &child.mesh, localBounds, b3MeshTimeOfImpactFcn, &meshContext ); + + output = meshContext.toiOutput; + } + break; + + case b3_sphereShape: + { + toiContext->toiInput.proxyA.points = &child.sphere.center; + toiContext->toiInput.proxyA.count = 1; + toiContext->toiInput.proxyA.radius = child.sphere.radius; + output = b3TimeOfImpact( &toiContext->toiInput ); + } + break; + + default: + B3_ASSERT( false ); + break; + } + + if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction ) + { + toiContext->toiOutput = output; + toiContext->toiInput.maxFraction = output.fraction; + } + + // Clear this to be safe + toiContext->toiInput.proxyA = {0}; + + // Continue the query + return true; +} + +b3TOIOutput b3CompoundTimeOfImpact(const b3CompoundData* compound, b3Transform transform, const b3ShapeProxy* proxy, + const b3Sweep* sweep, float maxFraction) +{ + b3CompoundImpactContext context = {0}; + context.toiInput.proxyB = b3MakeShapeProxy( shapeB ); + context.toiInput.sweepB = *sweepB; + context.toiInput.maxFraction = maxFraction; + + context.compoundTransform = { + .p = sweepA->c1, + .q = sweepA->q1, + }; + + b3Vec3 localCentroidB = b3GetShapeCentroid( shapeB ); + context.localCentroidB = localCentroidB; + + b3ShapeExtent extents = b3ComputeShapeExtent( shapeB, context.localCentroidB ); + context.fallbackRadius = b3MaxFloat( 0.5f * extents.minExtent, B3_SPECULATIVE_DISTANCE ); + + // Swept bounds of shapeB + b3AABB aabb = b3ComputeSweptShapeAABB( shapeB, sweepB, maxFraction ); + + // Bounds local to mesh + b3AABB localBounds = b3AABB_Transform( b3InvertTransform( context.compoundTransform ), bounds ); + context.localSweepBoundsB = localBounds; + + b3DynamicTree_Query( &compound->tree, aabb, B3_DEFAULT_MASK_BITS, false, TreeQueryCallbackFcn, &compoundContext ); + + return context.toiOutput; +} +#endif + +// xf = xfP * xfC +b3Sweep b3MakeCompoundChildSweep( b3Transform compoundTransform, b3Transform childTransform ) +{ + b3Transform xf = b3MulTransforms( compoundTransform, childTransform ); + return (b3Sweep){ + .localCenter = b3Vec3_zero, + .c1 = xf.p, + .c2 = xf.p, + .q1 = xf.q, + .q2 = xf.q, + }; +} + +struct b3CompoundMoverContext +{ + const b3CompoundData* compound; + b3PlaneResult* planes; + int planeCapacity; + int planeCount; + b3Capsule mover; +}; + +static bool b3CompoundMoverCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int childIndex = (int)userData; + struct b3CompoundMoverContext* moverContext = context; + b3ChildShape child = b3GetCompoundChild( moverContext->compound, childIndex ); + + // Transform mover to child space + b3Capsule localMover; + localMover.center1 = b3InvTransformPoint( child.transform, moverContext->mover.center1 ); + localMover.center2 = b3InvTransformPoint( child.transform, moverContext->mover.center2 ); + localMover.radius = moverContext->mover.radius; + + int capacity = moverContext->planeCapacity - moverContext->planeCount; + B3_ASSERT( capacity > 0 ); + + b3PlaneResult* planes = moverContext->planes + moverContext->planeCount; + int planeCount = 0; + + switch ( child.type ) + { + case b3_capsuleShape: + planeCount = b3CollideMoverAndCapsule( planes, &child.capsule, &localMover ); + break; + + case b3_hullShape: + planeCount = b3CollideMoverAndHull( planes, child.hull, &localMover ); + break; + + case b3_meshShape: + planeCount = b3CollideMoverAndMesh( planes, capacity, &child.mesh, &localMover ); + break; + + case b3_sphereShape: + planeCount = b3CollideMoverAndSphere( planes, &child.sphere, &localMover ); + break; + + default: + B3_ASSERT( false ); + break; + } + + // Transform results back to shape space + for ( int i = 0; i < planeCount; ++i ) + { + planes[i].plane.normal = b3RotateVector( child.transform.q, planes[i].plane.normal ); + planes[i].point = b3TransformPoint( child.transform, planes[i].point ); + } + + moverContext->planeCount += planeCount; + + // Continue query while there is room for more planes + return moverContext->planeCount < moverContext->planeCapacity; +} + +int b3CollideMoverAndCompound( b3PlaneResult* planes, int capacity, const b3CompoundData* shape, const b3Capsule* mover ) +{ + struct b3CompoundMoverContext context = { + .compound = shape, + .planes = planes, + .planeCapacity = capacity, + .planeCount = 0, + .mover = *mover, + }; + + b3AABB aabb; + aabb.lowerBound = b3Min( mover->center1, mover->center2 ); + aabb.upperBound = b3Max( mover->center1, mover->center2 ); + b3Vec3 r = { mover->radius, mover->radius, mover->radius }; + aabb.lowerBound = b3Sub( aabb.lowerBound, r ); + aabb.upperBound = b3Add( aabb.upperBound, r ); + + (void)b3DynamicTree_Query( &shape->tree, aabb, ~0ull, false, b3CompoundMoverCallback, &context ); + + return context.planeCount; +} diff --git a/vendor/box3d/src/src/compound.h b/vendor/box3d/src/src/compound.h new file mode 100644 index 000000000..1f4d7e508 --- /dev/null +++ b/vendor/box3d/src/src/compound.h @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/types.h" + +b3TOIOutput b3CompoundTimeOfImpact( const b3CompoundData* compound, b3Transform transform, const b3ShapeProxy* proxy, + const b3Sweep* sweep, float maxFraction ); + +// Transforms a sweep for a compound child shape +b3Sweep b3MakeCompoundChildSweep( b3Transform compoundTransform, b3Transform childTransform ); + +int b3CollideMoverAndCompound( b3PlaneResult* planes, int capacity, const b3CompoundData* shape, const b3Capsule* mover ); diff --git a/vendor/box3d/src/src/constraint_graph.c b/vendor/box3d/src/src/constraint_graph.c new file mode 100644 index 000000000..9f06e9e4e --- /dev/null +++ b/vendor/box3d/src/src/constraint_graph.c @@ -0,0 +1,328 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "constraint_graph.h" + +#include "bitset.h" +#include "body.h" +#include "contact.h" +#include "joint.h" +#include "physics_world.h" + +#include + +// Solver using graph coloring. Islands are only used for sleep. +// High-Performance Physical Simulations on Next-Generation Architecture with Many Cores +// http://web.eecs.umich.edu/~msmelyan/papers/physsim_onmanycore_itj.pdf + +// Kinematic bodies have to be treated like dynamic bodies in graph coloring. Unlike static bodies, we cannot use a dummy solver +// body for kinematic bodies. We cannot access a kinematic body from multiple threads efficiently because the SIMD solver body +// scatter would write to the same kinematic body from multiple threads. Even if these writes don't modify the body, they will +// cause horrible cache stalls. To make this feasible I would need a way to block these writes. + +// This is used for debugging by making all constraints be assigned to overflow. +#define B3_FORCE_OVERFLOW 0 + +static const b3HexColor b3_graphColors[B3_GRAPH_COLOR_COUNT] = { + b3_colorRed, b3_colorOrange, b3_colorYellow, b3_colorLimeGreen, b3_colorSpringGreen, + b3_colorAqua, b3_colorDodgerBlue, b3_colorBlueViolet, b3_colorMagenta, b3_colorDeepPink, + b3_colorCrimson, b3_colorCoral, b3_colorGold, b3_colorGreenYellow, b3_colorMediumSeaGreen, + b3_colorTurquoise, b3_colorDeepSkyBlue, b3_colorCornflowerBlue, b3_colorMediumSlateBlue, b3_colorMediumOrchid, + b3_colorHotPink, b3_colorTomato, b3_colorKhaki, b3_colorSilver, +}; + +b3HexColor b3GetGraphColor( int index ) +{ + B3_ASSERT( 0 <= index && index < B3_GRAPH_COLOR_COUNT ); + return b3_graphColors[index]; +} + +void b3CreateGraph( b3ConstraintGraph* graph, int bodyCapacity ) +{ + _Static_assert( B3_GRAPH_COLOR_COUNT >= 2, "must have at least two constraint graph colors" ); + _Static_assert( B3_OVERFLOW_INDEX == B3_GRAPH_COLOR_COUNT - 1, "bad over flow index" ); + + *graph = (b3ConstraintGraph){ 0 }; + + bodyCapacity = b3MaxInt( bodyCapacity, 8 ); + + // Initialize graph color bit set. + // No bitset for overflow color. + for ( int i = 0; i < B3_OVERFLOW_INDEX; ++i ) + { + b3GraphColor* color = graph->colors + i; + color->bodySet = b3CreateBitSet( bodyCapacity ); + b3SetBitCountAndClear( &color->bodySet, bodyCapacity ); + } +} + +void b3DestroyGraph( b3ConstraintGraph* graph ) +{ + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT; ++i ) + { + b3GraphColor* color = graph->colors + i; + + // The bit set should never be used on the overflow color + B3_ASSERT( i != B3_OVERFLOW_INDEX || color->bodySet.bits == NULL ); + + b3DestroyBitSet( &color->bodySet ); + + b3Array_Destroy( color->convexContacts ); + b3Array_Destroy( color->contacts ); + b3Array_Destroy( color->jointSims ); + } +} + +// Contacts are always created as non-touching. They get cloned into the constraint +// graph once they are found to be touching. +void b3AddContactToGraph( b3World* world, b3Contact* contact ) +{ + B3_ASSERT( contact->manifoldCount > 0 ); + B3_ASSERT( contact->flags & b3_contactTouchingFlag ); + + b3ConstraintGraph* graph = &world->constraintGraph; + int colorIndex = B3_OVERFLOW_INDEX; + + int bodyIdA = contact->edges[0].bodyId; + int bodyIdB = contact->edges[1].bodyId; + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + + b3BodyType typeA = bodyA->type; + b3BodyType typeB = bodyB->type; + B3_ASSERT( typeA == b3_dynamicBody || typeB == b3_dynamicBody ); + +#if B3_FORCE_OVERFLOW == 0 + if ( typeA == b3_dynamicBody && typeB == b3_dynamicBody ) + { + // Dynamic constraint colors cannot encroach on colors reserved for static constraints + for ( int i = 0; i < B3_DYNAMIC_COLOR_COUNT; ++i ) + { + b3GraphColor* color = graph->colors + i; + if ( b3GetBit( &color->bodySet, bodyIdA ) || b3GetBit( &color->bodySet, bodyIdB ) ) + { + continue; + } + + b3SetBitGrow( &color->bodySet, bodyIdA ); + b3SetBitGrow( &color->bodySet, bodyIdB ); + colorIndex = i; + break; + } + } + else if ( typeA == b3_dynamicBody ) + { + // Static constraint colors build from the end to get higher priority than dyn-dyn constraints + for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i ) + { + b3GraphColor* color = graph->colors + i; + if ( b3GetBit( &color->bodySet, bodyIdA ) ) + { + continue; + } + + b3SetBitGrow( &color->bodySet, bodyIdA ); + colorIndex = i; + break; + } + } + else if ( typeB == b3_dynamicBody ) + { + // Static constraint colors build from the end to get higher priority than dyn-dyn constraints + for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i ) + { + b3GraphColor* color = graph->colors + i; + if ( b3GetBit( &color->bodySet, bodyIdB ) ) + { + continue; + } + + b3SetBitGrow( &color->bodySet, bodyIdB ); + colorIndex = i; + break; + } + } +#endif + + bool isScalar = ( contact->flags & b3_simMeshContact ) || colorIndex == B3_OVERFLOW_INDEX; + + b3GraphColor* color = graph->colors + colorIndex; + contact->colorIndex = colorIndex; + contact->localIndex = isScalar ? color->contacts.count : color->convexContacts.count; + contact->bodySimIndexA = bodyA->type == b3_staticBody ? B3_NULL_INDEX : bodyA->localIndex; + contact->bodySimIndexB = bodyB->type == b3_staticBody ? B3_NULL_INDEX : bodyB->localIndex; + + if ( isScalar ) + { + B3_ASSERT( contact->manifoldCount < UINT16_MAX ); + b3ContactSpec spec = { + .contactId = contact->contactId, + .manifoldStart = 0, + .manifoldCount = (uint16_t)contact->manifoldCount, + }; + b3Array_Push( color->contacts, spec ); + } + else + { + b3Array_Push( color->convexContacts, contact->contactId ); + } +} + +void b3RemoveContactFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex, bool meshContact ) +{ + b3ConstraintGraph* graph = &world->constraintGraph; + + B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT ); + b3GraphColor* color = graph->colors + colorIndex; + + if ( colorIndex != B3_OVERFLOW_INDEX ) + { + // This might clear a bit for a static body, but this has no effect + b3ClearBit( &color->bodySet, bodyIdA ); + b3ClearBit( &color->bodySet, bodyIdB ); + } + + if ( meshContact || colorIndex == B3_OVERFLOW_INDEX ) + { + int movedIndex = b3Array_RemoveSwap( color->contacts, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix index on swapped contact + int movedContactId = color->contacts.data[localIndex].contactId; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId ); + B3_ASSERT( movedContact->setIndex == b3_awakeSet ); + B3_ASSERT( movedContact->colorIndex == colorIndex ); + B3_ASSERT( movedContact->localIndex == movedIndex ); + movedContact->localIndex = localIndex; + } + } + else + { + int movedIndex = b3Array_RemoveSwap( color->convexContacts, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix index on swapped contact + int movedContactId = color->convexContacts.data[localIndex]; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId ); + B3_ASSERT( movedContact->setIndex == b3_awakeSet ); + B3_ASSERT( movedContact->colorIndex == colorIndex ); + B3_ASSERT( movedContact->localIndex == movedIndex ); + B3_ASSERT( ( movedContact->flags & b3_simMeshContact ) == 0 ); + movedContact->localIndex = localIndex; + } + } +} + +static int b3AssignJointColor( b3ConstraintGraph* graph, int bodyIdA, int bodyIdB, b3BodyType typeA, b3BodyType typeB ) +{ + B3_ASSERT( typeA == b3_dynamicBody || typeB == b3_dynamicBody ); + B3_UNUSED( graph ); + B3_UNUSED( bodyIdA ); + B3_UNUSED( bodyIdB ); + B3_UNUSED( typeA ); + B3_UNUSED( typeB ); + +#if B3_FORCE_OVERFLOW == 0 + if ( typeA == b3_dynamicBody && typeB == b3_dynamicBody ) + { + // Dynamic constraint colors cannot encroach on colors reserved for static constraints + for ( int i = 0; i < B3_DYNAMIC_COLOR_COUNT; ++i ) + { + b3GraphColor* color = graph->colors + i; + if ( b3GetBit( &color->bodySet, bodyIdA ) || b3GetBit( &color->bodySet, bodyIdB ) ) + { + continue; + } + + b3SetBitGrow( &color->bodySet, bodyIdA ); + b3SetBitGrow( &color->bodySet, bodyIdB ); + return i; + } + } + else if ( typeA == b3_dynamicBody ) + { + // Static constraint colors build from the end to get higher priority than dyn-dyn constraints + for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i ) + { + b3GraphColor* color = graph->colors + i; + if ( b3GetBit( &color->bodySet, bodyIdA ) ) + { + continue; + } + + b3SetBitGrow( &color->bodySet, bodyIdA ); + return i; + } + } + else if ( typeB == b3_dynamicBody ) + { + // Static constraint colors build from the end to get higher priority than dyn-dyn constraints + for ( int i = B3_OVERFLOW_INDEX - 1; i >= 1; --i ) + { + b3GraphColor* color = graph->colors + i; + if ( b3GetBit( &color->bodySet, bodyIdB ) ) + { + continue; + } + + b3SetBitGrow( &color->bodySet, bodyIdB ); + return i; + } + } +#endif + + return B3_OVERFLOW_INDEX; +} + +b3JointSim* b3CreateJointInGraph( b3World* world, b3Joint* joint ) +{ + b3ConstraintGraph* graph = &world->constraintGraph; + + int bodyIdA = joint->edges[0].bodyId; + int bodyIdB = joint->edges[1].bodyId; + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + + int colorIndex = b3AssignJointColor( graph, bodyIdA, bodyIdB, bodyA->type, bodyB->type ); + + b3JointSim* jointSim = b3Array_Emplace( graph->colors[colorIndex].jointSims ); + memset( jointSim, 0, sizeof( b3JointSim ) ); + + joint->colorIndex = colorIndex; + joint->localIndex = graph->colors[colorIndex].jointSims.count - 1; + return jointSim; +} + +void b3AddJointToGraph( b3World* world, b3JointSim* jointSim, b3Joint* joint ) +{ + b3JointSim* jointDst = b3CreateJointInGraph( world, joint ); + memcpy( jointDst, jointSim, sizeof( b3JointSim ) ); +} + +void b3RemoveJointFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex ) +{ + b3ConstraintGraph* graph = &world->constraintGraph; + + B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT ); + b3GraphColor* color = graph->colors + colorIndex; + + if ( colorIndex != B3_OVERFLOW_INDEX ) + { + // May clear static bodies, no effect + b3ClearBit( &color->bodySet, bodyIdA ); + b3ClearBit( &color->bodySet, bodyIdB ); + } + + int movedIndex = b3Array_RemoveSwap( color->jointSims, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix moved joint + b3JointSim* movedJointSim = color->jointSims.data + localIndex; + int movedId = movedJointSim->jointId; + b3Joint* movedJoint = b3Array_Get( world->joints, movedId ); + B3_ASSERT( movedJoint->setIndex == b3_awakeSet ); + B3_ASSERT( movedJoint->colorIndex == colorIndex ); + B3_ASSERT( movedJoint->localIndex == movedIndex ); + movedJoint->localIndex = localIndex; + } +} diff --git a/vendor/box3d/src/src/constraint_graph.h b/vendor/box3d/src/src/constraint_graph.h new file mode 100644 index 000000000..ab17e9ac8 --- /dev/null +++ b/vendor/box3d/src/src/constraint_graph.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "bitset.h" +#include "contact.h" +#include "container.h" +#include "solver.h" +#include "solver_set.h" +#include "box3d/constants.h" + +typedef struct b3Body b3Body; +typedef struct b3Contact b3Contact; +typedef struct b3JointSim b3JointSim; +typedef struct b3Joint b3Joint; +typedef struct b3StepContext b3StepContext; +typedef struct b3World b3World; + +// This holds constraints that cannot fit the graph color limit. This happens when a single dynamic body +// is touching many other bodies. +#define B3_OVERFLOW_INDEX ( B3_GRAPH_COLOR_COUNT - 1 ) + +// This keeps constraints involving two dynamic bodies at a lower solver priority than constraints +// involving a dynamic and static bodies. This reduces tunneling due to push through. +#define B3_DYNAMIC_COLOR_COUNT ( B3_GRAPH_COLOR_COUNT - 4 ) + +// todo optimize mesh contact constraints +// They can be lumped with convex contacts and I can use the bitset event to re-link if the manifold count increases +// Could also create a group for two wide manifolds and use bitset event +// This could create ping-pong jitter so may need to pin to high water mark or introduce hysteresis somehow +// +// Dirk has the idea to do graph coloring based on manifolds. This suggests mesh contact will have manifolds +// in multiple graph colors. So each manifold with have a color and local index. +// Some concerns about this: +// - manifolds don't have a strong identity, would this affect stability/jitter? +// - this creates a lot of static graph colors and can overflow + +typedef struct b3GraphColor +{ + // This bitset is indexed by bodyId so this is over-sized to encompass static bodies + // however I never traverse these bits or use the bit count for anything + // This bitset is unused on the overflow color. + b3BitSet bodySet; + + // cache friendly arrays + b3Array( b3JointSim ) jointSims; + + b3Array( int ) convexContacts; + b3Array( b3ContactSpec ) contacts; + + // These are used for convex contacts + struct b3ContactConstraintWide* wideConstraints; + int wideConstraintCount; + + // These are used for mesh and overflow contacts + struct b3ManifoldConstraint* manifoldConstraints; + int manifoldConstraintCount; + struct b3ContactConstraint* contactConstraints; + int contactConstraintCount; +} b3GraphColor; + +typedef struct b3ConstraintGraph +{ + // including overflow at the end + b3GraphColor colors[B3_GRAPH_COLOR_COUNT]; +} b3ConstraintGraph; + +void b3CreateGraph( b3ConstraintGraph* graph, int bodyCapacity ); +void b3DestroyGraph( b3ConstraintGraph* graph ); + +void b3AddContactToGraph( b3World* world, b3Contact* contact ); +void b3RemoveContactFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex, bool meshContact ); + +b3JointSim* b3CreateJointInGraph( b3World* world, b3Joint* joint ); +void b3AddJointToGraph( b3World* world, b3JointSim* jointSim, b3Joint* joint ); +void b3RemoveJointFromGraph( b3World* world, int bodyIdA, int bodyIdB, int colorIndex, int localIndex ); diff --git a/vendor/box3d/src/src/contact.c b/vendor/box3d/src/src/contact.c new file mode 100644 index 000000000..92e38b018 --- /dev/null +++ b/vendor/box3d/src/src/contact.c @@ -0,0 +1,873 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "contact.h" + +#include "algorithm.h" +#include "body.h" +#include "compound.h" +#include "island.h" +#include "manifold.h" +#include "physics_world.h" +#include "shape.h" +#include "solver_set.h" +#include "table.h" + +#include "box3d/box3d.h" + +// Contacts and determinism +// A deterministic simulation requires contacts to exist in the same order in b3Island no matter the thread count. +// The order must reproduce from run to run. This is necessary because the Gauss-Seidel constraint solver is order dependent. +// +// Creation: +// - Contacts are created using results from b3UpdateBroadPhasePairs +// - These results are ordered according to the order of the broad-phase move array +// - The move array is ordered according to the shape creation order using a bitset. +// - The island/shape/body order is determined by creation order +// - Logically contacts are only created for awake bodies, so they are immediately added to the awake contact array (serially) +// +// Island linking: +// - The awake contact array is built from the body-contact graph for all awake bodies in awake islands. +// - Awake contacts are solved in parallel and they generate contact state changes. +// - These state changes may link islands together using union find. +// - The state changes are ordered using a bit array that encompasses all contacts +// - As long as contacts are created in deterministic order, island link order is deterministic. +// - This keeps the order of contacts in islands deterministic + +// Manifold functions should compute important results in local space to improve precision. However, this +// interface function takes two world transforms instead of a relative transform for these reasons: +// +// First: +// The anchors need to be computed relative to the shape origin in world space. This is necessary so the +// solver does not need to access static body transforms. Not even in constraint preparation. This approach +// has world space vectors yet retains precision. +// +// Second: +// b3ManifoldPoint::point is very useful for debugging and it is in world space. +// +// Third: +// The user may call the manifold functions directly and they should be easy to use and have easy to use +// results. +// typedef b3Manifold b3ManifoldFcn( const b3Shape* shapeA, b3Transform xfA, const b3Shape* shapeB, b3Transform xfB, +// b3ContactCache* cache ); + +static b3Contact* b3GetContactFullId( b3World* world, b3ContactId contactId ) +{ + int id = contactId.index1 - 1; + b3Contact* contact = b3Array_Get( world->contacts, id ); + B3_ASSERT( contact->contactId == id && contact->generation == contactId.generation ); + return contact; +} + +b3ContactData b3Contact_GetData( b3ContactId contactId ) +{ + b3World* world = b3GetWorld( contactId.world0 ); + b3Contact* contact = b3GetContactFullId( world, contactId ); + + const b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA ); + const b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB ); + + b3ContactData data = { 0 }; + data.contactId = contactId; + data.shapeIdA = (b3ShapeId){ + .index1 = shapeA->id + 1, + .world0 = contactId.world0, + .generation = shapeA->generation, + }; + data.shapeIdB = (b3ShapeId){ + .index1 = shapeB->id + 1, + .world0 = contactId.world0, + .generation = shapeB->generation, + }; + + if ( contact->manifoldCount > 0 ) + { + data.manifolds = contact->manifolds; + data.manifoldCount = contact->manifoldCount; + } + else + { + data.manifolds = NULL; + data.manifoldCount = 0; + } + + return data; +} + +struct b3ContactRegister +{ + // b3ManifoldFcn* fcn; + bool supported; + bool primary; +}; + +static struct b3ContactRegister s_registers[b3_shapeTypeCount][b3_shapeTypeCount]; +static bool s_initialized = false; + +static void b3AddType( b3ShapeType type1, b3ShapeType type2 ) +{ + B3_ASSERT( 0 <= type1 && type1 < b3_shapeTypeCount ); + B3_ASSERT( 0 <= type2 && type2 < b3_shapeTypeCount ); + + s_registers[type1][type2].supported = true; + s_registers[type1][type2].primary = true; + + if ( type1 != type2 ) + { + s_registers[type2][type1].supported = true; + s_registers[type2][type1].primary = false; + } +} + +void b3InitializeContactRegisters( void ) +{ + if ( s_initialized == false ) + { + b3AddType( b3_sphereShape, b3_sphereShape ); + b3AddType( b3_capsuleShape, b3_sphereShape ); + b3AddType( b3_capsuleShape, b3_capsuleShape ); + b3AddType( b3_compoundShape, b3_sphereShape ); + b3AddType( b3_compoundShape, b3_capsuleShape ); + b3AddType( b3_compoundShape, b3_hullShape ); + b3AddType( b3_hullShape, b3_sphereShape ); + b3AddType( b3_hullShape, b3_capsuleShape ); + b3AddType( b3_hullShape, b3_hullShape ); + b3AddType( b3_meshShape, b3_sphereShape ); + b3AddType( b3_meshShape, b3_capsuleShape ); + b3AddType( b3_meshShape, b3_hullShape ); + b3AddType( b3_heightShape, b3_sphereShape ); + b3AddType( b3_heightShape, b3_capsuleShape ); + b3AddType( b3_heightShape, b3_hullShape ); + s_initialized = true; + } +} + +void b3CreateContact( b3World* world, b3Shape* shapeA, b3Shape* shapeB, int childIndex ) +{ + b3ShapeType typeA = shapeA->type; + b3ShapeType typeB = shapeB->type; + + B3_ASSERT( 0 <= typeA && typeA < b3_shapeTypeCount ); + B3_ASSERT( 0 <= typeB && typeB < b3_shapeTypeCount ); + + if ( s_registers[typeA][typeB].supported == false ) + { + // For example, no mesh vs mesh collision + return; + } + + if ( s_registers[typeA][typeB].primary == false ) + { + // flip order + b3CreateContact( world, shapeB, shapeA, childIndex ); + return; + } + + b3Body* bodyA = b3Array_Get( world->bodies, shapeA->bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, shapeB->bodyId ); + + B3_ASSERT( bodyA->setIndex != b3_disabledSet && bodyB->setIndex != b3_disabledSet ); + B3_ASSERT( bodyA->setIndex != b3_staticSet || bodyB->setIndex != b3_staticSet ); + + int setIndex; + if ( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ) + { + setIndex = b3_awakeSet; + } + else + { + // sleeping and non-touching contacts live in the disabled set + // later if this set is found to be touching then the sleeping + // islands will be linked and the contact moved to the merged island + + // This is possible if a shape moves slightly then falls asleep + setIndex = b3_disabledSet; + } + + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + + // Create contact key and contact + int contactId = b3AllocId( &world->contactIdPool ); + if ( contactId == world->contacts.count ) + { + b3Contact emptyContact = { 0 }; + b3Array_Push( world->contacts, emptyContact ); + } + + int shapeIdA = shapeA->id; + int shapeIdB = shapeB->id; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + int generation = contact->generation; + *contact = (b3Contact){ 0 }; + contact->contactId = contactId; + contact->generation = generation + 1; + contact->setIndex = setIndex; + contact->colorIndex = B3_NULL_INDEX; + contact->localIndex = set->contactIndices.count; + contact->islandId = B3_NULL_INDEX; + contact->islandIndex = B3_NULL_INDEX; + contact->shapeIdA = shapeIdA; + contact->shapeIdB = shapeIdB; + contact->childIndex = childIndex; + + // Both bodies must enable recycling + if ( ( bodyA->flags & b3_bodyEnableContactRecycling ) != 0 && ( bodyB->flags & b3_bodyEnableContactRecycling ) != 0 ) + { + contact->flags |= b3_contactRecycleFlag; + } + + if ( shapeA->type == b3_meshShape || shapeA->type == b3_heightShape ) + { + contact->flags |= b3_simMeshContact; + } + else if ( shapeA->type == b3_compoundShape ) + { + b3ChildShape child = b3GetCompoundChild( shapeA->compound, childIndex ); + if ( child.type == b3_meshShape ) + { + contact->flags |= b3_simMeshContact; + } + } + + // todo impose these restrictions to make life easier + B3_ASSERT( shapeB->type == b3_sphereShape || shapeB->type == b3_capsuleShape || shapeB->type == b3_hullShape ); + // B3_ASSERT( bodyB->type != b3_staticBody ); + + // Is either body static? + // Note: it is possible to have a dynamic mesh collide with a static convex shape. Maybe I should disallow this. + if ( bodyA->type == b3_staticBody || bodyB->type == b3_staticBody ) + { + contact->flags |= b3_contactStaticFlag; + } + + B3_ASSERT( shapeA->sensorIndex == B3_NULL_INDEX && shapeB->sensorIndex == B3_NULL_INDEX ); + + if ( ( shapeA->flags & b3_enableContactEvents ) || ( shapeB->flags & b3_enableContactEvents ) ) + { + contact->flags |= b3_contactEnableContactEvents; + } + + if ( ( shapeA->flags & b3_enableSpeculative ) && ( shapeB->flags & b3_enableSpeculative ) ) + { + contact->flags |= b3_enableSpeculativePoints; + } + + // Connect to body A + { + contact->edges[0].bodyId = shapeA->bodyId; + contact->edges[0].prevKey = B3_NULL_INDEX; + contact->edges[0].nextKey = bodyA->headContactKey; + + int keyA = ( contactId << 1 ) | 0; + int headContactKey = bodyA->headContactKey; + if ( headContactKey != B3_NULL_INDEX ) + { + b3Contact* headContact = b3Array_Get( world->contacts, headContactKey >> 1 ); + headContact->edges[headContactKey & 1].prevKey = keyA; + } + bodyA->headContactKey = keyA; + bodyA->contactCount += 1; + } + + // Connect to body B + { + contact->edges[1].bodyId = shapeB->bodyId; + contact->edges[1].prevKey = B3_NULL_INDEX; + contact->edges[1].nextKey = bodyB->headContactKey; + + int keyB = ( contactId << 1 ) | 1; + int headContactKey = bodyB->headContactKey; + if ( bodyB->headContactKey != B3_NULL_INDEX ) + { + b3Contact* headContact = b3Array_Get( world->contacts, headContactKey >> 1 ); + headContact->edges[headContactKey & 1].prevKey = keyB; + } + bodyB->headContactKey = keyB; + bodyB->contactCount += 1; + } + + // Add to pair set for fast lookup + uint64_t pairKey = b3ShapePairKey( shapeIdA, shapeIdB, childIndex ); + b3AddKey( &world->broadPhase.pairSet, pairKey ); + + // Contacts are created as non-touching. Later if they are found to be touching + // they will link islands and be moved into the constraint graph. + b3Array_Push( set->contactIndices, contactId ); + + float radiusA = 0.0f; + if ( typeA == b3_sphereShape ) + { + radiusA = shapeA->sphere.radius; + } + else if ( typeA == b3_capsuleShape ) + { + radiusA = shapeA->capsule.radius; + } + + float radiusB = 0.0f; + if ( typeB == b3_sphereShape ) + { + radiusB = shapeB->sphere.radius; + } + else if ( typeB == b3_capsuleShape ) + { + radiusB = shapeB->capsule.radius; + } + + float maxRadius = b3MaxFloat( radiusA, radiusB ); + + // Assuming the rolling resistance doesn't change + contact->rollingResistance = + b3MaxFloat( b3GetShapeMaterials( shapeA )[0].rollingResistance, b3GetShapeMaterials( shapeB )[0].rollingResistance ) * + maxRadius; + + if ( ( shapeA->flags & b3_enablePreSolveEvents ) || ( shapeB->flags & b3_enablePreSolveEvents ) ) + { + contact->flags |= b3_simEnablePreSolveEvents; + } +} + +// A contact is destroyed when: +// - broad-phase proxies stop overlapping +// - a body is destroyed +// - a body is disabled +// - a body changes type from dynamic to kinematic or static +// - a shape is destroyed +// - contact filtering is modified +void b3DestroyContact( b3World* world, b3Contact* contact, bool wakeBodies ) +{ + // Remove pair from set + uint64_t pairKey = b3ShapePairKey( contact->shapeIdA, contact->shapeIdB, contact->childIndex ); + b3RemoveKey( &world->broadPhase.pairSet, pairKey ); + + b3FreeManifolds( world, contact->manifolds, contact->manifoldCount ); + contact->manifolds = NULL; + contact->manifoldCount = 0; + + b3ContactEdge* edgeA = contact->edges + 0; + b3ContactEdge* edgeB = contact->edges + 1; + + int bodyIdA = edgeA->bodyId; + int bodyIdB = edgeB->bodyId; + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + + uint32_t flags = contact->flags; + bool touching = ( flags & b3_contactTouchingFlag ) != 0; + + // End touch event + if ( touching && ( flags & b3_contactEnableContactEvents ) != 0 ) + { + uint16_t worldId = world->worldId; + const b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA ); + const b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB ); + b3ShapeId shapeIdA = { shapeA->id + 1, worldId, shapeA->generation }; + b3ShapeId shapeIdB = { shapeB->id + 1, worldId, shapeB->generation }; + + b3ContactId contactId = { + .index1 = contact->contactId + 1, + .world0 = world->worldId, + .padding = 0, + .generation = contact->generation, + }; + + b3ContactEndTouchEvent event = { + .shapeIdA = shapeIdA, + .shapeIdB = shapeIdB, + .contactId = contactId, + }; + + b3Array_Push( world->contactEndEvents[world->endEventArrayIndex], event ); + } + + // Remove from body A + if ( edgeA->prevKey != B3_NULL_INDEX ) + { + b3Contact* prevContact = b3Array_Get( world->contacts, edgeA->prevKey >> 1 ); + b3ContactEdge* prevEdge = prevContact->edges + ( edgeA->prevKey & 1 ); + prevEdge->nextKey = edgeA->nextKey; + } + + if ( edgeA->nextKey != B3_NULL_INDEX ) + { + b3Contact* nextContact = b3Array_Get( world->contacts, edgeA->nextKey >> 1 ); + b3ContactEdge* nextEdge = nextContact->edges + ( edgeA->nextKey & 1 ); + nextEdge->prevKey = edgeA->prevKey; + } + + int contactId = contact->contactId; + + int edgeKeyA = ( contactId << 1 ) | 0; + if ( bodyA->headContactKey == edgeKeyA ) + { + bodyA->headContactKey = edgeA->nextKey; + } + + bodyA->contactCount -= 1; + + // Remove from body B + if ( edgeB->prevKey != B3_NULL_INDEX ) + { + b3Contact* prevContact = b3Array_Get( world->contacts, edgeB->prevKey >> 1 ); + b3ContactEdge* prevEdge = prevContact->edges + ( edgeB->prevKey & 1 ); + prevEdge->nextKey = edgeB->nextKey; + } + + if ( edgeB->nextKey != B3_NULL_INDEX ) + { + b3Contact* nextContact = b3Array_Get( world->contacts, edgeB->nextKey >> 1 ); + b3ContactEdge* nextEdge = nextContact->edges + ( edgeB->nextKey & 1 ); + nextEdge->prevKey = edgeB->prevKey; + } + + int edgeKeyB = ( contactId << 1 ) | 1; + if ( bodyB->headContactKey == edgeKeyB ) + { + bodyB->headContactKey = edgeB->nextKey; + } + + bodyB->contactCount -= 1; + + if ( contact->flags & b3_simMeshContact ) + { + b3Array_Destroy( contact->meshContact.triangleCache ); + } + + // Remove contact from the array that owns it + if ( contact->islandId != B3_NULL_INDEX ) + { + b3UnlinkContact( world, contact ); + } + + if ( contact->colorIndex != B3_NULL_INDEX ) + { + // contact is an active constraint + B3_ASSERT( contact->setIndex == b3_awakeSet ); + bool meshContact = contact->flags & b3_simMeshContact; + b3RemoveContactFromGraph( world, bodyIdA, bodyIdB, contact->colorIndex, contact->localIndex, meshContact ); + } + else + { + // contact is non-touching or is sleeping or is a sensor + B3_ASSERT( contact->setIndex != b3_awakeSet || ( contact->flags & b3_contactTouchingFlag ) == 0 ); + b3SolverSet* set = b3Array_Get( world->solverSets, contact->setIndex ); + + int localIndex = contact->localIndex; + int movedIndex = b3Array_RemoveSwap( set->contactIndices, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + int movedContactIndex = set->contactIndices.data[localIndex]; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex ); + movedContact->localIndex = localIndex; + } + } + + // Free contact and id (preserve generation) + contact->contactId = B3_NULL_INDEX; + contact->setIndex = B3_NULL_INDEX; + contact->colorIndex = B3_NULL_INDEX; + contact->localIndex = B3_NULL_INDEX; + b3FreeId( &world->contactIdPool, contactId ); + + if ( wakeBodies && touching ) + { + b3WakeBody( world, bodyA ); + b3WakeBody( world, bodyB ); + } +} + +static bool b3ComputeConvexManifold( b3World* world, int workerIndex, b3Contact* contact, const b3Shape* shapeA, + b3WorldTransform xfA, const b3Shape* shapeB, b3WorldTransform xfB, b3Arena arena ) +{ + b3ShapeType typeA = shapeA->type; + b3ShapeType typeB = shapeB->type; + + b3ContactCache* cache = &contact->convexContact.cache; + + int pointCapacity = 32; + b3LocalManifoldPoint* pointBuffer = (b3LocalManifoldPoint*)b3Bump( &arena, pointCapacity * sizeof( b3LocalManifoldPoint ) ); + + b3LocalManifold geomManifold = { 0 }; + geomManifold.points = pointBuffer; + + b3Transform transformBtoA = b3InvMulWorldTransforms( xfA, xfB ); + + if ( typeA == b3_sphereShape ) + { + B3_ASSERT( typeB == b3_sphereShape ); + b3CollideSpheres( &geomManifold, pointCapacity, &shapeA->sphere, &shapeB->sphere, transformBtoA ); + } + else if ( typeA == b3_capsuleShape ) + { + if ( typeB == b3_sphereShape ) + { + b3CollideCapsuleAndSphere( &geomManifold, pointCapacity, &shapeA->capsule, &shapeB->sphere, transformBtoA ); + } + else + { + B3_ASSERT( typeB == b3_capsuleShape ); + b3CollideCapsules( &geomManifold, pointCapacity, &shapeA->capsule, &shapeB->capsule, transformBtoA ); + } + } + else + { + B3_ASSERT( typeA == b3_hullShape ); + + if ( typeB == b3_sphereShape ) + { + b3CollideHullAndSphere( &geomManifold, pointCapacity, shapeA->hull, &shapeB->sphere, transformBtoA, + &cache->simplexCache ); + } + else if ( typeB == b3_capsuleShape ) + { + b3CollideHullAndCapsule( &geomManifold, pointCapacity, shapeA->hull, &shapeB->capsule, transformBtoA, + &cache->simplexCache ); + } + else + { + B3_ASSERT( typeB == b3_hullShape ); + b3CollideHulls( &geomManifold, pointCapacity, shapeA->hull, shapeB->hull, transformBtoA, &cache->satCache ); + world->taskContexts.data[workerIndex].satCallCount += 1; + world->taskContexts.data[workerIndex].satCacheHitCount += cache->satCache.hit; + } + } + + if ( geomManifold.pointCount == 0 ) + { + if ( contact->manifoldCount > 0 ) + { + b3FreeManifolds( world, contact->manifolds, contact->manifoldCount ); + contact->manifolds = NULL; + contact->manifoldCount = 0; + } + + return false; + } + + b3ManifoldPoint oldPoints[B3_MAX_MANIFOLD_POINTS]; + int oldCount = 0; + + if ( contact->manifoldCount == 0 ) + { + contact->manifolds = b3AllocateManifolds( world, 1 ); + contact->manifoldCount = 1; + } + else + { + oldCount = contact->manifolds[0].pointCount; + memcpy( oldPoints, contact->manifolds[0].points, oldCount * sizeof( b3ManifoldPoint ) ); + } + + b3Manifold* manifold = contact->manifolds; + manifold->pointCount = geomManifold.pointCount; + + b3Matrix3 matrixA = b3MakeMatrixFromQuat( xfA.q ); + manifold->normal = b3MulMV( matrixA, geomManifold.normal ); + + // Store point data in contact + for ( int i = 0; i < geomManifold.pointCount; ++i ) + { + const b3LocalManifoldPoint* source = geomManifold.points + i; + b3ManifoldPoint* target = manifold->points + i; + + // Contact points are computed in frame A + target->anchorA = b3MulMV( matrixA, source->point ); + target->anchorB = b3Add( target->anchorA, b3SubPos( xfA.p, xfB.p ) ); + target->separation = source->separation; + target->featureId = b3MakeFeatureId( source->pair ); + target->triangleIndex = B3_NULL_INDEX; + target->normalVelocity = 0.0f; + } + + // Copy impulses from old points + for ( int i = 0; i < geomManifold.pointCount; ++i ) + { + b3ManifoldPoint* pt2 = manifold->points + i; + pt2->totalNormalImpulse = 0.0f; + pt2->persisted = false; + + for ( int j = 0; j < oldCount; ++j ) + { + b3ManifoldPoint* pt1 = oldPoints + j; + + if ( pt2->featureId == pt1->featureId ) + { + pt2->normalImpulse = pt1->normalImpulse; + pt2->persisted = true; + + // claimed + pt1->featureId = UINT32_MAX; + + break; + } + } + + if ( pt2->persisted == false ) + { + pt2->normalImpulse = 0.0f; + } + } + + return true; +} + +static bool b3UpdateConvexContact( b3World* world, int workerIndex, b3Contact* contact, b3Shape* shapeA, b3WorldTransform xfA, + b3Shape* shapeB, b3WorldTransform xfB, bool flip, b3Arena arena ) +{ + // Compute new manifold + bool touching = b3ComputeConvexManifold( world, workerIndex, contact, shapeA, xfA, shapeB, xfB, arena ); + + if ( touching == false ) + { + B3_ASSERT( contact->manifolds == NULL && contact->manifoldCount == 0 ); + return false; + } + + B3_ASSERT( contact->manifoldCount == 1 ); + + if ( flip ) + { + // Not flipping the feature ids because they just need to match and flipping is consistent. + b3Manifold* manifold = contact->manifolds + 0; + manifold->normal = b3Neg( manifold->normal ); + int pointCount = manifold->pointCount; + for ( int i = 0; i < pointCount; ++i ) + { + b3ManifoldPoint* mp = manifold->points + i; + B3_SWAP( mp->anchorA, mp->anchorB ); + } + } + + const b3SurfaceMaterial* materialA = b3GetShapeMaterials( shapeA ); + const b3SurfaceMaterial* materialB = b3GetShapeMaterials( shapeB ); + + // Keep these updated in case the values on the shapes are modified + contact->friction = + world->frictionCallback( materialA->friction, materialA->userMaterialId, materialB->friction, materialB->userMaterialId ); + contact->restitution = world->restitutionCallback( materialA->restitution, materialA->userMaterialId, materialB->restitution, + materialB->userMaterialId ); + + if ( materialA->rollingResistance > 0.0f || materialB->rollingResistance > 0.0f ) + { + b3ShapeType typeA = shapeA->type; + b3ShapeType typeB = shapeB->type; + + float radiusA = 0.0f; + if ( typeA == b3_sphereShape ) + { + radiusA = shapeA->sphere.radius; + } + else if ( typeA == b3_capsuleShape ) + { + radiusA = shapeA->capsule.radius; + } + else if ( typeA == b3_hullShape ) + { + radiusA = 0.25f * shapeA->hull->innerRadius; + } + + float radiusB = 0.0f; + if ( typeB == b3_sphereShape ) + { + radiusB = shapeB->sphere.radius; + } + else if ( typeB == b3_capsuleShape ) + { + radiusB = shapeB->capsule.radius; + } + else if ( typeB == b3_hullShape ) + { + radiusB = 0.25f * shapeB->hull->innerRadius; + } + + float maxRadius = b3MaxFloat( radiusA, radiusB ); + contact->rollingResistance = b3MaxFloat( materialA->rollingResistance, materialB->rollingResistance ) * maxRadius; + } + else + { + contact->rollingResistance = 0.0f; + } + + b3Vec3 tangentVelocityA = b3RotateVector( xfA.q, materialA->tangentVelocity ); + b3Vec3 tangentVelocityB = b3RotateVector( xfB.q, materialB->tangentVelocity ); + contact->tangentVelocity = b3Sub( tangentVelocityA, tangentVelocityB ); + + if ( world->preSolveFcn && ( contact->flags & b3_simEnablePreSolveEvents ) != 0 ) + { + b3ShapeId shapeIdA = { shapeA->id + 1, world->worldId, shapeA->generation }; + b3ShapeId shapeIdB = { shapeB->id + 1, world->worldId, shapeB->generation }; + + // this call assumes thread safety + b3Pos point = b3OffsetPos( xfA.p, contact->manifolds[0].points[0].anchorA ); + b3Vec3 normal = contact->manifolds[0].normal; + touching = world->preSolveFcn( shapeIdA, shapeIdB, point, normal, world->preSolveContext ); + if ( touching == false ) + { + // disable contact + b3FreeManifolds( world, contact->manifolds, contact->manifoldCount ); + contact->manifolds = NULL; + contact->manifoldCount = 0; + return false; + } + } + + if ( ( shapeA->flags & b3_enableHitEvents ) || ( shapeB->flags & b3_enableHitEvents ) ) + { + contact->flags |= b3_simEnableHitEvent; + } + else + { + contact->flags &= ~b3_simEnableHitEvent; + } + + return true; +} + +// Update the contact manifold and touching status. +// Note: do not assume the shape AABBs are overlapping or are valid. +bool b3UpdateContact( b3World* world, int workerIndex, b3Contact* contact, b3Shape* shapeA, b3Vec3 localCenterA, + b3WorldTransform xfA, b3Shape* shapeB, b3Vec3 localCenterB, b3WorldTransform xfB, bool isFast, + b3Arena arena ) +{ + bool touching; + + B3_ASSERT( shapeB->type != b3_compoundShape ); + + if ( shapeA->type == b3_compoundShape ) + { + int childIndex = contact->childIndex; + b3ChildShape child = b3GetCompoundChild( shapeA->compound, childIndex ); + + // Temporary child shape to match existing function signatures + b3Shape childShapeA; + memcpy( &childShapeA, shapeA, sizeof( b3Shape ) ); + + childShapeA.type = child.type; + + if ( child.type == b3_capsuleShape ) + { + childShapeA.capsule = child.capsule; + if ( shapeB->type == b3_hullShape ) + { + // Flip + bool flip = true; + touching = b3UpdateConvexContact( world, workerIndex, contact, shapeB, xfB, &childShapeA, xfA, flip, arena ); + } + else + { + bool flip = false; + touching = b3UpdateConvexContact( world, workerIndex, contact, &childShapeA, xfA, shapeB, xfB, flip, arena ); + } + } + else if ( child.type == b3_hullShape ) + { + childShapeA.hull = child.hull; + b3WorldTransform xfChild = b3MulWorldTransforms( xfA, child.transform ); + bool flip = false; + touching = b3UpdateConvexContact( world, workerIndex, contact, &childShapeA, xfChild, shapeB, xfB, flip, arena ); + } + else if ( child.type == b3_meshShape ) + { + childShapeA.mesh = child.mesh; + b3WorldTransform xfChild = b3MulWorldTransforms( xfA, child.transform ); + + touching = b3ComputeMeshManifolds( world, workerIndex, contact, &childShapeA, child.materialIndices, xfChild, shapeB, + xfB, isFast, arena ); + + if ( touching && ( ( shapeA->flags & b3_enableHitEvents ) || ( shapeB->flags & b3_enableHitEvents ) ) ) + { + contact->flags |= b3_simEnableHitEvent; + } + else + { + contact->flags &= ~b3_simEnableHitEvent; + } + + B3_ASSERT( ( touching == true && contact->manifoldCount > 0 ) || + ( touching == false && contact->manifoldCount == 0 ) ); + } + else + { + B3_ASSERT( child.type == b3_sphereShape ); + + childShapeA.sphere = child.sphere; + if ( shapeB->type == b3_capsuleShape || shapeB->type == b3_hullShape ) + { + // Flip + bool flip = true; + touching = b3UpdateConvexContact( world, workerIndex, contact, shapeB, xfB, &childShapeA, xfA, flip, arena ); + } + else + { + bool flip = false; + touching = b3UpdateConvexContact( world, workerIndex, contact, &childShapeA, xfA, shapeB, xfB, flip, arena ); + } + } + + // The anchor is relative to the child origin but oriented in world space. + // Offset the anchor to be relative to the compound origin. + int manifoldCount = contact->manifoldCount; + b3Vec3 offset = b3RotateVector( xfA.q, child.transform.p ); + for ( int i = 0; i < manifoldCount; ++i ) + { + b3Manifold* manifold = contact->manifolds + i; + int pointCount = manifold->pointCount; + for ( int j = 0; j < pointCount; ++j ) + { + b3ManifoldPoint* mp = manifold->points + j; + mp->anchorA = b3Add( mp->anchorA, offset ); + } + } + } + else if ( shapeA->type == b3_meshShape || shapeA->type == b3_heightShape ) + { + // Does this contact touch a mesh or height-field? + + // Compute mesh manifolds + touching = b3ComputeMeshManifolds( world, workerIndex, contact, shapeA, NULL, xfA, shapeB, xfB, isFast, arena ); + + if ( touching && ( ( shapeA->flags & b3_enableHitEvents ) || ( shapeB->flags & b3_enableHitEvents ) ) ) + { + contact->flags |= b3_simEnableHitEvent; + } + else + { + contact->flags &= ~b3_simEnableHitEvent; + } + + B3_ASSERT( ( touching == true && contact->manifoldCount > 0 ) || ( touching == false && contact->manifoldCount == 0 ) ); + } + else + { + // Convex-vs-convex + bool flip = false; + touching = b3UpdateConvexContact( world, workerIndex, contact, shapeA, xfA, shapeB, xfB, flip, arena ); + } + + if ( touching ) + { + b3Vec3 centerA = b3RotateVector( xfA.q, localCenterA ); + b3Vec3 centerB = b3RotateVector( xfB.q, localCenterB ); + + // Adjust anchors to be relative to center of mass + for ( int i = 0; i < contact->manifoldCount; ++i ) + { + b3Manifold* manifold = contact->manifolds + i; + for ( int j = 0; j < manifold->pointCount; ++j ) + { + b3ManifoldPoint* mp = manifold->points + j; + mp->anchorA = b3Sub( mp->anchorA, centerA ); + mp->anchorB = b3Sub( mp->anchorB, centerB ); + } + } + + contact->flags |= b3_simTouchingFlag; + } + else + { + contact->flags &= ~b3_simTouchingFlag; + } + + return touching; +} diff --git a/vendor/box3d/src/src/contact.h b/vendor/box3d/src/src/contact.h new file mode 100644 index 000000000..ad9fa74d6 --- /dev/null +++ b/vendor/box3d/src/src/contact.h @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "arena_allocator.h" +#include "container.h" + +#include "box3d/collision.h" +#include "box3d/types.h" + +#define B3_FORCE_GHOST_COLLISIONS 0 + +typedef struct b3Shape b3Shape; +typedef struct b3World b3World; + +typedef union b3ContactCache +{ + b3SATCache satCache; + b3SimplexCache simplexCache; +} b3ContactCache; + +typedef struct b3TriangleCache +{ + int triangleIndex; + b3ContactCache cache; +} b3TriangleCache; + +b3DeclareArray( b3TriangleCache ); + +enum b3ContactFlags +{ + // Set when the solid shapes are touching. + b3_contactTouchingFlag = 0x00000001, + + // Contact has a hit event + b3_contactHitEventFlag = 0x00000002, + + // This contact wants contact events + b3_contactEnableContactEvents = 0x00000004, + + // This is contact is between a dynamic and static body + b3_contactStaticFlag = 0x00000008, + + b3_contactRecycleFlag = 0x00000010, + + // Set when the shapes are touching + b3_simTouchingFlag = 0x00010000, + + // This contact no longer has overlapping AABBs + b3_simDisjoint = 0x00020000, + + // This contact started touching + b3_simStartedTouching = 0x00040000, + + // This contact stopped touching + b3_simStoppedTouching = 0x00080000, + + // This contact has a hit event + b3_simEnableHitEvent = 0x00100000, + + // This contact wants pre-solve events + b3_simEnablePreSolveEvents = 0x00200000, + + // This is a mesh contact + b3_simMeshContact = 0x00400000, + + // Relative transform is cached for contact recycling + b3_relativeTransformValid = 0x00800000, + + // Enable speculative contact points + b3_enableSpeculativePoints = 0x01000000, +}; + +// A contact edge is used to connect bodies and contacts together +// in a contact graph where each body is a node and each contact +// is an edge. A contact edge belongs to a doubly linked list +// maintained in each attached body. Each contact has two contact +// edges, one for each attached body. +typedef struct b3ContactEdge +{ + int bodyId; + int prevKey; + int nextKey; +} b3ContactEdge; + +typedef struct b3MeshContact +{ + b3Array( b3TriangleCache ) triangleCache; + b3AABB queryBounds; +} b3MeshContact; + +typedef struct b3ConvexContact +{ + b3ContactCache cache; +} b3ConvexContact; + +// Represents the persistent interaction between two shapes +typedef struct b3Contact +{ + // index of simulation set stored in b3World + // B3_NULL_INDEX when slot is free + int setIndex; + + // index into the constraint graph color array + // B3_NULL_INDEX for non-touching or sleeping contacts + // B3_NULL_INDEX when slot is free + int colorIndex; + + // contact index within set or graph color + // B3_NULL_INDEX when slot is free + int localIndex; + + b3ContactEdge edges[2]; + int shapeIdA; + int shapeIdB; + int childIndex; + + // A contact only belongs to an island if touching, otherwise B3_NULL_INDEX. + int islandId; + + // Index into the island's contacts array for O(1) swap-removal. + // B3_NULL_INDEX when not in an island. + int islandIndex; + + // Back index into b3World::contacts + int contactId; + + // These are transient and cached for improved performance. B3_NULL_INDEX for static bodies. + int bodySimIndexA; + int bodySimIndexB; + + // b3ContactFlags + uint32_t flags; + + b3Manifold* manifolds; + int manifoldCount; + + // Cache for contact recycling. + b3Quat cachedRotationA; + b3Quat cachedRotationB; + b3Transform cachedRelativePose; + + // Mixed friction and restitution + float friction; + + // Usage determined by b3_simMeshContact in simFlags + union + { + b3ConvexContact convexContact; + b3MeshContact meshContact; + }; + + float restitution; + float rollingResistance; + b3Vec3 tangentVelocity; + + // This is monotonically advanced when a contact is allocated in this slot + // Used to check for invalid b3ContactId + uint32_t generation; +} b3Contact; + +typedef struct b3ContactSpec +{ + int contactId; + + // Start of the global manifold constraint array + int manifoldStart; + uint16_t manifoldCount; +} b3ContactSpec; + +b3DeclareArray( b3ContactSpec ); + +void b3InitializeContactRegisters( void ); + +void b3CreateContact( b3World* world, b3Shape* shapeA, b3Shape* shapeB, int childIndex ); +void b3DestroyContact( b3World* world, b3Contact* contact, bool wakeBodies ); + +bool b3UpdateContact( b3World* world, int workerIndex, b3Contact* contact, b3Shape* shapeA, b3Vec3 localCenterA, b3WorldTransform xfA, + b3Shape* shapeB, b3Vec3 localCenterB, b3WorldTransform xfB, bool isFast, b3Arena arena ); + +bool b3ComputeMeshManifolds( b3World* world, int workerIndex, b3Contact* contact, const b3Shape* shapeA, const int* materialMap, + b3WorldTransform xfA, const b3Shape* shapeB, b3WorldTransform xfB, bool isFast, b3Arena arena ); diff --git a/vendor/box3d/src/src/contact_solver.c b/vendor/box3d/src/src/contact_solver.c new file mode 100644 index 000000000..501f85876 --- /dev/null +++ b/vendor/box3d/src/src/contact_solver.c @@ -0,0 +1,2472 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "contact_solver.h" + +#include "body.h" +#include "constraint_graph.h" +#include "contact.h" +#include "core.h" +#include "math_internal.h" +#include "physics_world.h" +#include "platform.h" +#include "solver_set.h" + +#if B3_ENABLE_VALIDATION +#include "shape.h" +#endif + +#define FIXED_ANCHORS 1 + +// contact separation for sub-stepping +// s = s0 + dot(cB + rB - cA - rA, normal) +// normal is held constant +// body positions c can translation and anchors r can rotate +// s(t) = s0 + dot(cB(t) + rB(t) - cA(t) - rA(t), normal) +// s(t) = s0 + dot(cB0 + dpB + rot(dqB, rB0) - cA0 - dpA - rot(dqA, rA0), normal) +// s(t) = s0 + dot(cB0 - cA0, normal) + dot(dpB - dpA + rot(dqB, rB0) - rot(dqA, rA0), normal) +// s_base = s0 + dot(cB0 - cA0, normal) + +// Prepare a mesh constraints +void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( prepare_contact, "Prepare Contact", b3_colorYellow, true ); + + b3World* world = context->world; + b3BodySim* bodySims = context->sims; + b3BodyState* bodyStates = context->states; + + float warmStartScale = world->enableWarmStarting ? 1.0f : 0.0f; + + // Need to use spans in order to find the associated b2Contact, which is per color + b3ContactPrepareSpan* spans = context->contactPrepareSpans; + b3ManifoldConstraint* manifoldBase = context->manifoldConstraints; + b3ContactConstraint* base = context->contactConstraints; + + // Overflow constraints are stored separately + if ( block.blockType == b3_overflowBlock ) + { + b3GraphColor* overflow = world->constraintGraph.colors + B3_OVERFLOW_INDEX; + spans = context->overflowSpans; + manifoldBase = overflow->manifoldConstraints; + base = overflow->contactConstraints; + } + + int index = block.startIndex; + int endIndex = block.startIndex + block.count; + + // Find color for start index. Linear search but fast. + int colorIndex = 0; + while ( spans[colorIndex + 1].start <= index ) + { + colorIndex += 1; + } + + // Loop over block + while ( index < endIndex ) + { + int colorStart = spans[colorIndex].start; + int colorEndIndex = b3MinInt( spans[colorIndex + 1].start, endIndex ); + b3ContactSpec* specs = spans[colorIndex].contacts; + + // Loop over color + for ( ; index < colorEndIndex; ++index ) + { + b3ContactConstraint* contactConstraint = base + index; + + int localIndex = index - colorStart; + B3_ASSERT( 0 <= localIndex && localIndex < spans[colorIndex].count ); + int contactId = specs[localIndex].contactId; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + B3_ASSERT( contact->contactId == contactId ); + + int indexA = contact->bodySimIndexA; + int indexB = contact->bodySimIndexB; + +#if B3_ENABLE_VALIDATION + if ( indexA != B3_NULL_INDEX ) + { + b3Body* bodyA = b3Array_Get( world->bodies, contact->edges[0].bodyId ); + B3_ASSERT( indexA == bodyA->localIndex ); + } + + if ( indexB != B3_NULL_INDEX ) + { + b3Body* bodyB = b3Array_Get( world->bodies, contact->edges[1].bodyId ); + B3_ASSERT( indexB == bodyB->localIndex ); + } +#endif + + // Body A data + float mA; + b3Matrix3 iA; + b3Vec3 vA; + b3Vec3 wA; + + if ( indexA == B3_NULL_INDEX ) + { + mA = 0.0f; + iA = b3Mat3_zero; + vA = b3Vec3_zero; + wA = b3Vec3_zero; + } + else + { + b3BodySim* simA = bodySims + indexA; + mA = simA->invMass; + iA = simA->invInertiaWorld; + + b3BodyState* stateA = bodyStates + indexA; + vA = stateA->linearVelocity; + wA = stateA->angularVelocity; + } + + // Body B data + float mB; + b3Matrix3 iB; + b3Vec3 vB; + b3Vec3 wB; + + if ( indexB == B3_NULL_INDEX ) + { + mB = 0.0f; + iB = b3Mat3_zero; + vB = b3Vec3_zero; + wB = b3Vec3_zero; + } + else + { + b3BodySim* simB = bodySims + indexB; + mB = simB->invMass; + iB = simB->invInertiaWorld; + + b3BodyState* stateB = bodyStates + indexB; + vB = stateB->linearVelocity; + wB = stateB->angularVelocity; + } + + int manifoldCount = contact->manifoldCount; + contactConstraint->contact = contact; + contactConstraint->manifoldCount = manifoldCount; + contactConstraint->indexA = indexA; + contactConstraint->indexB = indexB; + contactConstraint->invIA = iA; + contactConstraint->invMassA = mA; + contactConstraint->invIB = iB; + contactConstraint->invMassB = mB; + contactConstraint->rollingMass = b3InvertMatrix( b3AddMM( iA, iB ) ); + contactConstraint->softness = + ( contact->flags & b3_contactStaticFlag ) != 0 ? context->staticSoftness : context->contactSoftness; + contactConstraint->friction = contact->friction; + contactConstraint->restitution = contact->restitution; + contactConstraint->rollingResistance = contact->rollingResistance; + + b3ManifoldConstraint* manifoldConstraints = manifoldBase + specs[localIndex].manifoldStart; + contactConstraint->constraints = manifoldConstraints; + + for ( int manifoldIndex = 0; manifoldIndex < manifoldCount; ++manifoldIndex ) + { + b3Manifold* manifold = contact->manifolds + manifoldIndex; + b3ManifoldConstraint* constraint = manifoldConstraints + manifoldIndex; + int pointCount = manifold->pointCount; + b3Vec3 normal = manifold->normal; + b3Vec3 tangent1 = b3Perp( normal ); + b3Vec3 tangent2 = b3Cross( tangent1, normal ); + + constraint->pointCount = pointCount; + constraint->normal = normal; + constraint->tangent1 = tangent1; + constraint->tangent2 = tangent2; + + // Stiffer for static contacts to avoid bodies getting pushed through the ground + constraint->tangentVelocity1 = b3Dot( contact->tangentVelocity, constraint->tangent1 ); + constraint->tangentVelocity2 = b3Dot( contact->tangentVelocity, constraint->tangent2 ); + + b3Vec3 centerA = b3Vec3_zero; + b3Vec3 centerB = b3Vec3_zero; + + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + b3ManifoldConstraintPoint* cp = constraint->points + pointIndex; + + // Copy data from manifold point + b3ManifoldPoint* mp = manifold->points + pointIndex; + cp->rA = mp->anchorA; + cp->rB = mp->anchorB; + cp->baseSeparation = mp->separation - b3Dot( b3Sub( cp->rB, cp->rA ), normal ); + cp->normalImpulse = warmStartScale * mp->normalImpulse; + cp->totalNormalImpulse = 0.0f; + + b3Vec3 rA = cp->rA; + b3Vec3 rB = cp->rB; + + b3Vec3 rnA = b3Cross( rA, normal ); + b3Vec3 rnB = b3Cross( rB, normal ); + float kNormal = mA + mB + b3Dot( rnA, b3MulMV( iA, rnA ) ) + b3Dot( rnB, b3MulMV( iB, rnB ) ); + cp->normalMass = kNormal > 0.0f ? 1.0f / kNormal : 0.0f; + + // Save relative velocity for restitution + b3Vec3 vrA = b3Add( vA, b3Cross( wA, rA ) ); + b3Vec3 vrB = b3Add( vB, b3Cross( wB, rB ) ); + cp->relativeVelocity = b3Dot( normal, b3Sub( vrB, vrA ) ); + + centerA = b3Add( centerA, rA ); + centerB = b3Add( centerB, rB ); + } + + float invCount = 1.0f / pointCount; + centerA = b3MulSV( invCount, centerA ); + centerB = b3MulSV( invCount, centerB ); + constraint->originA = centerA; + constraint->originB = centerB; + + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + b3ManifoldConstraintPoint* cp = constraint->points + pointIndex; + cp->leverArm = b3Distance( cp->rA, centerA ); + } + + b3Vec3 rtA1 = b3Cross( centerA, tangent1 ); + b3Vec3 rtA2 = b3Cross( centerA, tangent2 ); + b3Vec3 rtB1 = b3Cross( centerB, tangent1 ); + b3Vec3 rtB2 = b3Cross( centerB, tangent2 ); + + { + b3Matrix2 k; + k.cx.x = mA + mB + b3Dot( rtA1, b3MulMV( iA, rtA1 ) ) + b3Dot( rtB1, b3MulMV( iB, rtB1 ) ); + k.cy.y = mA + mB + b3Dot( rtA2, b3MulMV( iA, rtA2 ) ) + b3Dot( rtB2, b3MulMV( iB, rtB2 ) ); + k.cx.y = k.cy.x = b3Dot( rtA1, b3MulMV( iA, rtA2 ) ) + b3Dot( rtB1, b3MulMV( iB, rtB2 ) ); + + constraint->tangentMass = b3Invert2( k ); + constraint->frictionImpulse.x = warmStartScale * b3Dot( manifold->frictionImpulse, tangent1 ); + constraint->frictionImpulse.y = warmStartScale * b3Dot( manifold->frictionImpulse, tangent2 ); + } + + { + float k = b3Dot( normal, b3MulMV( b3AddMM( iA, iB ), normal ) ); + constraint->twistMass = k > 0.0f ? 1.0f / k : 0.0f; + constraint->twistImpulse = warmStartScale * manifold->twistImpulse; + } + + { + constraint->rollingImpulse = b3MulSV( warmStartScale, manifold->rollingImpulse ); + } + } + } + + // Advance to next color + colorIndex += 1; + } + + b3TracyCZoneEnd( prepare_contact ); +} + +void b3WarmStartContacts_Mesh( b3SolverBlock block, b3StepContext* context ) +{ + b3World* world = context->world; + b3GraphColor* color = world->constraintGraph.colors + block.colorIndex; + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + b3BodyState* states = awakeSet->bodyStates.data; + b3ContactConstraint* constraints = color->contactConstraints; + + // This is a dummy state to represent a static body because static bodies don't have a solver body. + b3BodyState dummyState = b3_identityBodyState; + + int startIndex = block.startIndex; + int endIndex = startIndex + block.count; + + for ( int constraintIndex = startIndex; constraintIndex < endIndex; ++constraintIndex ) + { + const b3ContactConstraint* contactConstraint = constraints + constraintIndex; + int indexA = contactConstraint->indexA; + int indexB = contactConstraint->indexB; + + b3BodyState* stateA = indexA == B3_NULL_INDEX ? &dummyState : states + indexA; + b3BodyState* stateB = indexB == B3_NULL_INDEX ? &dummyState : states + indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + float mA = contactConstraint->invMassA; + b3Matrix3 iA = contactConstraint->invIA; + float mB = contactConstraint->invMassB; + b3Matrix3 iB = contactConstraint->invIB; + + int manifoldCount = contactConstraint->manifoldCount; + for ( int manifoldIndex = 0; manifoldIndex < manifoldCount; ++manifoldIndex ) + { + b3ManifoldConstraint* constraint = contactConstraint->constraints + manifoldIndex; + + // Normal impulses + b3Vec3 normal = constraint->normal; + int pointCount = constraint->pointCount; + for ( int j = 0; j < pointCount; ++j ) + { + const b3ManifoldConstraintPoint* cp = constraint->points + j; + + // fixed anchors + b3Vec3 rA = cp->rA; + b3Vec3 rB = cp->rB; + + b3Vec3 impulse = b3MulSV( cp->normalImpulse, normal ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vA = b3MulSub( vA, mA, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + } + + // Central friction + { + b3Vec3 rA = constraint->originA; + b3Vec3 rB = constraint->originB; + b3Vec3 impulse = b3MulSV( constraint->frictionImpulse.x, constraint->tangent1 ); + impulse = b3Add( impulse, b3MulSV( constraint->frictionImpulse.y, constraint->tangent2 ) ); + + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vA = b3MulSub( vA, mA, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + } + + // Central twist friction + { + b3Vec3 impulse = b3MulSV( constraint->twistImpulse, constraint->normal ); + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + + // Rolling resistance + { + b3Vec3 impulse = constraint->rollingImpulse; + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } + } +} + +// Merged normal and friction loops. This is much more stable for the Jenga stack. +void b3SolveContacts_Mesh( b3SolverBlock block, b3StepContext* context, bool useBias ) +{ + b3World* world = context->world; + b3GraphColor* color = world->constraintGraph.colors + block.colorIndex; + b3ContactConstraint* contactConstraints = color->contactConstraints; + b3BodyState* states = context->states; + + // This is a dummy state to represent a static body because static bodies don't have a solver body. + b3BodyState dummyState = b3_identityBodyState; + + // The last block might not be full + int startIndex = block.startIndex; + int endIndex = startIndex + block.count; + + float inv_h = context->inv_h; + const float contactSpeed = context->world->contactSpeed; + + for ( int i = startIndex; i < endIndex; ++i ) + { + b3ContactConstraint* contactConstraint = contactConstraints + i; + int manifoldCount = contactConstraint->manifoldCount; + + int indexA = contactConstraint->indexA; + int indexB = contactConstraint->indexB; + + float mA = contactConstraint->invMassA; + b3Matrix3 iA = contactConstraint->invIA; + float mB = contactConstraint->invMassB; + b3Matrix3 iB = contactConstraint->invIB; + + b3BodyState* stateA = indexA == B3_NULL_INDEX ? &dummyState : states + indexA; + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Quat dqA = stateA->deltaRotation; + + b3BodyState* stateB = indexB == B3_NULL_INDEX ? &dummyState : states + indexB; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + b3Quat dqB = stateB->deltaRotation; + + b3Vec3 dp = b3Sub( stateB->deltaPosition, stateA->deltaPosition ); + b3Softness softness = contactConstraint->softness; + float friction = contactConstraint->friction; + float rollingResistance = contactConstraint->rollingResistance; + + for ( int j = 0; j < manifoldCount; ++j ) + { + b3ManifoldConstraint* constraint = contactConstraint->constraints + j; + + int pointCount = constraint->pointCount; + b3Vec3 normal = constraint->normal; + + float totalNormalImpulse = 0.0f; + float totalTwistLimit = 0.0f; + + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + b3ManifoldConstraintPoint* cp = constraint->points + pointIndex; + + // Fixed anchor points for applying impulses + b3Vec3 rA = cp->rA; + b3Vec3 rB = cp->rB; + + // compute current separation + // this is subject to round-off error if the anchor is far from the body center of mass + b3Vec3 ds = b3Add( dp, b3Sub( b3RotateVector( dqB, rB ), b3RotateVector( dqA, rA ) ) ); + float s = b3Dot( ds, normal ) + cp->baseSeparation; + + float velocityBias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( s > 0.0f ) + { + // speculative bias + velocityBias = s * inv_h; + } + else if ( useBias ) + { + velocityBias = b3MaxFloat( softness.massScale * softness.biasRate * s, -contactSpeed ); + massScale = softness.massScale; + impulseScale = softness.impulseScale; + } + + // relative normal velocity at contact + b3Vec3 vrA = b3Add( vA, b3Cross( wA, rA ) ); + b3Vec3 vrB = b3Add( vB, b3Cross( wB, rB ) ); + float vn = b3Dot( b3Sub( vrB, vrA ), normal ); + + // incremental normal impulse + float deltaImpulse = -cp->normalMass * ( massScale * vn + velocityBias ) - impulseScale * cp->normalImpulse; + + // clamp the accumulated impulse + float newImpulse = b3MaxFloat( cp->normalImpulse + deltaImpulse, 0.0f ); + deltaImpulse = newImpulse - cp->normalImpulse; + cp->normalImpulse = newImpulse; + cp->totalNormalImpulse += newImpulse; + + totalNormalImpulse += newImpulse; + totalTwistLimit += cp->leverArm * cp->normalImpulse; + + // apply normal impulse + b3Vec3 P = b3MulSV( deltaImpulse, normal ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + + // No friction when applying bias + if ( useBias == true ) + { + // Go to next manifold + continue; + } + + // Central twist friction + { + float twistSpeed = b3Dot( constraint->normal, b3Sub( wB, wA ) ); + float maxImpulse = friction * totalTwistLimit; + float deltaImpulse = -constraint->twistMass * twistSpeed; + float oldImpulse = constraint->twistImpulse; + constraint->twistImpulse = b3ClampFloat( oldImpulse + deltaImpulse, -maxImpulse, maxImpulse ); + deltaImpulse = constraint->twistImpulse - oldImpulse; + + wA = b3Sub( wA, b3MulMV( iA, b3MulSV( deltaImpulse, constraint->normal ) ) ); + wB = b3Add( wB, b3MulMV( iB, b3MulSV( deltaImpulse, constraint->normal ) ) ); + } + + // Rolling resistance + if ( rollingResistance > 0.0f ) + { + b3Vec3 deltaImpulse = b3Neg( b3MulMV( contactConstraint->rollingMass, b3Sub( wB, wA ) ) ); + b3Vec3 oldImpulse = constraint->rollingImpulse; + constraint->rollingImpulse = b3Add( oldImpulse, deltaImpulse ); + + float maxImpulse = rollingResistance * totalNormalImpulse; + float magSqr = b3Dot( constraint->rollingImpulse, constraint->rollingImpulse ); + if ( magSqr > maxImpulse * maxImpulse + FLT_EPSILON ) + { + constraint->rollingImpulse = b3MulSV( maxImpulse / sqrtf( magSqr ), constraint->rollingImpulse ); + } + + deltaImpulse = b3Sub( constraint->rollingImpulse, oldImpulse ); + + wA = b3Sub( wA, b3MulMV( iA, deltaImpulse ) ); + wB = b3Add( wB, b3MulMV( iB, deltaImpulse ) ); + } + + // Central friction + { + b3Vec3 tangent1 = constraint->tangent1; + b3Vec3 tangent2 = constraint->tangent2; + + // Fixed anchor points for applying impulses + b3Vec3 rA = constraint->originA; + b3Vec3 rB = constraint->originB; + + // Relative tangent velocity at contact + b3Vec3 vrA = b3Add( vA, b3Cross( wA, rA ) ); + b3Vec3 vrB = b3Add( vB, b3Cross( wB, rB ) ); + b3Vec3 vr = b3Sub( vrB, vrA ); + b3Vec2 vt = { + b3Dot( vr, tangent1 ) - constraint->tangentVelocity1, + b3Dot( vr, tangent2 ) - constraint->tangentVelocity2, + }; + + // Incremental tangent impulse + b3Vec2 tm = b3MulMV2( constraint->tangentMass, vt ); + b3Vec2 deltaImpulse = { -tm.x, -tm.y }; + b3Vec2 newImpulse = { + constraint->frictionImpulse.x + deltaImpulse.x, + constraint->frictionImpulse.y + deltaImpulse.y, + }; + + float maxImpulse = friction * totalNormalImpulse; + + // Clamp the accumulated impulse + float lengthSquared = b3Dot2( newImpulse, newImpulse ); + if ( lengthSquared > maxImpulse * maxImpulse ) + { + float scale = maxImpulse / sqrtf( lengthSquared ); + newImpulse.x *= scale; + newImpulse.y *= scale; + } + deltaImpulse = b3Sub2( newImpulse, constraint->frictionImpulse ); + constraint->frictionImpulse = newImpulse; + + // Apply delta impulse + b3Vec3 P = b3Blend2( deltaImpulse.x, tangent1, deltaImpulse.y, tangent2 ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } + } +} + +void b3ApplyRestitution_Mesh( b3SolverBlock block, b3StepContext* context ) +{ + b3World* world = context->world; + b3GraphColor* color = world->constraintGraph.colors + block.colorIndex; + b3BodyState* states = context->states; + b3ContactConstraint* constraints = color->contactConstraints; + + // This is a dummy state to represent a static body because static bodies don't have a solver body. + b3BodyState dummyState = b3_identityBodyState; + + int startIndex = block.startIndex; + int endIndex = startIndex + block.count; + + float threshold = context->world->restitutionThreshold; + + for ( int constraintIndex = startIndex; constraintIndex < endIndex; ++constraintIndex ) + { + const b3ContactConstraint* contactConstraint = constraints + constraintIndex; + float restitution = contactConstraint->restitution; + if ( restitution == 0.0f ) + { + continue; + } + + int indexA = contactConstraint->indexA; + int indexB = contactConstraint->indexB; + + b3BodyState* stateA = indexA == B3_NULL_INDEX ? &dummyState : states + indexA; + b3BodyState* stateB = indexB == B3_NULL_INDEX ? &dummyState : states + indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + float mA = contactConstraint->invMassA; + b3Matrix3 iA = contactConstraint->invIA; + float mB = contactConstraint->invMassB; + b3Matrix3 iB = contactConstraint->invIB; + + int manifoldCount = contactConstraint->manifoldCount; + for ( int manifoldIndex = 0; manifoldIndex < manifoldCount; ++manifoldIndex ) + { + b3ManifoldConstraint* cm = contactConstraint->constraints + manifoldIndex; + + b3Vec3 normal = cm->normal; + int pointCount = cm->pointCount; + B3_ASSERT( 0 < pointCount && pointCount <= B3_MAX_MANIFOLD_POINTS ); + + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + b3ManifoldConstraintPoint* cp = cm->points + pointIndex; + + // If the total normal impulse is zero then there was no collision + // this skips speculative contact points that didn't generate an impulse + // The max normal impulse is used in case there was a collision that moved away within the sub-step + // process + if ( cp->relativeVelocity > -threshold || cp->totalNormalImpulse == 0.0f ) + { + continue; + } + + // fixed anchor points + b3Vec3 rA = cp->rA; + b3Vec3 rB = cp->rB; + + // relative normal velocity at contact + b3Vec3 vrB = b3Add( vB, b3Cross( wB, rB ) ); + b3Vec3 vrA = b3Add( vA, b3Cross( wA, rA ) ); + float vn = b3Dot( b3Sub( vrB, vrA ), normal ); + + // compute normal impulse + float impulse = -cp->normalMass * ( vn + restitution * cp->relativeVelocity ); + + // clamp the accumulated impulse + float newImpulse = b3MaxFloat( cp->normalImpulse + impulse, 0.0f ); + impulse = newImpulse - cp->normalImpulse; + cp->normalImpulse = newImpulse; + cp->totalNormalImpulse += impulse; + + // apply contact impulse + b3Vec3 P = b3MulSV( impulse, normal ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } + } + } +} + +// Don't need to use spans for colors for this because the constraint to contact association +// is already linked by pointer. +void b3StoreImpulses_Mesh( b3SolverBlock block, b3StepContext* context, int workerIndex ) +{ + b3World* world = context->world; + + // Mirror b3PrepareContacts_Mesh: the per-color flat arrays and the overflow color + // each have their own (base, spans, manifoldBase). + b3ContactPrepareSpan* spans = context->contactPrepareSpans; + b3ContactConstraint* base = context->contactConstraints; + + if ( block.blockType == b3_overflowBlock ) + { + b3GraphColor* overflow = world->constraintGraph.colors + B3_OVERFLOW_INDEX; + spans = context->overflowSpans; + base = overflow->contactConstraints; + } + + b3TaskContext* taskContext = world->taskContexts.data + workerIndex; + b3BitSet* hitEventBitSet = &taskContext->hitEventBitSet; + bool hasHitEvents = taskContext->hasHitEvents; + float negHitThreshold = -world->hitEventThreshold; + + int index = block.startIndex; + int endIndex = block.startIndex + block.count; + + // Find color for start index. Linear search but fast. + int colorIndex = 0; + while ( spans[colorIndex + 1].start <= index ) + { + colorIndex += 1; + } + + // Loop over block + while ( index < endIndex ) + { + int colorStart = spans[colorIndex].start; + int colorEndIndex = b3MinInt( spans[colorIndex + 1].start, endIndex ); + + // Loop over color + for ( ; index < colorEndIndex; ++index ) + { + b3ContactConstraint* contactConstraint = base + index; + + int localIndex = index - colorStart; + B3_UNUSED( localIndex ); + B3_ASSERT( 0 <= localIndex && localIndex < spans[colorIndex].count ); + + // Having this contact pointer simplifies impulse storage + b3Contact* contact = contactConstraint->contact; + B3_ASSERT( contact != NULL ); + + // Catches the wrong-(base, spans) pairing: the contact pointer stashed by + // b3PrepareContacts_Mesh at this flat slot must reference the same contact + // the span at this slot describes. + B3_VALIDATE( contact->contactId == spans[colorIndex].contacts[localIndex].contactId ); + + int manifoldCount = contactConstraint->manifoldCount; + B3_ASSERT( manifoldCount == contact->manifoldCount ); + + bool checkHitEvents = ( contact->flags & b3_simEnableHitEvent ) != 0; + bool flagged = false; + + for ( int manifoldIndex = 0; manifoldIndex < manifoldCount; ++manifoldIndex ) + { + b3Manifold* manifold = contact->manifolds + manifoldIndex; + b3ManifoldConstraint* constraint = contactConstraint->constraints + manifoldIndex; + manifold->twistImpulse = constraint->twistImpulse; + manifold->frictionImpulse = b3Blend2( constraint->frictionImpulse.x, constraint->tangent1, + constraint->frictionImpulse.y, constraint->tangent2 ); + manifold->rollingImpulse = constraint->rollingImpulse; + + int count = constraint->pointCount; + B3_ASSERT( count == manifold->pointCount ); + for ( int pointIndex = 0; pointIndex < count; ++pointIndex ) + { + b3ManifoldConstraintPoint* cp = constraint->points + pointIndex; + b3ManifoldPoint* mp = manifold->points + pointIndex; + mp->normalImpulse = cp->normalImpulse; + mp->totalNormalImpulse = cp->totalNormalImpulse; + mp->normalVelocity = cp->relativeVelocity; + + if ( checkHitEvents && flagged == false && + mp->normalVelocity < negHitThreshold && mp->totalNormalImpulse > 0.0f ) + { + b3SetBit( hitEventBitSet, contact->contactId ); + hasHitEvents = true; + flagged = true; + } + } + } + } + + // Advance to next color + colorIndex += 1; + } + + taskContext->hasHitEvents = hasHitEvents; +} + +#if defined( B3_SIMD_NEON ) + +#include + +// wide float holds 4 numbers +typedef float32x4_t b3FloatW; + +#elif defined( B3_SIMD_SSE2 ) + +#include + +// wide float holds 4 numbers +typedef __m128 b3FloatW; + +#else + +// scalar math +typedef struct b3FloatW +{ + float x, y, z, w; +} b3FloatW; + +#endif + +// Wide vec2 +typedef struct b3Vec2W +{ + b3FloatW x, y; +} b3Vec2W; + +// Wide vec3 +typedef struct b3Vec3W +{ + b3FloatW X, Y, Z; +} b3Vec3W; + +// Wide quaternion +typedef struct b3QuatW +{ + b3Vec3W V; + b3FloatW S; +} b3QuatW; + +// Wide symmetric matrix2 +typedef struct b3SymMatrix2W +{ + b3FloatW cxx, cxy, cyy; +} b3SymMatrix2W; + +// Wide symmetric matrix3 +typedef struct b3SymMatrix3W +{ + b3FloatW cxx, cxy, cxz, cyy, cyz, czz; +} b3SymMatrix3W; + +#if defined( B3_SIMD_NEON ) + +static inline b3FloatW b3ZeroW( void ) +{ + return vdupq_n_f32( 0.0f ); +} + +static inline b3FloatW b3SplatW( float scalar ) +{ + return vdupq_n_f32( scalar ); +} + +static inline b3FloatW b3NegW( b3FloatW a ) +{ + return vnegq_f32( a ); +} + +static inline b3FloatW b3SetW( float a, float b, float c, float d ) +{ + float32_t array[4] = { a, b, c, d }; + return vld1q_f32( array ); +} + +static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) +{ + return vaddq_f32( a, b ); +} + +static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) +{ + return vsubq_f32( a, b ); +} + +static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) +{ + return vmulq_f32( a, b ); +} + +static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) +{ + return vdivq_f32( a, b ); +} + +static inline b3FloatW b3SqrtW( b3FloatW a ) +{ + return vsqrtq_f32( a ); +} + +// Cannot use real FMA because it doesn't match the non-SIMD path +static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) +{ + return vaddq_f32( a, vmulq_f32( b, c ) ); +} + +// static inline b3FloatW b3MulSubW( b3FloatW a, b3FloatW b, b3FloatW c ) +//{ +// return vsubq_f32( a, vmulq_f32( b, c ) ); +// } + +static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) +{ + return vminq_f32( a, b ); +} + +static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) +{ + return vmaxq_f32( a, b ); +} + +// clamp a to [-b, b] +static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) +{ + b3FloatW nb = b3NegW( b ); + b3FloatW c = b3MaxW( nb, a ); + return b3MinW( c, b ); +} + +static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vorrq_u32( vreinterpretq_u32_f32( a ), vreinterpretq_u32_f32( b ) ) ); +} + +static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vcgtq_f32( a, b ) ); +} + +static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) +{ + return vreinterpretq_f32_u32( vceqq_f32( a, b ) ); +} + +static inline bool b3AllZeroW( b3FloatW a ) +{ + // Create a zero vector for comparison + b3FloatW zero = vdupq_n_f32( 0.0f ); + + // Compare the input vector with zero + uint32x4_t cmp_result = vceqq_f32( a, zero ); + +// Check if all comparison results are non-zero using vminvq +#ifdef __ARM_FEATURE_SVE + // ARM v8.2+ has horizontal minimum instruction + return vminvq_u32( cmp_result ) != 0; +#else + // For older ARM architectures, we need to manually check all lanes + return vgetq_lane_u32( cmp_result, 0 ) != 0 && vgetq_lane_u32( cmp_result, 1 ) != 0 && vgetq_lane_u32( cmp_result, 2 ) != 0 && + vgetq_lane_u32( cmp_result, 3 ) != 0; +#endif +} + +// component-wise returns mask ? b : a +static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) +{ + uint32x4_t mask32 = vreinterpretq_u32_f32( mask ); + return vbslq_f32( mask32, b, a ); +} + +#elif defined( B3_SIMD_SSE2 ) + +static inline b3FloatW b3ZeroW( void ) +{ + return _mm_setzero_ps(); +} + +static inline b3FloatW b3SplatW( float scalar ) +{ + return _mm_set1_ps( scalar ); +} + +static inline b3FloatW b3NegW( b3FloatW a ) +{ + // Create a mask with the sign bit set for each element + __m128 mask = _mm_set1_ps( -0.0f ); + + // XOR the input with the mask to negate each element + return _mm_xor_ps( a, mask ); +} + +static inline b3FloatW b3SetW( float a, float b, float c, float d ) +{ + return _mm_setr_ps( a, b, c, d ); +} + +static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) +{ + return _mm_add_ps( a, b ); +} + +static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) +{ + return _mm_sub_ps( a, b ); +} + +static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) +{ + return _mm_mul_ps( a, b ); +} + +static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) +{ + return _mm_div_ps( a, b ); +} + +static inline b3FloatW b3SqrtW( b3FloatW a ) +{ + return _mm_sqrt_ps( a ); +} + +static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) +{ + return _mm_add_ps( a, _mm_mul_ps( b, c ) ); +} + +// static inline b3FloatW b3MulSubW( b3FloatW a, b3FloatW b, b3FloatW c ) +//{ +// return _mm_sub_ps( a, _mm_mul_ps( b, c ) ); +// } + +static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) +{ + return _mm_min_ps( a, b ); +} + +static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) +{ + return _mm_max_ps( a, b ); +} + +// clamp a to [-b, b] +static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) +{ + b3FloatW nb = b3NegW( b ); + b3FloatW c = b3MaxW( nb, a ); + return b3MinW( c, b ); +} + +static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) +{ + return _mm_or_ps( a, b ); +} + +static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) +{ + return _mm_cmpgt_ps( a, b ); +} + +static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) +{ + return _mm_cmpeq_ps( a, b ); +} + +static inline bool b3AllZeroW( b3FloatW a ) +{ + // Compare each element with zero + b3FloatW zero = _mm_setzero_ps(); + b3FloatW cmp = _mm_cmpeq_ps( a, zero ); + + // Create a mask from the comparison results + int mask = _mm_movemask_ps( cmp ); + + // If all elements are zero, the mask will be 0xF (1111 in binary) + return mask == 0xF; +} + +// component-wise returns mask ? b : a +static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) +{ + return _mm_or_ps( _mm_and_ps( mask, b ), _mm_andnot_ps( mask, a ) ); +} + +#else + +static inline b3FloatW b3ZeroW( void ) +{ + return (b3FloatW){ 0.0f, 0.0f, 0.0f, 0.0f }; +} + +static inline b3FloatW b3SplatW( float scalar ) +{ + return (b3FloatW){ scalar, scalar, scalar, scalar }; +} + +static inline b3FloatW b3NegW( b3FloatW a ) +{ + return (b3FloatW){ -a.x, -a.y, -a.z, -a.w }; +} + +static inline b3FloatW b3AddW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }; +} + +static inline b3FloatW b3SubW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }; +} + +static inline b3FloatW b3MulW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w }; +} + +static inline b3FloatW b3DivW( b3FloatW a, b3FloatW b ) +{ + return (b3FloatW){ a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w }; +} + +static inline b3FloatW b3SqrtW( b3FloatW a ) +{ + return (b3FloatW){ sqrtf( a.x ), sqrtf( a.y ), sqrtf( a.z ), sqrtf( a.w ) }; +} + +static inline b3FloatW b3MulAddW( b3FloatW a, b3FloatW b, b3FloatW c ) +{ + return (b3FloatW){ a.x + b.x * c.x, a.y + b.y * c.y, a.z + b.z * c.z, a.w + b.w * c.w }; +} + +// static inline b3FloatW b3MulSubW( b3FloatW a, b3FloatW b, b3FloatW c ) +//{ +// return { a.x - b.x * c.x, a.y - b.y * c.y, a.z - b.z * c.z, a.w - b.w * c.w }; +// } + +// static inline b3FloatW b3MinW( b3FloatW a, b3FloatW b ) +//{ +// b3FloatW r; +// r.x = a.x <= b.x ? a.x : b.x; +// r.y = a.y <= b.y ? a.y : b.y; +// r.z = a.z <= b.z ? a.z : b.z; +// r.w = a.w <= b.w ? a.w : b.w; +// return r; +// } + +static inline b3FloatW b3MaxW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x >= b.x ? a.x : b.x; + r.y = a.y >= b.y ? a.y : b.y; + r.z = a.z >= b.z ? a.z : b.z; + r.w = a.w >= b.w ? a.w : b.w; + return r; +} + +// clamp a to [-b, b] +static inline b3FloatW b3SymClampW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x <= b.x ? a.x : b.x; + r.y = a.y <= b.y ? a.y : b.y; + r.z = a.z <= b.z ? a.z : b.z; + r.w = a.w <= b.w ? a.w : b.w; + r.x = r.x <= -b.x ? -b.x : r.x; + r.y = r.y <= -b.y ? -b.y : r.y; + r.z = r.z <= -b.z ? -b.z : r.z; + r.w = r.w <= -b.w ? -b.w : r.w; + return r; +} + +static inline b3FloatW b3OrW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x != 0.0f || b.x != 0.0f ? 1.0f : 0.0f; + r.y = a.y != 0.0f || b.y != 0.0f ? 1.0f : 0.0f; + r.z = a.z != 0.0f || b.z != 0.0f ? 1.0f : 0.0f; + r.w = a.w != 0.0f || b.w != 0.0f ? 1.0f : 0.0f; + return r; +} + +static inline b3FloatW b3GreaterThanW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x > b.x ? 1.0f : 0.0f; + r.y = a.y > b.y ? 1.0f : 0.0f; + r.z = a.z > b.z ? 1.0f : 0.0f; + r.w = a.w > b.w ? 1.0f : 0.0f; + return r; +} + +static inline b3FloatW b3EqualsW( b3FloatW a, b3FloatW b ) +{ + b3FloatW r; + r.x = a.x == b.x ? 1.0f : 0.0f; + r.y = a.y == b.y ? 1.0f : 0.0f; + r.z = a.z == b.z ? 1.0f : 0.0f; + r.w = a.w == b.w ? 1.0f : 0.0f; + return r; +} + +static inline bool b3AllZeroW( b3FloatW a ) +{ + return a.x == 0.0f && a.y == 0.0f && a.z == 0.0f && a.w == 0.0f; +} + +// component-wise returns mask ? b : a +static inline b3FloatW b3BlendW( b3FloatW a, b3FloatW b, b3FloatW mask ) +{ + b3FloatW r; + r.x = mask.x != 0.0f ? b.x : a.x; + r.y = mask.y != 0.0f ? b.y : a.y; + r.z = mask.z != 0.0f ? b.z : a.z; + r.w = mask.w != 0.0f ? b.w : a.w; + return r; +} + +#endif + +// s * a +static inline b3Vec3W b3MulSVW( b3FloatW s, b3Vec3W a ) +{ + return (b3Vec3W){ b3MulW( s, a.X ), b3MulW( s, a.Y ), b3MulW( s, a.Z ) }; +} + +// a - s * b +static inline b3Vec3W b3MulSubSVW( b3Vec3W a, b3FloatW s, b3Vec3W b ) +{ + return (b3Vec3W){ b3SubW( a.X, b3MulW( s, b.X ) ), b3SubW( a.Y, b3MulW( s, b.Y ) ), b3SubW( a.Z, b3MulW( s, b.Z ) ) }; +} + +// a + s * b +static inline b3Vec3W b3MulAddSVW( b3Vec3W a, b3FloatW s, b3Vec3W b ) +{ + return (b3Vec3W){ b3AddW( a.X, b3MulW( s, b.X ) ), b3AddW( a.Y, b3MulW( s, b.Y ) ), b3AddW( a.Z, b3MulW( s, b.Z ) ) }; +} + +// a + b +static inline b3Vec2W b3AddV2W( b3Vec2W a, b3Vec2W b ) +{ + return (b3Vec2W){ + b3AddW( a.x, b.x ), + b3AddW( a.y, b.y ), + }; +} + +// a - b +static inline b3Vec3W b3SubVW( b3Vec3W a, b3Vec3W b ) +{ + return (b3Vec3W){ + b3SubW( a.X, b.X ), + b3SubW( a.Y, b.Y ), + b3SubW( a.Z, b.Z ), + }; +} + +// a + b +static inline b3Vec3W b3AddVW( b3Vec3W a, b3Vec3W b ) +{ + return (b3Vec3W){ + b3AddW( a.X, b.X ), + b3AddW( a.Y, b.Y ), + b3AddW( a.Z, b.Z ), + }; +} + +// m * a +static inline b3Vec2W b3MulMV2W( b3SymMatrix2W m, b3Vec2W a ) +{ + b3Vec2W b = { + b3AddW( b3MulW( m.cxx, a.x ), b3MulW( m.cxy, a.y ) ), + b3AddW( b3MulW( m.cxy, a.x ), b3MulW( m.cyy, a.y ) ), + }; + + return b; +} + +// m * a +static inline b3Vec3W b3MulMVW( b3SymMatrix3W m, b3Vec3W a ) +{ + b3Vec3W b = { + b3AddW( b3MulW( m.cxx, a.X ), b3AddW( b3MulW( m.cxy, a.Y ), b3MulW( m.cxz, a.Z ) ) ), + b3AddW( b3MulW( m.cxy, a.X ), b3AddW( b3MulW( m.cyy, a.Y ), b3MulW( m.cyz, a.Z ) ) ), + b3AddW( b3MulW( m.cxz, a.X ), b3AddW( b3MulW( m.cyz, a.Y ), b3MulW( m.czz, a.Z ) ) ), + }; + + return b; +} + +// a - m * b +static inline b3Vec3W b3MulSubMVW( b3Vec3W a, b3SymMatrix3W m, b3Vec3W b ) +{ + b3Vec3W c = { + b3AddW( b3MulW( m.cxx, b.X ), b3AddW( b3MulW( m.cxy, b.Y ), b3MulW( m.cxz, b.Z ) ) ), + b3AddW( b3MulW( m.cxy, b.X ), b3AddW( b3MulW( m.cyy, b.Y ), b3MulW( m.cyz, b.Z ) ) ), + b3AddW( b3MulW( m.cxz, b.X ), b3AddW( b3MulW( m.cyz, b.Y ), b3MulW( m.czz, b.Z ) ) ), + }; + + return (b3Vec3W){ b3SubW( a.X, c.X ), b3SubW( a.Y, c.Y ), b3SubW( a.Z, c.Z ) }; +} + +// a + m * b +static inline b3Vec3W b3MulAddMVW( b3Vec3W a, b3SymMatrix3W m, b3Vec3W b ) +{ + b3Vec3W c = { + b3AddW( b3MulW( m.cxx, b.X ), b3AddW( b3MulW( m.cxy, b.Y ), b3MulW( m.cxz, b.Z ) ) ), + b3AddW( b3MulW( m.cxy, b.X ), b3AddW( b3MulW( m.cyy, b.Y ), b3MulW( m.cyz, b.Z ) ) ), + b3AddW( b3MulW( m.cxz, b.X ), b3AddW( b3MulW( m.cyz, b.Y ), b3MulW( m.czz, b.Z ) ) ), + }; + + return (b3Vec3W){ b3AddW( a.X, c.X ), b3AddW( a.Y, c.Y ), b3AddW( a.Z, c.Z ) }; +} + +static inline b3FloatW b3DotW( b3Vec3W a, b3Vec3W b ) +{ + return b3AddW( b3AddW( b3MulW( a.X, b.X ), b3MulW( a.Y, b.Y ) ), b3MulW( a.Z, b.Z ) ); +} + +static inline b3Vec3W b3CrossW( b3Vec3W a, b3Vec3W b ) +{ + b3Vec3W c; + c.X = b3SubW( b3MulW( a.Y, b.Z ), b3MulW( a.Z, b.Y ) ); + c.Y = b3SubW( b3MulW( a.Z, b.X ), b3MulW( a.X, b.Z ) ); + c.Z = b3SubW( b3MulW( a.X, b.Y ), b3MulW( a.Y, b.X ) ); + return c; +} + +static inline b3Vec3W b3RotateVectorW( b3QuatW q, b3Vec3W a ) +{ + b3Vec3W t1 = b3CrossW( q.V, a ); + b3Vec3W t2; + t2.X = b3MulAddW( t1.X, q.S, a.X ); + t2.Y = b3MulAddW( t1.Y, q.S, a.Y ); + t2.Z = b3MulAddW( t1.Z, q.S, a.Z ); + b3Vec3W t3 = b3CrossW( q.V, t2 ); + b3FloatW two = b3SplatW( 2.0f ); + b3Vec3W b; + b.X = b3MulAddW( a.X, two, t3.X ); + b.Y = b3MulAddW( a.Y, two, t3.Y ); + b.Z = b3MulAddW( a.Z, two, t3.Z ); + return b; +} + +// Soft contact constraints with sub-stepping support +// Uses fixed anchors for Jacobians for better behavior on rolling shapes (circles & capsules) +// http://mmacklin.com/smallsteps.pdf +// https://box2d.org/files/ErinCatto_SoftConstraints_GDC2011.pdf + +typedef struct b3ContactConstraintPointWide +{ + b3Vec3W anchorAs, anchorBs; + b3FloatW baseSeparations; + b3FloatW normalImpulses; + b3FloatW totalNormalImpulses; + b3FloatW normalMasses; + b3FloatW leverArms; + b3FloatW relativeVelocities; +} b3ContactConstraintPointWide; + +// Solves four points +typedef struct b3ContactConstraintWide +{ + // These are base 1 + int indexA[B3_SIMD_WIDTH]; + int indexB[B3_SIMD_WIDTH]; + + b3FloatW invMassA, invMassB; + b3SymMatrix3W invIA, invIB; + b3Vec3W normal; + + // todo test computing the tangents on the fly, at least tangent2 + b3Vec3W tangent1; + b3Vec3W tangent2; + + b3Vec3W originA, originB; + b3FloatW twistMass; + b3FloatW twistImpulse; + b3SymMatrix2W tangentMass; + b3Vec2W frictionImpulse; + b3SymMatrix3W rollingMass; + b3Vec3W rollingImpulse; + b3FloatW friction; + b3FloatW rollingResistance; + b3FloatW tangentVelocity1; + b3FloatW tangentVelocity2; + + b3FloatW biasRate; + b3FloatW massScale; + b3FloatW impulseScale; + b3FloatW restitution; + + b3Manifold* manifolds[B3_SIMD_WIDTH]; + + // todo store the maximum point count per wide constraint + // to make this work I need zero initialization which is too + // expensive for all the wide constraint data. Instead + // the graph color should store the point count as a compact secondary + // transient array with zero initialization. + b3ContactConstraintPointWide points[B3_MAX_MANIFOLD_POINTS]; + +} b3ContactConstraintWide; + +int b3GetWideContactConstraintByteCount( void ) +{ + return sizeof( b3ContactConstraintWide ); +} + +// wide version of b3BodyState +typedef struct b3BodyStateW +{ + b3Vec3W v; + b3Vec3W w; + b3Vec3W dp; + b3QuatW dq; +} b3BodyStateW; + +#if defined( B3_SIMD_SSE2 ) || defined( B3_SIMD_NEON ) + +static b3BodyStateW b3GatherBodies( const b3BodyState* states, int* indices ) +{ + b3BodyState dummy = { 0 }; + dummy.deltaRotation.s = 1.0f; + + // Indices are 0 for null + b3BodyState b1 = indices[0] == 0 ? dummy : states[indices[0] - 1]; + b3BodyState b2 = indices[1] == 0 ? dummy : states[indices[1] - 1]; + b3BodyState b3 = indices[2] == 0 ? dummy : states[indices[2] - 1]; + b3BodyState b4 = indices[3] == 0 ? dummy : states[indices[3] - 1]; + + b3BodyStateW s; + s.v.X = b3SetW( b1.linearVelocity.x, b2.linearVelocity.x, b3.linearVelocity.x, b4.linearVelocity.x ); + s.v.Y = b3SetW( b1.linearVelocity.y, b2.linearVelocity.y, b3.linearVelocity.y, b4.linearVelocity.y ); + s.v.Z = b3SetW( b1.linearVelocity.z, b2.linearVelocity.z, b3.linearVelocity.z, b4.linearVelocity.z ); + + s.w.X = b3SetW( b1.angularVelocity.x, b2.angularVelocity.x, b3.angularVelocity.x, b4.angularVelocity.x ); + s.w.Y = b3SetW( b1.angularVelocity.y, b2.angularVelocity.y, b3.angularVelocity.y, b4.angularVelocity.y ); + s.w.Z = b3SetW( b1.angularVelocity.z, b2.angularVelocity.z, b3.angularVelocity.z, b4.angularVelocity.z ); + + s.dp.X = b3SetW( b1.deltaPosition.x, b2.deltaPosition.x, b3.deltaPosition.x, b4.deltaPosition.x ); + s.dp.Y = b3SetW( b1.deltaPosition.y, b2.deltaPosition.y, b3.deltaPosition.y, b4.deltaPosition.y ); + s.dp.Z = b3SetW( b1.deltaPosition.z, b2.deltaPosition.z, b3.deltaPosition.z, b4.deltaPosition.z ); + + s.dq.V.X = b3SetW( b1.deltaRotation.v.x, b2.deltaRotation.v.x, b3.deltaRotation.v.x, b4.deltaRotation.v.x ); + s.dq.V.Y = b3SetW( b1.deltaRotation.v.y, b2.deltaRotation.v.y, b3.deltaRotation.v.y, b4.deltaRotation.v.y ); + s.dq.V.Z = b3SetW( b1.deltaRotation.v.z, b2.deltaRotation.v.z, b3.deltaRotation.v.z, b4.deltaRotation.v.z ); + s.dq.S = b3SetW( b1.deltaRotation.s, b2.deltaRotation.s, b3.deltaRotation.s, b4.deltaRotation.s ); + return s; +} + +// This writes only the velocities back to the solver bodies +static void b3ScatterBodies( b3BodyState* states, int* indices, const b3BodyStateW* simdBody ) +{ + const float* vx = (const float*)&simdBody->v.X; + const float* vy = (const float*)&simdBody->v.Y; + const float* vz = (const float*)&simdBody->v.Z; + const float* wx = (const float*)&simdBody->w.X; + const float* wy = (const float*)&simdBody->w.Y; + const float* wz = (const float*)&simdBody->w.Z; + + // I don't use any dummy body in the body array because this will lead to multithreaded sharing and the + // associated cache flushing. + + // Warning: indices start at 1 with 0 indicating null + + if ( indices[0] != 0 && ( states[indices[0] - 1].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* s = states + ( indices[0] - 1 ); + + b3Vec3 v = { vx[0], vy[0], vz[0] }; + b3Vec3 w = { wx[0], wy[0], wz[0] }; + + uint32_t flags = s->flags; + if ( flags & b3_allLocks ) + { + v.x = ( flags & b3_lockLinearX ) ? 0.0f : v.x; + v.y = ( flags & b3_lockLinearY ) ? 0.0f : v.y; + v.z = ( flags & b3_lockLinearZ ) ? 0.0f : v.z; + w.x = ( flags & b3_lockAngularX ) ? 0.0f : w.x; + w.y = ( flags & b3_lockAngularY ) ? 0.0f : w.y; + w.z = ( flags & b3_lockAngularZ ) ? 0.0f : w.z; + } + + s->linearVelocity = v; + s->angularVelocity = w; + } + + if ( indices[1] != 0 && ( states[indices[1] - 1].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* s = states + ( indices[1] - 1 ); + + b3Vec3 v = { vx[1], vy[1], vz[1] }; + b3Vec3 w = { wx[1], wy[1], wz[1] }; + + uint32_t flags = s->flags; + if ( flags & b3_allLocks ) + { + v.x = ( flags & b3_lockLinearX ) ? 0.0f : v.x; + v.y = ( flags & b3_lockLinearY ) ? 0.0f : v.y; + v.z = ( flags & b3_lockLinearZ ) ? 0.0f : v.z; + w.x = ( flags & b3_lockAngularX ) ? 0.0f : w.x; + w.y = ( flags & b3_lockAngularY ) ? 0.0f : w.y; + w.z = ( flags & b3_lockAngularZ ) ? 0.0f : w.z; + } + + s->linearVelocity = v; + s->angularVelocity = w; + } + + if ( indices[2] != 0 && ( states[indices[2] - 1].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* s = states + ( indices[2] - 1 ); + + b3Vec3 v = { vx[2], vy[2], vz[2] }; + b3Vec3 w = { wx[2], wy[2], wz[2] }; + + uint32_t flags = s->flags; + if ( flags & b3_allLocks ) + { + v.x = ( flags & b3_lockLinearX ) ? 0.0f : v.x; + v.y = ( flags & b3_lockLinearY ) ? 0.0f : v.y; + v.z = ( flags & b3_lockLinearZ ) ? 0.0f : v.z; + w.x = ( flags & b3_lockAngularX ) ? 0.0f : w.x; + w.y = ( flags & b3_lockAngularY ) ? 0.0f : w.y; + w.z = ( flags & b3_lockAngularZ ) ? 0.0f : w.z; + } + + s->linearVelocity = v; + s->angularVelocity = w; + } + + if ( indices[3] != 0 && ( states[indices[3] - 1].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* s = states + ( indices[3] - 1 ); + + b3Vec3 v = { vx[3], vy[3], vz[3] }; + b3Vec3 w = { wx[3], wy[3], wz[3] }; + + uint32_t flags = s->flags; + if ( flags & b3_allLocks ) + { + v.x = ( flags & b3_lockLinearX ) ? 0.0f : v.x; + v.y = ( flags & b3_lockLinearY ) ? 0.0f : v.y; + v.z = ( flags & b3_lockLinearZ ) ? 0.0f : v.z; + w.x = ( flags & b3_lockAngularX ) ? 0.0f : w.x; + w.y = ( flags & b3_lockAngularY ) ? 0.0f : w.y; + w.z = ( flags & b3_lockAngularZ ) ? 0.0f : w.z; + } + + s->linearVelocity = v; + s->angularVelocity = w; + } +} + +#else // non-simd + +static b3BodyStateW b3GatherBodies( const b3BodyState* states, int* indices ) +{ + b3BodyState identity = b3_identityBodyState; + + b3BodyState s1 = indices[0] == 0 ? identity : states[indices[0] - 1]; + b3BodyState s2 = indices[1] == 0 ? identity : states[indices[1] - 1]; + b3BodyState s3 = indices[2] == 0 ? identity : states[indices[2] - 1]; + b3BodyState s4 = indices[3] == 0 ? identity : states[indices[3] - 1]; + + b3BodyStateW simdBody; + simdBody.v.X = (b3FloatW){ s1.linearVelocity.x, s2.linearVelocity.x, s3.linearVelocity.x, s4.linearVelocity.x }; + simdBody.v.Y = (b3FloatW){ s1.linearVelocity.y, s2.linearVelocity.y, s3.linearVelocity.y, s4.linearVelocity.y }; + simdBody.v.Z = (b3FloatW){ s1.linearVelocity.z, s2.linearVelocity.z, s3.linearVelocity.z, s4.linearVelocity.z }; + simdBody.w.X = (b3FloatW){ s1.angularVelocity.x, s2.angularVelocity.x, s3.angularVelocity.x, s4.angularVelocity.x }; + simdBody.w.Y = (b3FloatW){ s1.angularVelocity.y, s2.angularVelocity.y, s3.angularVelocity.y, s4.angularVelocity.y }; + simdBody.w.Z = (b3FloatW){ s1.angularVelocity.z, s2.angularVelocity.z, s3.angularVelocity.z, s4.angularVelocity.z }; + simdBody.dp.X = (b3FloatW){ s1.deltaPosition.x, s2.deltaPosition.x, s3.deltaPosition.x, s4.deltaPosition.x }; + simdBody.dp.Y = (b3FloatW){ s1.deltaPosition.y, s2.deltaPosition.y, s3.deltaPosition.y, s4.deltaPosition.y }; + simdBody.dp.Z = (b3FloatW){ s1.deltaPosition.z, s2.deltaPosition.z, s3.deltaPosition.z, s4.deltaPosition.z }; + simdBody.dq.V.X = (b3FloatW){ s1.deltaRotation.v.x, s2.deltaRotation.v.x, s3.deltaRotation.v.x, s4.deltaRotation.v.x }; + simdBody.dq.V.Y = (b3FloatW){ s1.deltaRotation.v.y, s2.deltaRotation.v.y, s3.deltaRotation.v.y, s4.deltaRotation.v.y }; + simdBody.dq.V.Z = (b3FloatW){ s1.deltaRotation.v.z, s2.deltaRotation.v.z, s3.deltaRotation.v.z, s4.deltaRotation.v.z }; + simdBody.dq.S = (b3FloatW){ s1.deltaRotation.s, s2.deltaRotation.s, s3.deltaRotation.s, s4.deltaRotation.s }; + + return simdBody; +} + +// This writes only the velocities back to the solver bodies +static void b3ScatterBodies( b3BodyState* states, int* indices, const b3BodyStateW* simdBody ) +{ + int index1 = indices[0] - 1; + if ( index1 != -1 && ( states[index1].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* state = states + index1; + state->linearVelocity.x = simdBody->v.X.x; + state->linearVelocity.y = simdBody->v.Y.x; + state->linearVelocity.z = simdBody->v.Z.x; + state->angularVelocity.x = simdBody->w.X.x; + state->angularVelocity.y = simdBody->w.Y.x; + state->angularVelocity.z = simdBody->w.Z.x; + } + + int index2 = indices[1] - 1; + if ( index2 != -1 && ( states[index2].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* state = states + index2; + state->linearVelocity.x = simdBody->v.X.y; + state->linearVelocity.y = simdBody->v.Y.y; + state->linearVelocity.z = simdBody->v.Z.y; + state->angularVelocity.x = simdBody->w.X.y; + state->angularVelocity.y = simdBody->w.Y.y; + state->angularVelocity.z = simdBody->w.Z.y; + } + + int index3 = indices[2] - 1; + if ( index3 != -1 && ( states[index3].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* state = states + index3; + state->linearVelocity.x = simdBody->v.X.z; + state->linearVelocity.y = simdBody->v.Y.z; + state->linearVelocity.z = simdBody->v.Z.z; + state->angularVelocity.x = simdBody->w.X.z; + state->angularVelocity.y = simdBody->w.Y.z; + state->angularVelocity.z = simdBody->w.Z.z; + } + + int index4 = indices[3] - 1; + if ( index4 != -1 && ( states[index4].flags & b3_dynamicFlag ) != 0 ) + { + b3BodyState* state = states + index4; + state->linearVelocity.x = simdBody->v.X.w; + state->linearVelocity.y = simdBody->v.Y.w; + state->linearVelocity.z = simdBody->v.Z.w; + state->angularVelocity.x = simdBody->w.X.w; + state->angularVelocity.y = simdBody->w.Y.w; + state->angularVelocity.z = simdBody->w.Z.w; + } +} +#endif + +// Prepare convex contact constraints +void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( prepare_contact, "Prepare Contact", b3_colorYellow, true ); + b3World* world = context->world; + b3BodySim* sims = context->sims; + b3BodyState* states = context->states; +#if B3_ENABLE_VALIDATION + b3Body* bodies = world->bodies.data; +#endif + b3WidePrepareSpan* spans = context->widePrepareSpans; + b3ContactConstraintWide* wideBase = context->wideConstraints; + + // Stiffer for static contacts to avoid bodies getting pushed through the ground + b3Softness contactSoftness = context->contactSoftness; + b3Softness staticSoftness = context->staticSoftness; + + float warmStartScale = world->enableWarmStarting ? 1.0f : 0.0f; + + int wideIndex = block.startIndex; + int endWideIndex = block.startIndex + block.count; + + // Find color for start index. Linear search but fast. + int colorIndex = 0; + while ( spans[colorIndex + 1].start <= wideIndex ) + { + colorIndex += 1; + } + + // Loop over block + while ( wideIndex < endWideIndex ) + { + int colorWideStart = spans[colorIndex].start; + int colorWideEndIndex = b3MinInt( spans[colorIndex + 1].start, endWideIndex ); + int colorContactCount = spans[colorIndex].count; + int* contactIds = spans[colorIndex].contacts; + + // Loop over color + for ( ; wideIndex < colorWideEndIndex; ++wideIndex ) + { + b3ContactConstraintWide* constraint = wideBase + wideIndex; + int localWideIndex = wideIndex - colorWideStart; + + for ( int lane = 0; lane < B3_SIMD_WIDTH; ++lane ) + { + int contactIndex = B3_SIMD_WIDTH * localWideIndex + lane; + if ( contactIndex >= colorContactCount ) + { + // Remainder lanes were zeroed in solver setup. + break; + } + + int contactId = contactIds[contactIndex]; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + B3_ASSERT( contact->manifoldCount == 1 ); + b3Manifold* manifold = contact->manifolds + 0; + + int indexA = contact->bodySimIndexA; + int indexB = contact->bodySimIndexB; + +#if B3_ENABLE_VALIDATION + b3Body* bodyA = bodies + contact->edges[0].bodyId; + int validIndexA = bodyA->setIndex == b3_awakeSet ? bodyA->localIndex : B3_NULL_INDEX; + b3Body* bodyB = bodies + contact->edges[1].bodyId; + int validIndexB = bodyB->setIndex == b3_awakeSet ? bodyB->localIndex : B3_NULL_INDEX; + B3_ASSERT( indexA == validIndexA ); + B3_ASSERT( indexB == validIndexB ); +#endif + + // 0 for null + constraint->indexA[lane] = indexA + 1; + constraint->indexB[lane] = indexB + 1; + constraint->manifolds[lane] = manifold; + + // Body A data + float mA; + b3Matrix3 iA; + b3Vec3 vA; + b3Vec3 wA; + + if ( indexA == B3_NULL_INDEX ) + { + mA = 0.0f; + iA = b3Mat3_zero; + vA = b3Vec3_zero; + wA = b3Vec3_zero; + } + else + { + b3BodySim* simA = sims + indexA; + mA = simA->invMass; + iA = simA->invInertiaWorld; + + b3BodyState* stateA = states + indexA; + vA = stateA->linearVelocity; + wA = stateA->angularVelocity; + } + + // Body B data + float mB; + b3Matrix3 iB; + b3Vec3 vB; + b3Vec3 wB; + + if ( indexB == B3_NULL_INDEX ) + { + mB = 0.0f; + iB = b3Mat3_zero; + vB = b3Vec3_zero; + wB = b3Vec3_zero; + } + else + { + b3BodySim* simB = sims + indexB; + mB = simB->invMass; + iB = simB->invInertiaWorld; + + b3BodyState* stateB = states + indexB; + vB = stateB->linearVelocity; + wB = stateB->angularVelocity; + } + + ( (float*)&constraint->invMassA )[lane] = mA; + ( (float*)&constraint->invMassB )[lane] = mB; + + ( (float*)&constraint->invIA.cxx )[lane] = iA.cx.x; + ( (float*)&constraint->invIA.cxy )[lane] = iA.cx.y; + ( (float*)&constraint->invIA.cxz )[lane] = iA.cx.z; + ( (float*)&constraint->invIA.cyy )[lane] = iA.cy.y; + ( (float*)&constraint->invIA.cyz )[lane] = iA.cy.z; + ( (float*)&constraint->invIA.czz )[lane] = iA.cz.z; + + ( (float*)&constraint->invIB.cxx )[lane] = iB.cx.x; + ( (float*)&constraint->invIB.cxy )[lane] = iB.cx.y; + ( (float*)&constraint->invIB.cxz )[lane] = iB.cx.z; + ( (float*)&constraint->invIB.cyy )[lane] = iB.cy.y; + ( (float*)&constraint->invIB.cyz )[lane] = iB.cy.z; + ( (float*)&constraint->invIB.czz )[lane] = iB.cz.z; + + b3Softness soft = ( indexA == B3_NULL_INDEX || indexB == B3_NULL_INDEX ) ? staticSoftness : contactSoftness; + + b3Vec3 normal = manifold->normal; + ( (float*)&constraint->normal.X )[lane] = normal.x; + ( (float*)&constraint->normal.Y )[lane] = normal.y; + ( (float*)&constraint->normal.Z )[lane] = normal.z; + + b3Vec3 tangent1 = b3Perp( normal ); + ( (float*)&constraint->tangent1.X )[lane] = tangent1.x; + ( (float*)&constraint->tangent1.Y )[lane] = tangent1.y; + ( (float*)&constraint->tangent1.Z )[lane] = tangent1.z; + + b3Vec3 tangent2 = b3Cross( tangent1, normal ); + ( (float*)&constraint->tangent2.X )[lane] = tangent2.x; + ( (float*)&constraint->tangent2.Y )[lane] = tangent2.y; + ( (float*)&constraint->tangent2.Z )[lane] = tangent2.z; + + ( (float*)&constraint->friction )[lane] = contact->friction; + ( (float*)&constraint->restitution )[lane] = contact->restitution; + ( (float*)&constraint->rollingResistance )[lane] = contact->rollingResistance; + + ( (float*)&constraint->tangentVelocity1 )[lane] = b3Dot( contact->tangentVelocity, tangent1 ); + ( (float*)&constraint->tangentVelocity2 )[lane] = b3Dot( contact->tangentVelocity, tangent2 ); + + ( (float*)&constraint->biasRate )[lane] = soft.biasRate; + ( (float*)&constraint->massScale )[lane] = soft.massScale; + ( (float*)&constraint->impulseScale )[lane] = soft.impulseScale; + + int pointCount = manifold->pointCount; + b3Vec3 originA = b3Vec3_zero; + b3Vec3 originB = b3Vec3_zero; + + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + const b3ManifoldPoint* mp = manifold->points + pointIndex; + b3ContactConstraintPointWide* cp = constraint->points + pointIndex; + + b3Vec3 rA = mp->anchorA; + b3Vec3 rB = mp->anchorB; + originA = b3Add( originA, rA ); + originB = b3Add( originB, rB ); + + ( (float*)&cp->anchorAs.X )[lane] = rA.x; + ( (float*)&cp->anchorAs.Y )[lane] = rA.y; + ( (float*)&cp->anchorAs.Z )[lane] = rA.z; + + ( (float*)&cp->anchorBs.X )[lane] = rB.x; + ( (float*)&cp->anchorBs.Y )[lane] = rB.y; + ( (float*)&cp->anchorBs.Z )[lane] = rB.z; + + float baseSeparation = mp->separation - b3Dot( b3Sub( rB, rA ), normal ); + ( (float*)&cp->baseSeparations )[lane] = baseSeparation; + + ( (float*)&cp->normalImpulses )[lane] = warmStartScale * mp->normalImpulse; + ( (float*)&cp->totalNormalImpulses )[lane] = 0.0f; + + b3Vec3 rnA = b3Cross( rA, normal ); + b3Vec3 rnB = b3Cross( rB, normal ); + float kNormal = mA + mB + b3Dot( rnA, b3MulMV( iA, rnA ) ) + b3Dot( rnB, b3MulMV( iB, rnB ) ); + ( (float*)&cp->normalMasses )[lane] = kNormal > 0.0f ? 1.0f / kNormal : 0.0f; + + // Save relative velocity for restitution + b3Vec3 vrA = b3Add( vA, b3Cross( wA, rA ) ); + b3Vec3 vrB = b3Add( vB, b3Cross( wB, rB ) ); + ( (float*)&cp->relativeVelocities )[lane] = b3Dot( normal, b3Sub( vrB, vrA ) ); + } + + float invCount = 1.0f / pointCount; + originA = b3MulSV( invCount, originA ); + originB = b3MulSV( invCount, originB ); + + ( (float*)&constraint->originA.X )[lane] = originA.x; + ( (float*)&constraint->originA.Y )[lane] = originA.y; + ( (float*)&constraint->originA.Z )[lane] = originA.z; + ( (float*)&constraint->originB.X )[lane] = originB.x; + ( (float*)&constraint->originB.Y )[lane] = originB.y; + ( (float*)&constraint->originB.Z )[lane] = originB.z; + + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + const b3ManifoldPoint* mp = manifold->points + pointIndex; + b3ContactConstraintPointWide* cp = constraint->points + pointIndex; + ( (float*)&cp->leverArms )[lane] = b3Distance( mp->anchorA, originA ); + } + + b3Vec3 rtA1 = b3Cross( originA, tangent1 ); + b3Vec3 rtA2 = b3Cross( originA, tangent2 ); + + b3Vec3 rtB1 = b3Cross( originB, tangent1 ); + b3Vec3 rtB2 = b3Cross( originB, tangent2 ); + + { + b3Matrix2 k; + k.cx.x = mA + mB + b3Dot( rtA1, b3MulMV( iA, rtA1 ) ) + b3Dot( rtB1, b3MulMV( iB, rtB1 ) ); + k.cy.y = mA + mB + b3Dot( rtA2, b3MulMV( iA, rtA2 ) ) + b3Dot( rtB2, b3MulMV( iB, rtB2 ) ); + k.cx.y = k.cy.x = b3Dot( rtA1, b3MulMV( iA, rtA2 ) ) + b3Dot( rtB1, b3MulMV( iB, rtB2 ) ); + b3Matrix2 tangentMass = b3Invert2( k ); + + ( (float*)&constraint->tangentMass.cxx )[lane] = tangentMass.cx.x; + ( (float*)&constraint->tangentMass.cxy )[lane] = tangentMass.cx.y; + ( (float*)&constraint->tangentMass.cyy )[lane] = tangentMass.cy.y; + + ( (float*)&constraint->frictionImpulse.x )[lane] = + warmStartScale * b3Dot( manifold->frictionImpulse, tangent1 ); + ( (float*)&constraint->frictionImpulse.y )[lane] = + warmStartScale * b3Dot( manifold->frictionImpulse, tangent2 ); + } + + { + float k = b3Dot( normal, b3MulMV( b3AddMM( iA, iB ), normal ) ); + ( (float*)&constraint->twistMass )[lane] = k > 0.0f ? 1.0f / k : 0.0f; + ( (float*)&constraint->twistImpulse )[lane] = warmStartScale * manifold->twistImpulse; + } + + { + b3Matrix3 rollingMass = b3InvertMatrix( b3AddMM( iA, iB ) ); + + ( (float*)&constraint->rollingMass.cxx )[lane] = rollingMass.cx.x; + ( (float*)&constraint->rollingMass.cxy )[lane] = rollingMass.cx.y; + ( (float*)&constraint->rollingMass.cxz )[lane] = rollingMass.cx.z; + ( (float*)&constraint->rollingMass.cyy )[lane] = rollingMass.cy.y; + ( (float*)&constraint->rollingMass.cyz )[lane] = rollingMass.cy.z; + ( (float*)&constraint->rollingMass.czz )[lane] = rollingMass.cz.z; + + ( (float*)&constraint->rollingImpulse.X )[lane] = warmStartScale * manifold->rollingImpulse.x; + ( (float*)&constraint->rollingImpulse.Y )[lane] = warmStartScale * manifold->rollingImpulse.y; + ( (float*)&constraint->rollingImpulse.Z )[lane] = warmStartScale * manifold->rollingImpulse.z; + } + + // zero remaining points + for ( int pointIndex = pointCount; pointIndex < B3_MAX_MANIFOLD_POINTS; ++pointIndex ) + { + b3ContactConstraintPointWide* cp = constraint->points + pointIndex; + ( (float*)&cp->anchorAs.X )[lane] = 0.0f; + ( (float*)&cp->anchorAs.Y )[lane] = 0.0f; + ( (float*)&cp->anchorAs.Z )[lane] = 0.0f; + ( (float*)&cp->anchorBs.X )[lane] = 0.0f; + ( (float*)&cp->anchorBs.Y )[lane] = 0.0f; + ( (float*)&cp->anchorBs.Z )[lane] = 0.0f; + ( (float*)&cp->baseSeparations )[lane] = 0.0f; + ( (float*)&cp->normalImpulses )[lane] = 0.0f; + ( (float*)&cp->totalNormalImpulses )[lane] = 0.0f; + ( (float*)&cp->normalMasses )[lane] = 0.0f; + ( (float*)&cp->relativeVelocities )[lane] = 0.0f; + ( (float*)&cp->leverArms )[lane] = 0.0f; + } + } + } + + // Advance to next color + colorIndex += 1; + } + + b3TracyCZoneEnd( prepare_contact ); +} + +void b3WarmStartContacts_Convex( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( warm_start_contact, "Warm Start", b3_colorGreen, true ); + + b3BodyState* states = context->states; + b3ContactConstraintWide* constraints = context->graph->colors[block.colorIndex].wideConstraints; + + for ( int i = block.startIndex; i < block.startIndex + block.count; ++i ) + { + b3ContactConstraintWide* c = constraints + i; + b3BodyStateW bA = b3GatherBodies( states, c->indexA ); + b3BodyStateW bB = b3GatherBodies( states, c->indexB ); + + // Normal impulses + for ( int j = 0; j < B3_MAX_MANIFOLD_POINTS; ++j ) + { + b3ContactConstraintPointWide* cp = c->points + j; + + b3Vec3W rA = cp->anchorAs; + b3Vec3W rB = cp->anchorBs; + + b3Vec3W impulse; + impulse.X = b3MulW( cp->normalImpulses, c->normal.X ); + impulse.Y = b3MulW( cp->normalImpulses, c->normal.Y ); + impulse.Z = b3MulW( cp->normalImpulses, c->normal.Z ); + + bA.w = b3MulSubMVW( bA.w, c->invIA, b3CrossW( rA, impulse ) ); + bA.v = b3MulSubSVW( bA.v, c->invMassA, impulse ); + bB.w = b3MulAddMVW( bB.w, c->invIB, b3CrossW( rB, impulse ) ); + bB.v = b3MulAddSVW( bB.v, c->invMassB, impulse ); + } + + // Central friction + { + b3Vec3W rA = c->originA; + b3Vec3W rB = c->originB; + b3Vec3W impulse = b3MulSVW( c->frictionImpulse.x, c->tangent1 ); + impulse = b3MulAddSVW( impulse, c->frictionImpulse.y, c->tangent2 ); + + bA.w = b3MulSubMVW( bA.w, c->invIA, b3CrossW( rA, impulse ) ); + bA.v = b3MulSubSVW( bA.v, c->invMassA, impulse ); + bB.w = b3MulAddMVW( bB.w, c->invIB, b3CrossW( rB, impulse ) ); + bB.v = b3MulAddSVW( bB.v, c->invMassB, impulse ); + } + + // Central twist friction + { + b3Vec3W impulse = b3MulSVW( c->twistImpulse, c->normal ); + bA.w = b3MulSubMVW( bA.w, c->invIA, impulse ); + bB.w = b3MulAddMVW( bB.w, c->invIB, impulse ); + } + + // Rolling resistance + { + b3Vec3W impulse = c->rollingImpulse; + bA.w = b3MulSubMVW( bA.w, c->invIA, impulse ); + bB.w = b3MulAddMVW( bB.w, c->invIB, impulse ); + } + + b3ScatterBodies( states, c->indexA, &bA ); + b3ScatterBodies( states, c->indexB, &bB ); + } + + b3TracyCZoneEnd( warm_start_contact ); +} + +void b3SolveContacts_Convex( b3SolverBlock block, b3StepContext* context, bool useBias ) +{ + b3TracyCZoneNC( solve_contact, "Solve Contact", b3_colorAliceBlue, true ); + + b3BodyState* states = context->states; + b3ContactConstraintWide* constraints = context->graph->colors[block.colorIndex].wideConstraints; + b3FloatW inv_h = b3SplatW( context->inv_h ); + b3FloatW contactSpeed = b3SplatW( -context->world->contactSpeed ); + b3FloatW oneW = b3SplatW( 1.0f ); + b3FloatW epsilonW = b3SplatW( FLT_EPSILON ); + + for ( int wideIndex = block.startIndex; wideIndex < block.startIndex + block.count; ++wideIndex ) + { + b3ContactConstraintWide* c = constraints + wideIndex; + + b3BodyStateW bA = b3GatherBodies( states, c->indexA ); + b3BodyStateW bB = b3GatherBodies( states, c->indexB ); + + b3FloatW biasRate, massScale, impulseScale; + if ( useBias ) + { + biasRate = b3MulW( c->massScale, c->biasRate ); + massScale = c->massScale; + impulseScale = c->impulseScale; + } + else + { + biasRate = b3ZeroW(); + massScale = oneW; + impulseScale = b3ZeroW(); + } + + b3Vec3W dp = b3SubVW( bB.dp, bA.dp ); + + b3FloatW totalNormalImpulse = b3ZeroW(); + b3FloatW totalTwistLimit = b3ZeroW(); + + // todo_erin use the max point count of the four manifolds + for ( int pointIndex = 0; pointIndex < B3_MAX_MANIFOLD_POINTS; ++pointIndex ) + { + b3ContactConstraintPointWide* cp = c->points + pointIndex; + + // Fixed anchor points for applying impulses + b3Vec3W rA = cp->anchorAs; + b3Vec3W rB = cp->anchorBs; + + // Moving anchors for current separation + // todo speed this up using matrices + b3Vec3W rsA = b3RotateVectorW( bA.dq, rA ); + b3Vec3W rsB = b3RotateVectorW( bB.dq, rB ); + + // compute current separation + // this is subject to round-off error if the anchor is far from the body center of mass + b3Vec3W ds = b3AddVW( dp, b3SubVW( rsB, rsA ) ); + b3FloatW s = b3AddW( b3DotW( c->normal, ds ), cp->baseSeparations ); + + // Apply speculative bias if separation is greater than zero, otherwise apply soft constraint bias + b3FloatW mask = b3GreaterThanW( s, b3ZeroW() ); + b3FloatW specBias = b3MulW( s, inv_h ); + b3FloatW softBias = b3MaxW( b3MulW( biasRate, s ), contactSpeed ); + b3FloatW bias = b3BlendW( softBias, specBias, mask ); + + b3FloatW pointMassScale = b3BlendW( massScale, oneW, mask ); + b3FloatW pointImpulseScale = b3BlendW( impulseScale, b3ZeroW(), mask ); + + // Relative velocity at contact + b3Vec3W vrA = b3AddVW( bA.v, b3CrossW( bA.w, rA ) ); + b3Vec3W vrB = b3AddVW( bB.v, b3CrossW( bB.w, rB ) ); + b3FloatW vn = b3DotW( b3SubVW( vrB, vrA ), c->normal ); + + // Compute normal impulse + b3FloatW negImpulse = b3AddW( b3MulW( cp->normalMasses, b3AddW( b3MulW( pointMassScale, vn ), bias ) ), + b3MulW( pointImpulseScale, cp->normalImpulses ) ); + + // Clamp the accumulated impulse + b3FloatW newImpulse = b3MaxW( b3SubW( cp->normalImpulses, negImpulse ), b3ZeroW() ); + b3FloatW deltaImpulse = b3SubW( newImpulse, cp->normalImpulses ); + cp->normalImpulses = newImpulse; + cp->totalNormalImpulses = b3AddW( cp->totalNormalImpulses, newImpulse ); + + totalNormalImpulse = b3AddW( totalNormalImpulse, newImpulse ); + totalTwistLimit = b3AddW( totalTwistLimit, b3MulW( cp->leverArms, newImpulse ) ); + + // Apply contact impulse + b3Vec3W P = b3MulSVW( deltaImpulse, c->normal ); + bA.w = b3MulSubMVW( bA.w, c->invIA, b3CrossW( rA, P ) ); + bA.v = b3MulSubSVW( bA.v, c->invMassA, P ); + bB.w = b3MulAddMVW( bB.w, c->invIB, b3CrossW( rB, P ) ); + bB.v = b3MulAddSVW( bB.v, c->invMassB, P ); + } + + // No friction when applying bias + if ( useBias == false ) + { + // Rolling resistance + if ( b3AllZeroW( c->rollingResistance ) == false ) + { + // flip A/B order to negate + b3Vec3W deltaImpulse = b3MulMVW( c->rollingMass, b3SubVW( bA.w, bB.w ) ); + b3Vec3W oldImpulse = c->rollingImpulse; + c->rollingImpulse = b3AddVW( oldImpulse, deltaImpulse ); + + b3FloatW maxImpulse = b3MulW( c->rollingResistance, totalNormalImpulse ); + b3FloatW lengthSquared = b3DotW( c->rollingImpulse, c->rollingImpulse ); + + // if ( magSqr > maxLambda * maxLambda + FLT_EPSILON ) + //{ + // c->rollingImpulse *= maxLambda / sqrtf( magSqr ); + // } + + b3FloatW mask = b3GreaterThanW( lengthSquared, b3MulAddW( epsilonW, maxImpulse, maxImpulse ) ); + + // No approximate _mm_rsqrt_ps here to maintain cross-platform determinism + b3FloatW normalize = b3DivW( maxImpulse, b3AddW( b3SqrtW( lengthSquared ), epsilonW ) ); + b3FloatW scale = b3BlendW( oneW, normalize, mask ); + + // Ensure zero rolling resistance yields no impulse + b3FloatW rollingMask = b3GreaterThanW( c->rollingResistance, b3ZeroW() ); + scale = b3BlendW( b3ZeroW(), scale, rollingMask ); + + c->rollingImpulse = b3MulSVW( scale, c->rollingImpulse ); + + deltaImpulse = b3SubVW( c->rollingImpulse, oldImpulse ); + + bA.w = b3MulSubMVW( bA.w, c->invIA, deltaImpulse ); + bB.w = b3MulAddMVW( bB.w, c->invIB, deltaImpulse ); + } + + // Central twist friction + { + b3FloatW twistSpeed = b3DotW( c->normal, b3SubVW( bB.w, bA.w ) ); + b3FloatW maxLambda = b3MulW( c->friction, totalTwistLimit ); + b3FloatW deltaImpulse = b3NegW( b3MulW( c->twistMass, twistSpeed ) ); + b3FloatW oldImpulse = c->twistImpulse; + c->twistImpulse = b3SymClampW( b3AddW( oldImpulse, deltaImpulse ), maxLambda ); + deltaImpulse = b3SubW( c->twistImpulse, oldImpulse ); + + b3Vec3W L = b3MulSVW( deltaImpulse, c->normal ); + bA.w = b3MulSubMVW( bA.w, c->invIA, L ); + bB.w = b3MulAddMVW( bB.w, c->invIB, L ); + } + + // Central friction + { + b3Vec3W tangent1 = c->tangent1; + b3Vec3W tangent2 = c->tangent2; + + // Fixed anchor points for applying impulses + b3Vec3W rA = c->originA; + b3Vec3W rB = c->originB; + + // Relative tangent velocity at contact + b3Vec3W vrA = b3AddVW( bA.v, b3CrossW( bA.w, rA ) ); + b3Vec3W vrB = b3AddVW( bB.v, b3CrossW( bB.w, rB ) ); + b3Vec3W vr = b3SubVW( vrB, vrA ); + b3Vec2W vt = { + b3SubW( b3DotW( vr, tangent1 ), c->tangentVelocity1 ), + b3SubW( b3DotW( vr, tangent2 ), c->tangentVelocity2 ), + }; + + // Incremental tangent impulse + b3Vec2W deltaImpulse = b3MulMV2W( c->tangentMass, vt ); + deltaImpulse = (b3Vec2W){ b3NegW( deltaImpulse.x ), b3NegW( deltaImpulse.y ) }; + b3Vec2W newImpulse = b3AddV2W( c->frictionImpulse, deltaImpulse ); + + b3FloatW friction = c->friction; + b3FloatW maxImpulse = b3MulW( friction, totalNormalImpulse ); + + // Clamp the accumulated impulse + b3FloatW lengthSquared = b3AddW( b3MulW( newImpulse.x, newImpulse.x ), b3MulW( newImpulse.y, newImpulse.y ) ); + + // Max impulse can be zero + b3FloatW mask = b3GreaterThanW( lengthSquared, b3MulW( maxImpulse, maxImpulse ) ); + + // No approximate _mm_rsqrt_ps here to maintain cross-platform determinism. Add epsilon to avoid divide by + // zero. + b3FloatW normalize = b3DivW( maxImpulse, b3AddW( b3SqrtW( lengthSquared ), epsilonW ) ); + b3FloatW scale = b3BlendW( oneW, normalize, mask ); + newImpulse = (b3Vec2W){ + b3MulW( scale, newImpulse.x ), + b3MulW( scale, newImpulse.y ), + }; + + deltaImpulse = (b3Vec2W){ + b3SubW( newImpulse.x, c->frictionImpulse.x ), + b3SubW( newImpulse.y, c->frictionImpulse.y ), + }; + + c->frictionImpulse = newImpulse; + + // Apply delta impulse + b3Vec3W P = b3AddVW( b3MulSVW( deltaImpulse.x, tangent1 ), b3MulSVW( deltaImpulse.y, tangent2 ) ); + bA.w = b3MulSubMVW( bA.w, c->invIA, b3CrossW( rA, P ) ); + bA.v = b3MulSubSVW( bA.v, c->invMassA, P ); + bB.w = b3MulAddMVW( bB.w, c->invIB, b3CrossW( rB, P ) ); + bB.v = b3MulAddSVW( bB.v, c->invMassB, P ); + } + } + + b3ScatterBodies( states, c->indexA, &bA ); + b3ScatterBodies( states, c->indexB, &bB ); + } + + b3TracyCZoneEnd( solve_contact ); +} + +void b3ApplyRestitution_Convex( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( restitution, "Restitution", b3_colorDodgerBlue, true ); + + b3BodyState* states = context->states; + b3ContactConstraintWide* constraints = context->graph->colors[block.colorIndex].wideConstraints; + b3FloatW threshold = b3SplatW( context->world->restitutionThreshold ); + b3FloatW zero = b3ZeroW(); + + for ( int i = block.startIndex; i < block.startIndex + block.count; ++i ) + { + b3ContactConstraintWide* c = constraints + i; + + if ( b3AllZeroW( c->restitution ) ) + { + // No lanes have restitution. Common case. + continue; + } + + // Single gather for all manifolds + b3BodyStateW bA = b3GatherBodies( states, c->indexA ); + b3BodyStateW bB = b3GatherBodies( states, c->indexB ); + + // Create a mask based on restitution so that lanes with no restitution are not affected + // by the calculations below. + b3FloatW restitutionMask = b3EqualsW( c->restitution, zero ); + + for ( int pointIndex = 0; pointIndex < B3_MAX_MANIFOLD_POINTS; ++pointIndex ) + { + b3ContactConstraintPointWide* cp = c->points + pointIndex; + + // Set effective mass to zero if restitution should not be applied + b3FloatW mask1 = b3GreaterThanW( b3AddW( cp->relativeVelocities, threshold ), zero ); + b3FloatW mask2 = b3EqualsW( cp->totalNormalImpulses, zero ); + b3FloatW mask = b3OrW( b3OrW( mask1, mask2 ), restitutionMask ); + b3FloatW mass = b3BlendW( cp->normalMasses, zero, mask ); + + // Fixed anchors for impulses + b3Vec3W rA = cp->anchorAs; + b3Vec3W rB = cp->anchorBs; + + // Relative velocity at contact + b3Vec3W vrA = b3AddVW( bA.v, b3CrossW( bA.w, rA ) ); + b3Vec3W vrB = b3AddVW( bB.v, b3CrossW( bB.w, rB ) ); + b3FloatW vn = b3DotW( b3SubVW( vrB, vrA ), c->normal ); + + // Compute normal impulse + b3FloatW negImpulse = b3MulW( mass, b3AddW( vn, b3MulW( c->restitution, cp->relativeVelocities ) ) ); + + // Clamp the accumulated impulse + b3FloatW newImpulse = b3MaxW( b3SubW( cp->normalImpulses, negImpulse ), b3ZeroW() ); + b3FloatW deltaImpulse = b3SubW( newImpulse, cp->normalImpulses ); + cp->normalImpulses = newImpulse; + cp->totalNormalImpulses = b3AddW( cp->totalNormalImpulses, deltaImpulse ); + + // Apply contact impulse + b3Vec3W P = b3MulSVW( deltaImpulse, c->normal ); + bA.w = b3MulSubMVW( bA.w, c->invIA, b3CrossW( rA, P ) ); + bA.v = b3MulSubSVW( bA.v, c->invMassA, P ); + bB.w = b3MulAddMVW( bB.w, c->invIB, b3CrossW( rB, P ) ); + bB.v = b3MulAddSVW( bB.v, c->invMassB, P ); + } + + b3ScatterBodies( states, c->indexA, &bA ); + b3ScatterBodies( states, c->indexB, &bB ); + } + + b3TracyCZoneEnd( restitution ); +} + +// Store impulses by contact constraint +void b3StoreImpulses_Convex( b3SolverBlock block, b3StepContext* context, int workerIndex ) +{ + b3TracyCZoneNC( store_impulses, "Store", b3_colorFireBrick, true ); + + b3World* world = context->world; + b3WidePrepareSpan* spans = context->widePrepareSpans; + const b3ContactConstraintWide* wideBase = context->wideConstraints; + b3TaskContext* taskContext = world->taskContexts.data + workerIndex; + b3BitSet* hitEventBitSet = &taskContext->hitEventBitSet; + bool hasHitEvents = taskContext->hasHitEvents; + float negHitThreshold = -world->hitEventThreshold; + + int wideIndex = block.startIndex; + int endWideIndex = block.startIndex + block.count; + + // Find color for start index + int colorIndex = 0; + while ( spans[colorIndex + 1].start <= wideIndex ) + { + colorIndex += 1; + } + + while ( wideIndex < endWideIndex ) + { + int colorWideStart = spans[colorIndex].start; + int colorWideEndIndex = b3MinInt( spans[colorIndex + 1].start, endWideIndex ); + int colorContactCount = spans[colorIndex].count; + int* contactIds = spans[colorIndex].contacts; + + for ( ; wideIndex < colorWideEndIndex; ++wideIndex ) + { + const b3ContactConstraintWide* c = wideBase + wideIndex; + const float* frictionImpulse1 = (float*)&c->frictionImpulse.x; + const float* frictionImpulse2 = (float*)&c->frictionImpulse.y; + const float* tangent1X = (float*)&c->tangent1.X; + const float* tangent1Y = (float*)&c->tangent1.Y; + const float* tangent1Z = (float*)&c->tangent1.Z; + const float* tangent2X = (float*)&c->tangent2.X; + const float* tangent2Y = (float*)&c->tangent2.Y; + const float* tangent2Z = (float*)&c->tangent2.Z; + const float* twistImpulse = (float*)&c->twistImpulse; + const float* rollingImpulseX = (float*)&c->rollingImpulse.X; + const float* rollingImpulseY = (float*)&c->rollingImpulse.Y; + const float* rollingImpulseZ = (float*)&c->rollingImpulse.Z; + + int localWideIndex = wideIndex - colorWideStart; + + for ( int lane = 0; lane < B3_SIMD_WIDTH; ++lane ) + { + int contactIndex = B3_SIMD_WIDTH * localWideIndex + lane; + if ( contactIndex >= colorContactCount ) + { + break; + } + + b3Manifold* m = c->manifolds[lane]; + if ( m == NULL ) + { + continue; + } + + float f1 = frictionImpulse1[lane]; + float f2 = frictionImpulse2[lane]; + m->frictionImpulse = (b3Vec3){ + f1 * tangent1X[lane] + f2 * tangent2X[lane], + f1 * tangent1Y[lane] + f2 * tangent2Y[lane], + f1 * tangent1Z[lane] + f2 * tangent2Z[lane], + }; + m->twistImpulse = twistImpulse[lane]; + m->rollingImpulse = (b3Vec3){ + rollingImpulseX[lane], + rollingImpulseY[lane], + rollingImpulseZ[lane], + }; + + int pointCount = m->pointCount; + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + const b3ContactConstraintPointWide* cp = c->points + pointIndex; + const float* normalImpulse = (float*)&cp->normalImpulses; + const float* totalNormalImpulse = (float*)&cp->totalNormalImpulses; + const float* normalVelocity = (float*)&cp->relativeVelocities; + + b3ManifoldPoint* mp = m->points + pointIndex; + mp->normalImpulse = normalImpulse[lane]; + mp->totalNormalImpulse = totalNormalImpulse[lane]; + mp->normalVelocity = normalVelocity[lane]; + } + + int contactId = contactIds[contactIndex]; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + if ( ( contact->flags & b3_simEnableHitEvent ) != 0 ) + { + for ( int k = 0; k < pointCount; ++k ) + { + b3ManifoldPoint* mp = m->points + k; + + // Need to check total impulse because the point may be speculative and not colliding + if ( mp->normalVelocity < negHitThreshold && mp->totalNormalImpulse > 0.0f ) + { + b3SetBit( hitEventBitSet, contact->contactId ); + hasHitEvents = true; + break; + } + } + } + } + } + + colorIndex += 1; + } + + taskContext->hasHitEvents = hasHitEvents; + + b3TracyCZoneEnd( store_impulses ); +} + +void b3PrepareContacts_Overflow( b3StepContext* context ) +{ + b3ConstraintGraph* graph = context->graph; + b3GraphColor* color = graph->colors + B3_OVERFLOW_INDEX; + + uint16_t count = (uint16_t)color->contacts.count; + if (count == 0) + { + return; + } + + b3SolverBlock block = { + .startIndex = 0, + .count = count, + .blockType = b3_overflowBlock, + .colorIndex = B3_OVERFLOW_INDEX, + }; + + b3PrepareContacts_Mesh( block, context ); +} + +void b3WarmStartContacts_Overflow( b3StepContext* context ) +{ + b3ConstraintGraph* graph = context->graph; + b3GraphColor* color = graph->colors + B3_OVERFLOW_INDEX; + + uint16_t count = (uint16_t)color->contacts.count; + if ( count == 0 ) + { + return; + } + + b3SolverBlock block = { + .startIndex = 0, + .count = count, + .blockType = b3_overflowBlock, + .colorIndex = B3_OVERFLOW_INDEX, + }; + + b3WarmStartContacts_Mesh( block, context ); +} + +void b3SolveContacts_Overflow( b3StepContext* context, bool useBias ) +{ + b3ConstraintGraph* graph = context->graph; + b3GraphColor* color = graph->colors + B3_OVERFLOW_INDEX; + + uint16_t count = (uint16_t)color->contacts.count; + if ( count == 0 ) + { + return; + } + + b3SolverBlock block = { + .startIndex = 0, + .count = count, + .blockType = b3_overflowBlock, + .colorIndex = B3_OVERFLOW_INDEX, + }; + + b3SolveContacts_Mesh( block, context, useBias ); +} + +void b3ApplyRestitution_Overflow( b3StepContext* context ) +{ + b3ConstraintGraph* graph = context->graph; + b3GraphColor* color = graph->colors + B3_OVERFLOW_INDEX; + + uint16_t count = (uint16_t)color->contacts.count; + if ( count == 0 ) + { + return; + } + + b3SolverBlock block = { + .startIndex = 0, + .count = count, + .blockType = b3_overflowBlock, + .colorIndex = B3_OVERFLOW_INDEX, + }; + + b3ApplyRestitution_Mesh( block, context ); +} + +void b3StoreImpulses_Overflow( b3StepContext* context ) +{ + b3ConstraintGraph* graph = context->graph; + b3GraphColor* color = graph->colors + B3_OVERFLOW_INDEX; + + uint16_t count = (uint16_t)color->contacts.count; + if ( count == 0 ) + { + return; + } + + b3SolverBlock block = { + .startIndex = 0, + .count = count, + .blockType = b3_overflowBlock, + .colorIndex = B3_OVERFLOW_INDEX, + }; + + b3StoreImpulses_Mesh( block, context, 0 ); +} diff --git a/vendor/box3d/src/src/contact_solver.h b/vendor/box3d/src/src/contact_solver.h new file mode 100644 index 000000000..fab17ee53 --- /dev/null +++ b/vendor/box3d/src/src/contact_solver.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "math_internal.h" +#include "solver.h" + +typedef struct b3ManifoldConstraintPoint +{ + b3Vec3 rA, rB; + float baseSeparation; + float relativeVelocity; + float normalImpulse; + float totalNormalImpulse; + float normalMass; + float leverArm; +} b3ManifoldConstraintPoint; + +typedef struct b3ManifoldConstraint +{ + // todo use pointer buffer + b3ManifoldConstraintPoint points[4]; + int pointCount; + b3Vec3 normal; + b3Vec3 tangent1; + b3Vec3 tangent2; + b3Vec3 originA, originB; + float twistMass; + float twistImpulse; + b3Matrix2 tangentMass; + b3Vec2 frictionImpulse; + b3Vec3 rollingImpulse; + float tangentVelocity1; + float tangentVelocity2; +} b3ManifoldConstraint; + +typedef struct b3ContactConstraint +{ + b3ManifoldConstraint* constraints; + struct b3Contact* contact; + int indexA; + int indexB; + float invMassA, invMassB; + b3Matrix3 invIA, invIB; + b3Softness softness; + b3Matrix3 rollingMass; + float friction; + float restitution; + float rollingResistance; + int manifoldCount; +} b3ContactConstraint; + +int b3GetWideContactConstraintByteCount( void ); + +// Overflow contacts don't fit into the constraint graph coloring +void b3PrepareContacts_Overflow( b3StepContext* context ); +void b3WarmStartContacts_Overflow( b3StepContext* context ); +void b3SolveContacts_Overflow( b3StepContext* context, bool useBias ); +void b3ApplyRestitution_Overflow( b3StepContext* context ); +void b3StoreImpulses_Overflow( b3StepContext* context ); + +void b3PrepareContacts_Mesh( b3SolverBlock block, b3StepContext* context ); +void b3WarmStartContacts_Mesh( b3SolverBlock block, b3StepContext* context ); +void b3SolveContacts_Mesh( b3SolverBlock block, b3StepContext* context, bool useBias ); +void b3ApplyRestitution_Mesh( b3SolverBlock block, b3StepContext* context ); +void b3StoreImpulses_Mesh( b3SolverBlock block, b3StepContext* context, int workerIndex ); + +void b3PrepareContacts_Convex( b3SolverBlock block, b3StepContext* context ); +void b3WarmStartContacts_Convex( b3SolverBlock block, b3StepContext* context ); +void b3SolveContacts_Convex( b3SolverBlock block, b3StepContext* context, bool useBias ); +void b3ApplyRestitution_Convex( b3SolverBlock block, b3StepContext* context ); +void b3StoreImpulses_Convex( b3SolverBlock block, b3StepContext* context, int workerIndex ); diff --git a/vendor/box3d/src/src/container.h b/vendor/box3d/src/src/container.h new file mode 100644 index 000000000..2831cb6ff --- /dev/null +++ b/vendor/box3d/src/src/container.h @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "algorithm.h" +#include "core.h" + +#include +#include + +#define b3DeclareArray( T ) \ + typedef struct b3DynamicArray_##T \ + { \ + struct T* data; \ + int count; \ + int capacity; \ + } b3DynamicArray_##T + +#define b3DeclareArrayNative( T ) \ + typedef struct b3DynamicArray_##T \ + { \ + T* data; \ + int count; \ + int capacity; \ + } b3DynamicArray_##T + +// Define an array. +// It may be zero initialized: +// b3Array(int) myArray = { 0 }; +#define b3Array( T ) b3DynamicArray_##T + +// Alternative to zero initialization +#define b3Array_Create( a ) \ + do \ + { \ + ( a ).data = NULL; \ + ( a ).count = 0; \ + ( a ).capacity = 0; \ + } \ + while ( 0 ) + +#define b3Array_CreateN( a, n ) \ + do \ + { \ + ( a ).data = ( n ) > 0 ? b3GrowAlloc( NULL, 0, ( n ) * sizeof( *( a ).data ) ) : NULL; \ + ( a ).count = 0; \ + ( a ).capacity = ( n ); \ + } \ + while ( 0 ) + +#define b3Array_Destroy( a ) \ + do \ + { \ + b3Free( ( a ).data, ( a ).capacity * sizeof( *( a ).data ) ); \ + ( a ).data = NULL; \ + ( a ).count = 0; \ + ( a ).capacity = 0; \ + } \ + while ( 0 ) + +#define b3Array_Reserve( a, n ) \ + do \ + { \ + if ( ( a ).capacity < n ) \ + { \ + int oldSize = ( a ).capacity * sizeof( *( a ).data ); \ + int newSize = ( n ) * sizeof( *( a ).data ); \ + ( a ).data = b3GrowAlloc( ( a ).data, oldSize, newSize ); \ + ( a ).capacity = ( n ); \ + } \ + } \ + while ( 0 ) + +#define b3Array_Resize( a, n ) \ + do \ + { \ + b3Array_Reserve( a, n ); \ + ( a ).count = ( n ); \ + } \ + while ( 0 ) + +// Push a new element by value +#define b3Array_Push( a, value ) \ + do \ + { \ + if ( ( a ).count >= ( a ).capacity ) \ + { \ + int oldSize = ( a ).capacity * sizeof( *( a ).data ); \ + int newCapacity = ( a ).capacity == 0 ? 8 : 2 * ( a ).capacity; \ + int newSize = newCapacity * sizeof( *( a ).data ); \ + ( a ).data = b3GrowAlloc( ( a ).data, oldSize, newSize ); \ + ( a ).capacity = newCapacity; \ + } \ + ( a ).data[( a ).count++] = ( value ); \ + } \ + while ( 0 ) + +// Get a pointer to an element +#define b3Array_Get( a, index ) ( B3_ASSERT( 0 <= ( index ) && ( index ) < ( a ).count ), ( a ).data + ( index ) ) + +// Create a new uninitialized element and return a pointer to it +#define b3Array_Emplace( a ) \ + ( b3EmplaceHelper( (void**)&( a ).data, &( a ).count, &( a ).capacity, sizeof( *( a ).data ) ) ) + +// Remove the last element and return it by value. +#define b3Array_Pop( a ) ( B3_ASSERT( 0 < ( a ).count ), ( a ).data[-1 + ( a ).count--] ) + +// Add an uninitialized element and return its index. +#define b3Array_AddIndex( a ) \ + ( b3EmplaceHelper( (void**)&( a ).data, &( a ).count, &( a ).capacity, sizeof( *( a ).data ) ), ( a ).count - 1 ) + +// Append a contiguous run of values. _n is used to cache the input count while avoiding naming conflicts. +#define b3Array_Append( a, src, n ) \ + do \ + { \ + int _n = ( n ); \ + if ( ( a ).count + _n > ( a ).capacity ) \ + { \ + int req = ( a ).count + _n; \ + int newCapacity = req > 2 ? req + ( req >> 1 ) : 8; \ + int oldSize = ( a ).capacity * sizeof( *( a ).data ); \ + int newSize = newCapacity * sizeof( *( a ).data ); \ + ( a ).data = b3GrowAlloc( ( a ).data, oldSize, newSize ); \ + ( a ).capacity = newCapacity; \ + } \ + memcpy( ( a ).data + ( a ).count, ( src ), _n * sizeof( *( a ).data ) ); \ + ( a ).count += _n; \ + } \ + while ( 0 ) + +// Zero the entire allocated buffer (capacity, not just count). +#define b3Array_MemZero( a ) \ + do \ + { \ + if ( ( a ).capacity > 0 ) \ + { \ + memset( ( a ).data, 0, ( a ).capacity * sizeof( *( a ).data ) ); \ + } \ + } \ + while ( 0 ) + +// Remove and element by swapping with the last element. If the index is the last element it returns +// B3_NULL_INDEX, otherwise it returns the index of the last element (which is now out of bounds). +#define b3Array_RemoveSwap( a, index ) b3RemoveHelper( ( a ).data, &( a ).count, ( index ), sizeof( *( a ).data ) ) + +B3_INLINE void* b3EmplaceHelper( void** data, int* count, int* capacity, int elem_size ) +{ + if ( *count >= *capacity ) + { + int oldCapacity = *capacity; + int oldSize = oldCapacity * elem_size; + int newCapacity = ( oldCapacity == 0 ? 16 : 2 * oldCapacity ); + int newSize = newCapacity * elem_size; + *data = b3GrowAlloc( *data, oldSize, newSize ); + *capacity = newCapacity; + } + return (char*)*data + ( *count )++ * elem_size; +} + +B3_INLINE int b3RemoveHelper( void* data, int* count, int index, int elementSize ) +{ + B3_ASSERT( 0 <= index && index < *count && "Array index out of bounds" ); + + ( *count )--; + if ( index != *count ) + { + memcpy( (char*)data + index * elementSize, (char*)data + ( *count ) * elementSize, elementSize ); + return *count; + } + + return B3_NULL_INDEX; +} + +#define b3Array_Clear( a ) \ + do \ + { \ + ( a ).count = 0; \ + } \ + while ( 0 ) + +#define b3Array_ByteCount( a ) ( ( a ).capacity * (int)sizeof( *( a ).data ) ) + +b3DeclareArrayNative( int ); diff --git a/vendor/box3d/src/src/convex_manifold.c b/vendor/box3d/src/src/convex_manifold.c new file mode 100644 index 000000000..2422567c4 --- /dev/null +++ b/vendor/box3d/src/src/convex_manifold.c @@ -0,0 +1,1600 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "algorithm.h" +#include "manifold.h" +#include "shape.h" + +#include "box3d/base.h" +#include "box3d/collision.h" +#include "box3d/constants.h" + +#include +#include + +static inline bool b3IsMinkowskiFaceIsolated( b3Vec3 a, b3Vec3 b, b3Vec3 n ) +{ + // An isolated edge (e.g. like in a capsule) defines a circle through the + // origin on the Gauss map. So testing for overlap between this circle and + // the arc AB simplifies to a simple plane test. + float an = b3Dot( a, n ); + float bn = b3Dot( b, n ); + + return an * bn <= 0.0f; +} + +// bxa = cross(b, a) and dxc = cross(d, c) +// but in practice we use the edge vector between the faces for robustness +static inline bool b3IsMinkowskiFace( b3Vec3 a, b3Vec3 b, b3Vec3 bxa, b3Vec3 c, b3Vec3 d, b3Vec3 dxc ) +{ + // Two edges build a face on the Minkowski sum if the associated arcs ab and cd intersect on the Gauss map. + // The associated arcs are defined by the adjacent face normals of each edge. + float cba = b3Dot( c, bxa ); + float dba = b3Dot( d, bxa ); + float adc = b3Dot( a, dxc ); + float bdc = b3Dot( b, dxc ); + + return cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f; +} + +static int b3ClipSegment( b3ClipVertex segment[2], b3Plane plane ) +{ + int vertexCount = 0; + b3ClipVertex vertex1 = segment[0]; + b3ClipVertex vertex2 = segment[1]; + + float distance1 = b3PlaneSeparation( plane, vertex1.position ); + float distance2 = b3PlaneSeparation( plane, vertex2.position ); + + // If the points are behind the plane + if ( distance1 <= 0.0f ) + { + segment[vertexCount++] = vertex1; + } + if ( distance2 <= 0.0f ) + { + segment[vertexCount++] = vertex2; + } + + // If the points are on different sides of the plane + if ( distance1 * distance2 < 0.0f ) + { + // Find intersection point of edge and plane + float t = distance1 / ( distance1 - distance2 ); + segment[vertexCount].position = b3Add( b3MulSV( 1.0f - t, vertex1.position ), b3MulSV( t, vertex2.position ) ); + segment[vertexCount].pair = distance1 > 0.0f ? vertex1.pair : vertex2.pair; + vertexCount++; + } + + return vertexCount; +} + +static int b3ClipSegmentToHullFace( b3ClipVertex segment[2], const b3HullData* hull, int refFace ) +{ + const b3HullFace* faces = b3GetHullFaces( hull ); + const b3Plane* planes = b3GetHullPlanes( hull ); + const b3HullHalfEdge* edges = b3GetHullEdges( hull ); + const b3Vec3* points = b3GetHullPoints( hull ); + + b3Plane refPlane = planes[refFace]; + + const b3HullFace* face = faces + refFace; + + int edgeIndex = face->edge; + + do + { + const b3HullHalfEdge* edge = edges + edgeIndex; + int nextEdgeIndex = edge->next; + const b3HullHalfEdge* next = edges + nextEdgeIndex; + + b3Vec3 vertex1 = points[edge->origin]; + b3Vec3 vertex2 = points[next->origin]; + b3Vec3 tangent = b3Normalize( b3Sub( vertex2, vertex1 ) ); + b3Vec3 binormal = b3Cross( tangent, refPlane.normal ); + + int pointCount = b3ClipSegment( segment, b3MakePlaneFromNormalAndPoint( binormal, vertex1 ) ); + if ( pointCount < 2 ) + { + return 0; + } + + edgeIndex = nextEdgeIndex; + } + while ( edgeIndex != face->edge ); + + return 2; +} + +static b3FaceQuery b3QueryFaceDirectionHullAndCapsule( const b3HullData* hull, const b3Capsule* capsule, + b3Transform capsuleTransform ) +{ + int maxFaceIndex = -1; + int maxVertexIndex = -1; + float maxFaceSeparation = -FLT_MAX; + const b3Plane* planes = b3GetHullPlanes( hull ); + + b3Vec3 capsulePoints[2] = { + b3TransformPoint( capsuleTransform, capsule->center1 ), + b3TransformPoint( capsuleTransform, capsule->center2 ), + }; + + for ( int faceIndex = 0; faceIndex < hull->faceCount; ++faceIndex ) + { + b3Plane plane = planes[faceIndex]; + + int vertexIndex = b3GetPointSupport( capsulePoints, 2, b3Neg( plane.normal ) ); + b3Vec3 support = capsulePoints[vertexIndex]; + float separation = b3PlaneSeparation( plane, support ); + if ( separation > maxFaceSeparation ) + { + maxVertexIndex = vertexIndex; + maxFaceIndex = faceIndex; + maxFaceSeparation = separation; + } + } + + return (b3FaceQuery){ + .separation = maxFaceSeparation, + .faceIndex = (uint8_t)maxFaceIndex, + .vertexIndex = (uint8_t)maxVertexIndex, + }; +} + +static b3FaceQuery b3QueryFaceDirections( const b3HullData* hullA, const b3HullData* hullB, b3Transform relativeTransform ) +{ + // We perform all computations in local space of the second hull + b3Transform transform = b3InvertTransform( relativeTransform ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + + int maxFaceIndex = -1; + int maxVertexIndex = -1; + float maxFaceSeparation = -FLT_MAX; + + for ( int faceIndex = 0; faceIndex < hullA->faceCount; ++faceIndex ) + { + b3Plane plane = b3TransformPlane( transform, planesA[faceIndex] ); + + int vertexIndex = b3FindHullSupportVertex( hullB, b3Neg( plane.normal ) ); + b3Vec3 support = pointsB[vertexIndex]; + float separation = b3PlaneSeparation( plane, support ); + if ( separation > maxFaceSeparation ) + { + maxFaceIndex = faceIndex; + maxVertexIndex = vertexIndex; + maxFaceSeparation = separation; + } + } + + return (b3FaceQuery){ + .separation = maxFaceSeparation, + .faceIndex = (uint8_t)maxFaceIndex, + .vertexIndex = (uint8_t)maxVertexIndex, + }; +} + +static b3EdgeQuery b3QueryEdgeDirectionHullAndCapsule( const b3HullData* hull, const b3Capsule* capsule, + b3Transform capsuleTransform ) +{ + // Find axis of minimum penetration + float maxSeparation = -FLT_MAX; + int maxIndex1 = -1; + int maxIndex2 = -1; + + // We perform all computations in local space of the hull + b3Vec3 p1 = b3TransformPoint( capsuleTransform, capsule->center1 ); + b3Vec3 q1 = b3TransformPoint( capsuleTransform, capsule->center2 ); + b3Vec3 e1 = b3Sub( q1, p1 ); + + const b3HullHalfEdge* edges = b3GetHullEdges( hull ); + const b3Vec3* points = b3GetHullPoints( hull ); + const b3Plane* planes = b3GetHullPlanes( hull ); + + for ( int index = 0; index < hull->edgeCount; index += 2 ) + { + const b3HullHalfEdge* edge = edges + index; + const b3HullHalfEdge* twin = edges + index + 1; + B3_ASSERT( edge->twin == index + 1 && twin->twin == index ); + + b3Vec3 p2 = points[edge->origin]; + b3Vec3 q2 = points[twin->origin]; + b3Vec3 e2 = b3Sub( q2, p2 ); + + b3Vec3 u2 = planes[edge->face].normal; + b3Vec3 v2 = planes[twin->face].normal; + + if ( b3IsMinkowskiFaceIsolated( u2, v2, e1 ) ) + { + // We can pass any point on the edge and choose + // the edge centers for better numerical precision. + b3Vec3 c1 = b3MulSV( 0.5f, b3Add( q1, p1 ) ); + b3Vec3 c2 = hull->center; + float separation = b3EdgeEdgeSeparation( q1, e1, c1, q2, e2, c2 ); + if ( separation > maxSeparation ) + { + // Note: We don't exit early if we find a separating axis here since we want to + // find the best one for caching and account for the convex radius later. + maxSeparation = separation; + maxIndex1 = 0; + maxIndex2 = index; + } + } + } + + // Save result + return (b3EdgeQuery){ + .separation = maxSeparation, + .indexA = (uint8_t)maxIndex1, + .indexB = (uint8_t)maxIndex2, + }; +} + +static b3EdgeQuery b3QueryEdgeDirections( const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA ) +{ + // Find axis of minimum penetration + float maxSeparation = -FLT_MAX; + int maxIndexA = B3_NULL_INDEX; + int maxIndexB = B3_NULL_INDEX; + + const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); + const b3Vec3* pointsA = b3GetHullPoints( hullA ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3HullHalfEdge* edgesB = b3GetHullEdges( hullB ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + const b3Plane* planesB = b3GetHullPlanes( hullB ); + + // Work in frame A + b3Matrix3 matrix = b3MakeMatrixFromQuat( transformBtoA.q ); + + // Arranged to minimize transform operations + for ( int indexB = 0; indexB < hullB->edgeCount; indexB += 2 ) + { + const b3HullHalfEdge* edgeB = edgesB + indexB; + const b3HullHalfEdge* twinB = edgesB + indexB + 1; + B3_ASSERT( edgeB->twin == indexB + 1 && twinB->twin == indexB ); + + b3Vec3 qB = pointsB[twinB->origin]; + b3Vec3 eB = b3MulMV( matrix, b3Sub( qB, pointsB[edgeB->origin] ) ); + qB = b3Add( b3MulMV( matrix, qB ), transformBtoA.p ); + + b3Vec3 uB = b3MulMV( matrix, planesB[edgeB->face].normal ); + b3Vec3 vB = b3MulMV( matrix, planesB[twinB->face].normal ); + + for ( int indexA = 0; indexA < hullA->edgeCount; indexA += 2 ) + { + const b3HullHalfEdge* edgeA = edgesA + indexA; + const b3HullHalfEdge* twinA = edgesA + indexA + 1; + B3_ASSERT( edgeA->twin == indexA + 1 && twinA->twin == indexA ); + + b3Vec3 qA = pointsA[twinA->origin]; + b3Vec3 eA = b3Sub( qA, pointsA[edgeA->origin] ); + b3Vec3 uA = planesA[edgeA->face].normal; + b3Vec3 vA = planesA[twinA->face].normal; + + bool isMinkowski; + { + // Two edges build a face on the Minkowski sum if the associated arcs AB and CD intersect on the Gauss map. + // The associated arcs are defined by the adjacent face normals of each edge. + float cba = b3Dot( uB, eA ); + float dba = b3Dot( vB, eA ); + float adc = -b3Dot( uA, eB ); + float bdc = -b3Dot( vA, eB ); + + isMinkowski = cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f; + } + + if ( isMinkowski ) + { + b3Vec3 centerA = hullA->center; + b3Vec3 centerB = b3TransformPoint( transformBtoA, hullB->center ); + float separation = b3EdgeEdgeSeparation( qA, eA, centerA, qB, eB, centerB ); + + if ( separation > maxSeparation ) + { + // Continues to find the maximum separating axis + maxSeparation = separation; + maxIndexA = indexA; + maxIndexB = indexB; + } + } + } + } + + return (b3EdgeQuery){ + .separation = maxSeparation, + .indexA = maxIndexA, + .indexB = maxIndexB, + }; +} + +// Reduce the manifold points to a maximum of 4 points. +// Note: this modifies the input point array to improve performance +static void b3ReduceManifoldPoints( b3LocalManifold* manifold, int capacity, b3LocalManifoldPoint* points, int count ) +{ + if ( capacity < 4 ) + { + return; + } + + if ( count <= 4 ) + { + for ( int i = 0; i < count; ++i ) + { + manifold->points[i] = points[i]; + } + + manifold->pointCount = count; + return; + } + + b3Vec3 normal = manifold->normal; + // float linearSlop = B3_LINEAR_SLOP; + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + float tolSqr = speculativeDistance * speculativeDistance; + + // This bias is very important for contact point consistency across time steps. + // It creates a pecking order to avoid flickering between candidates with similar scores. + float bias = 0.95f; + + // Step 1: find extreme point that is touching + int bestIndex = B3_NULL_INDEX; + float bestScore = -FLT_MAX; + + // Arbitrary tangent direction + // b3Vec3 perp1 = b3Perp( normal ); + // b3Vec3 perp2 = b3Cross( perp1, normal ); + // b3Vec3 searchDirection = -0.4535961214255773f * perp1 + 0.8912073600614354f * perp2; + b3Vec3 searchDirection = b3ArbitraryPerp( normal ); + for ( int index = 0; index < count; ++index ) + { + b3LocalManifoldPoint* pt = points + index; + + if ( pt->separation > speculativeDistance ) + { + continue; + } + + // The deeper the better + float score = -pt->separation + b3Dot( searchDirection, pt->point ); + if ( bias * score > bestScore ) + { + bestIndex = index; + bestScore = score; + } + } + + B3_VALIDATE( 0 <= bestIndex && bestIndex < count ); + if ( bestIndex == B3_NULL_INDEX ) + { + manifold->pointCount = 0; + return; + } + + manifold->points[0] = points[bestIndex]; + manifold->pointCount = 1; + + // Remove best point from array + points[bestIndex] = points[count - 1]; + count -= 1; + + b3Vec3 a = manifold->points[0].point; + + // Step 2: Find farthest point in 2D + bestScore = 0.0f; + bestIndex = B3_NULL_INDEX; + float maxDistanceSquared = 0.0f; + + for ( int index = 0; index < count; ++index ) + { + b3Vec3 p = points[index].point; + b3Vec3 d = b3Sub( p, a ); + b3Vec3 v = b3MulSub( d, b3Dot( d, normal ), normal ); + float distanceSquared = b3LengthSquared( v ); + maxDistanceSquared = b3MaxFloat( maxDistanceSquared, distanceSquared ); + float separation = b3MaxFloat( 0.0f, -points[index].separation ); + float score = distanceSquared + 4.0f * separation * separation; + if ( bias * score > bestScore ) + { + bestScore = score; + bestIndex = index; + } + } + + if ( bestScore < tolSqr ) + { + return; + } + + B3_ASSERT( 0 <= bestIndex && bestIndex < count ); + manifold->points[1] = points[bestIndex]; + manifold->pointCount = 2; + + // Remove best point from array + points[bestIndex] = points[count - 1]; + count -= 1; + + b3Vec3 b = manifold->points[1].point; + + // Step 3: Find the point with the maximum triangular area + bestScore = tolSqr; + bestIndex = B3_NULL_INDEX; + float bestSignedArea = 0.0f; + b3Vec3 ba = b3Sub( b, a ); + for ( int index = 0; index < count; ++index ) + { + b3Vec3 p = points[index].point; + float signedArea = b3Dot( normal, b3Cross( ba, b3Sub( p, a ) ) ); + float score = b3AbsFloat( signedArea ); + if ( bias * score >= bestScore ) + { + bestScore = score; + bestIndex = index; + bestSignedArea = signedArea; + } + } + + if ( bestIndex == B3_NULL_INDEX ) + { + return; + } + + B3_ASSERT( bestIndex != B3_NULL_INDEX ); + + manifold->points[2] = points[bestIndex]; + manifold->pointCount = 3; + points[bestIndex] = points[count - 1]; + count -= 1; + + b3Vec3 c = manifold->points[2].point; + + // Step 4: get the point that adds the most area outside the current triangle + bestScore = tolSqr; + bestIndex = B3_NULL_INDEX; + float sign = bestSignedArea < 0.0f ? -1.0f : 1.0f; + for ( int index = 0; index < count; ++index ) + { + b3Vec3 p = points[index].point; + float u1 = sign * b3Dot( normal, b3Cross( b3Sub( p, a ), ba ) ); + float u2 = sign * b3Dot( normal, b3Cross( b3Sub( p, b ), b3Sub( c, b ) ) ); + float u3 = sign * b3Dot( normal, b3Cross( b3Sub( p, c ), b3Sub( a, c ) ) ); + float score = b3MaxFloat( u1, b3MaxFloat( u2, u3 ) ); + + if ( bias * score > bestScore ) + { + bestScore = score; + bestIndex = index; + } + } + + if ( bestIndex != B3_NULL_INDEX ) + { + manifold->points[manifold->pointCount] = points[bestIndex]; + manifold->pointCount += 1; + } +} + +void b3CollideSpheres( b3LocalManifold* manifold, int capacity, const b3Sphere* sphereA, const b3Sphere* sphereB, + b3Transform transformBtoA ) +{ + if ( capacity == 0 ) + { + return; + } + + // Work in shapeB coordinates + b3Vec3 center1 = sphereA->center; + b3Vec3 center2 = b3TransformPoint( transformBtoA, sphereB->center ); + + float totalRadius = sphereA->radius + sphereB->radius; + b3Vec3 offset = b3Sub( center2, center1 ); + float distanceSq = b3LengthSquared( offset ); + + if ( distanceSq > totalRadius * totalRadius ) + { + // We found a separating axis + return; + } + + b3Vec3 normal = { 0.0f, 1.0f, 0.0f }; + float distance = sqrtf( distanceSq ); + if ( distance * distance > 1000.0f * FLT_MIN ) + { + normal = b3MulSV( 1.0f / distance, offset ); + } + + // Contact at the midpoint + // 0.5 * ( ((c1 + rA*n) + c2) - rB*n ) + b3Vec3 point = + b3MulSV( 0.5f, b3MulSub( b3Add( b3MulAdd( center1, sphereA->radius, normal ), center2 ), sphereB->radius, normal ) ); + + // Manifold in frame B + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = distance - totalRadius; + pt->pair = b3FeaturePair_single; +} + +void b3CollideCapsuleAndSphere( b3LocalManifold* manifold, int capacity, const b3Capsule* capsuleA, const b3Sphere* sphereB, + b3Transform transformBtoA ) +{ + manifold->pointCount = 0; + + if ( capacity < 1 ) + { + return; + } + + // Work in shape B coordinates + b3Vec3 center = b3TransformPoint( transformBtoA, sphereB->center ); + b3Vec3 center1 = capsuleA->center1; + b3Vec3 center2 = capsuleA->center2; + + float totalRadius = sphereB->radius + capsuleA->radius; + + b3Vec3 closestPoint = b3PointToSegmentDistance( center1, center2, center ); + b3Vec3 offset = b3Sub( center, closestPoint ); + float distanceSq = b3LengthSquared( offset ); + + if ( distanceSq > totalRadius * totalRadius ) + { + // We found a separating axis + return; + } + + b3Vec3 normal = { 0.0f, 1.0f, 0.0f }; + float distance = sqrtf( distanceSq ); + if ( distance * distance > 1000.0f * FLT_MIN ) + { + normal = b3MulSV( 1.0f / distance, offset ); + } + + // Contact at the midpoint + // 0.5 * (((center - sB*n) + closestPoint) + cA*n) + b3Vec3 point = + b3MulSV( 0.5f, b3MulAdd( b3Add( b3MulSub( center, sphereB->radius, normal ), closestPoint ), capsuleA->radius, normal ) ); + + // Manifold in frame B + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = distance - totalRadius; + pt->pair = b3FeaturePair_single; +} + +void b3CollideHullAndSphere( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3Sphere* sphereB, + b3Transform transformBtoA, b3SimplexCache* cache ) +{ + manifold->pointCount = 0; + + if ( capacity == 0 ) + { + return; + } + + b3Vec3 center = b3TransformPoint( transformBtoA, sphereB->center ); + + const float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + // Work in shapeA coordinates + + b3DistanceInput distanceInput; + distanceInput.proxyA = (b3ShapeProxy){ b3GetHullPoints( hullA ), hullA->vertexCount, 0.0f }; + distanceInput.proxyB = (b3ShapeProxy){ ¢er, 1, 0.0f }; + distanceInput.transform = b3Transform_identity; + distanceInput.useRadii = false; + + float radiusA = 0.0f; + float radiusB = sphereB->radius; + float radius = radiusA + radiusB; + + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, cache, NULL, 0 ); + + if ( distanceOutput.distance > radius + speculativeDistance ) + { + // We found a separating axis + *cache = (b3SimplexCache){ 0 }; + return; + } + + if ( distanceOutput.distance > 100.0f * FLT_EPSILON ) + { + // Shallow penetration + b3Vec3 normal = b3Normalize( b3Sub( distanceOutput.pointB, distanceOutput.pointA ) ); + + // cA is the projection of the sphere center onto to the hull (pointA if radiusA == 0). + b3Vec3 cA = b3MulAdd( center, radiusA - b3Dot( b3Sub( center, distanceOutput.pointA ), normal ), normal ); + + // cB is the deepest point on the sphere with respect to the reference f + b3Vec3 cB = b3MulSub( center, radiusB, normal ); + + b3Vec3 point = b3Lerp( cA, cB, 0.5f ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = distanceOutput.distance - radius; + pt->pair = b3FeaturePair_single; + } + else + { + // Deep penetration + int bestIndex = -1; + float bestDistance = -FLT_MAX; + const b3Plane* planes = b3GetHullPlanes( hullA ); + + for ( int index = 0; index < hullA->faceCount; ++index ) + { + b3Plane plane = planes[index]; + + float distance = b3PlaneSeparation( plane, center ); + if ( distance > bestDistance ) + { + bestIndex = index; + bestDistance = distance; + } + } + B3_ASSERT( bestIndex >= 0 ); + + b3Vec3 normal = planes[bestIndex].normal; + + // cA is the projection of the sphere center onto to the hull + b3Vec3 cA = b3MulAdd( center, radiusA - b3Dot( b3Sub( center, distanceOutput.pointA ), normal ), normal ); + + // cB is the deepest point on the sphere with respect to the reference f + b3Vec3 cB = b3MulSub( center, radiusB, normal ); + + b3Vec3 point = b3Lerp( cA, cB, 0.5f ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = bestDistance - radius; + pt->pair = b3FeaturePair_single; + } +} + +void b3CollideCapsules( b3LocalManifold* manifold, int capacity, const b3Capsule* capsuleA, const b3Capsule* capsuleB, + b3Transform transformBtoA ) +{ + manifold->pointCount = 0; + + if ( capacity < 2 ) + { + return; + } + + // Work in shapeA coordinates + b3Vec3 centerA1 = capsuleA->center1; + b3Vec3 centerA2 = capsuleA->center2; + b3Vec3 centerB1 = b3TransformPoint( transformBtoA, capsuleB->center1 ); + b3Vec3 centerB2 = b3TransformPoint( transformBtoA, capsuleB->center2 ); + + float radius = capsuleA->radius + capsuleB->radius; + float maxDistance = radius + B3_SPECULATIVE_DISTANCE; + + b3SegmentDistanceResult result = b3SegmentDistance( centerA1, centerA2, centerB1, centerB2 ); + b3Vec3 offset = b3Sub( result.point2, result.point1 ); + float distanceSquared = b3LengthSquared( offset ); + float linearSlop = B3_LINEAR_SLOP; + float minDistance = 0.01f * linearSlop; + + if ( distanceSquared > maxDistance * maxDistance || distanceSquared < minDistance * minDistance ) + { + // We found a separating axis + return; + } + + float lengthA; + b3Vec3 segmentA = b3Sub( centerA2, centerA1 ); + b3Vec3 edgeA = b3GetLengthAndNormalize( &lengthA, segmentA ); + if ( lengthA < B3_MIN_CAPSULE_LENGTH ) + { + return; + } + + float lengthB; + b3Vec3 segmentB = b3Sub( centerB2, centerB1 ); + b3Vec3 edgeB = b3GetLengthAndNormalize( &lengthB, segmentB ); + if ( lengthB < B3_MIN_CAPSULE_LENGTH ) + { + return; + } + + // Parallel edges: |eA x eB| = sin(alpha) + const float alphaTol = 0.05f; + const float alphaTolSqr = alphaTol * alphaTol; + b3Vec3 axis = b3Cross( edgeA, edgeB ); + + // Try to create two contact points if the capsules are nearly parallel + if ( b3LengthSquared( axis ) < alphaTolSqr ) + { + // Clip segment B against side planes of segment A + + // Sides planes of A + b3Plane planesA[2]; + planesA[0].normal = b3Neg( edgeA ); + planesA[0].offset = -b3Dot( edgeA, capsuleA->center1 ); + planesA[1].normal = edgeA; + planesA[1].offset = b3Dot( edgeA, capsuleA->center2 ); + + // Clip points for B + b3ClipVertex verticesB[2]; + verticesB[0].position = centerB1; + verticesB[0].separation = 0.0f; + verticesB[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 ); + verticesB[1].position = centerB2; + verticesB[1].separation = 0.0f; + verticesB[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 ); + + int pointCount = b3ClipSegment( verticesB, planesA[0] ); + if ( pointCount == 2 ) + { + pointCount = b3ClipSegment( verticesB, planesA[1] ); + } + + if ( pointCount == 2 ) + { + // Closest points on A to the clipped points on B. + b3Vec3 closestPoint1 = b3PointToSegmentDistance( centerA1, centerA2, verticesB[0].position ); + b3Vec3 closestPoint2 = b3PointToSegmentDistance( centerA1, centerA2, verticesB[1].position ); + + float distance1 = b3Distance( closestPoint1, verticesB[0].position ); + float distance2 = b3Distance( closestPoint2, verticesB[1].position ); + if ( distance1 <= radius && distance2 <= radius ) + { + if ( distance1 < minDistance || distance2 < minDistance ) + { + // Avoid divide by zero + return; + } + + b3Vec3 normal1 = b3MulSV( 1.0f / distance1, b3Sub( verticesB[0].position, closestPoint1 ) ); + b3Vec3 normal2 = b3MulSV( 1.0f / distance2, b3Sub( verticesB[1].position, closestPoint2 ) ); + b3Vec3 normal = b3Normalize( b3Add( normal1, normal2 ) ); + float radiusA = capsuleA->radius; + float radiusB = capsuleB->radius; + + // Contact is at the midpoint: 0.5 * (((vB.pos + rA*nK) + cP) - rB*n) + b3Vec3 point1 = + b3MulSV( 0.5f, b3MulSub( b3Add( b3MulAdd( verticesB[0].position, radiusA, normal1 ), closestPoint1 ), radiusB, + normal ) ); + b3Vec3 point2 = + b3MulSV( 0.5f, b3MulSub( b3Add( b3MulAdd( verticesB[1].position, radiusA, normal2 ), closestPoint2 ), radiusB, + normal ) ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 2; + + b3LocalManifoldPoint* pt1 = manifold->points + 0; + pt1->point = point1; + pt1->separation = distance1 - radius; + pt1->pair = verticesB[0].pair; + + b3LocalManifoldPoint* pt2 = manifold->points + 1; + pt2->point = point2; + pt2->separation = distance2 - radius; + pt2->pair = verticesB[1].pair; + + return; + } + } + } + + float distance; + b3Vec3 normal = b3GetLengthAndNormalize( &distance, offset ); + // Contact at the midpoint 0.5 * (((p1 + rA*n) + p2) - rB*n) + b3Vec3 point = b3MulSV( + 0.5f, b3MulSub( b3Add( b3MulAdd( result.point1, capsuleA->radius, normal ), result.point2 ), capsuleB->radius, normal ) ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = distance - radius; + pt->pair = b3FeaturePair_single; +} + +static bool b3BuildHullFaceAndCapsuleContact( b3LocalManifold* manifold, const b3HullData* hullA, const b3Capsule* capsuleB, + b3Transform transformBtoA, b3FaceQuery query ) +{ + // Work in shapeA coordinates + const b3Plane* planes = b3GetHullPlanes( hullA ); + + // Clip the capsule edge against the side planes of the reference face + int refFace = query.faceIndex; + b3Plane refPlane = planes[refFace]; + + b3ClipVertex segmentB[2]; + segmentB[0].position = b3TransformPoint( transformBtoA, capsuleB->center1 ); + segmentB[0].separation = 0.0f; + segmentB[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 ); + segmentB[1].position = b3TransformPoint( transformBtoA, capsuleB->center2 ); + segmentB[1].separation = 0.0f; + segmentB[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 ); + + int pointCount = b3ClipSegmentToHullFace( segmentB, hullA, refFace ); + if ( pointCount < 2 ) + { + return false; + } + + float distance1 = b3PlaneSeparation( refPlane, segmentB[0].position ); + float distance2 = b3PlaneSeparation( refPlane, segmentB[1].position ); + const float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + if ( distance1 <= speculativeDistance || distance2 <= speculativeDistance ) + { + b3Vec3 normal = refPlane.normal; + b3Vec3 point1 = b3MulSub( segmentB[0].position, 0.5f * ( distance1 + capsuleB->radius ), normal ); + b3Vec3 point2 = b3MulSub( segmentB[1].position, 0.5f * ( distance2 + capsuleB->radius ), normal ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 2; + + b3LocalManifoldPoint* pt1 = manifold->points + 0; + pt1->point = point1; + pt1->separation = distance1 - capsuleB->radius; + pt1->pair = segmentB[0].pair; + + b3LocalManifoldPoint* pt2 = manifold->points + 1; + pt2->point = point2; + pt2->separation = distance2 - capsuleB->radius; + pt2->pair = segmentB[1].pair; + + return true; + } + + return false; +} + +static inline float b3DeepestPointSeparation( const b3LocalManifold* manifold ) +{ + // Deepest point + float minSeparation = FLT_MAX; + int pointCount = manifold->pointCount; + for ( int i = 0; i < pointCount; ++i ) + { + minSeparation = b3MinFloat( minSeparation, manifold->points[i].separation ); + } + + return minSeparation; +} + +static bool b3BuildHullAndCapsuleEdgeContact( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, + const b3Capsule* capsuleB, b3Transform transformBtoA, b3EdgeQuery query ) +{ + if ( capacity < 1 ) + { + return false; + } + + // Work in shapeA coordinates + + b3Vec3 pc = b3TransformPoint( transformBtoA, capsuleB->center1 ); + b3Vec3 qc = b3TransformPoint( transformBtoA, capsuleB->center2 ); + b3Vec3 ec = b3Sub( qc, pc ); + + const b3HullHalfEdge* edges = b3GetHullEdges( hullA ); + const b3Vec3* points = b3GetHullPoints( hullA ); + + const b3HullHalfEdge* edge2 = edges + query.indexB; + const b3HullHalfEdge* twin2 = edges + edge2->twin; + b3Vec3 ch = hullA->center; + b3Vec3 ph = points[edge2->origin]; + b3Vec3 qh = points[twin2->origin]; + b3Vec3 eh = b3Sub( qh, ph ); + + b3Vec3 normal = b3Cross( ec, eh ); + normal = b3Normalize( normal ); + + // Normal should point outward from hull + if ( b3Dot( normal, b3Sub( ph, ch ) ) < 0.0f ) + { + normal = b3Neg( normal ); + } + + b3SegmentDistanceResult result = b3LineDistance( ph, eh, pc, ec ); + + if ( b3IsWithinSegments( &result ) == false ) + { + // closest point beyond end points + return false; + } + + b3Vec3 point = b3MulSV( 0.5f, b3Add( b3MulSub( result.point1, capsuleB->radius, normal ), result.point2 ) ); + + float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); + B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = separation - capsuleB->radius; + pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB ); + return true; +} + +void b3CollideHullAndCapsule( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3Capsule* capsuleB, + b3Transform transformBtoA, b3SimplexCache* cache ) +{ + manifold->pointCount = 0; + + if ( capacity < 2 ) + { + return; + } + + // Work in shapeA coordinates + b3DistanceInput distanceInput; + distanceInput.proxyA = (b3ShapeProxy){ b3GetHullPoints( hullA ), hullA->vertexCount, 0.0f }; + distanceInput.proxyB = (b3ShapeProxy){ &capsuleB->center1, 2, 0.0f }; + distanceInput.transform = transformBtoA; + distanceInput.useRadii = false; + + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, cache, NULL, 0 ); + const float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + if ( distanceOutput.distance > capsuleB->radius + speculativeDistance ) + { + // We found a separating axis + *cache = (b3SimplexCache){ 0 }; + return; + } + + if ( distanceOutput.distance > 100.0f * FLT_EPSILON ) + { + const b3Plane* planes = b3GetHullPlanes( hullA ); + + // Shallow penetration + b3Vec3 delta = distanceOutput.normal; + int refFace = b3FindHullSupportFace( hullA, delta ); + b3Plane refPlane = planes[refFace]; + + // Try to create two contact points if closest + // points difference is nearly parallel to face normal + const float kTolerance = 0.998f; + if ( b3AbsFloat( b3Dot( refPlane.normal, delta ) ) > kTolerance ) + { + // Clip capsule segment against side planes of reference face + b3ClipVertex verticesB[2]; + verticesB[0].position = b3TransformPoint( transformBtoA, capsuleB->center1 ); + verticesB[0].separation = 0.0f; + verticesB[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 ); + verticesB[1].position = b3TransformPoint( transformBtoA, capsuleB->center2 ); + verticesB[1].separation = 0.0f; + verticesB[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 ); + + int pointCount = b3ClipSegmentToHullFace( verticesB, hullA, refFace ); + + if ( pointCount == 2 ) + { + float distance1 = b3PlaneSeparation( refPlane, verticesB[0].position ); + float distance2 = b3PlaneSeparation( refPlane, verticesB[1].position ); + if ( distance1 <= capsuleB->radius + speculativeDistance || distance2 <= capsuleB->radius + speculativeDistance ) + { + b3Vec3 normal = refPlane.normal; + b3Vec3 point1 = b3MulSub( verticesB[0].position, 0.5f * ( capsuleB->radius + distance1 ), normal ); + b3Vec3 point2 = b3MulSub( verticesB[1].position, 0.5f * ( capsuleB->radius + distance2 ), normal ); + + // Manifold in frame A + manifold->normal = normal; + manifold->pointCount = 2; + + b3LocalManifoldPoint* pt1 = manifold->points + 0; + pt1->point = point1; + pt1->separation = distance1 - capsuleB->radius; + pt1->pair = verticesB[0].pair; + + b3LocalManifoldPoint* pt2 = manifold->points + 1; + pt2->point = point2; + pt2->separation = distance2 - capsuleB->radius; + pt2->pair = verticesB[1].pair; + + return; + } + } + } + + // Create contact from closest points + b3Vec3 point = + b3MulSV( 0.5f, b3Add( b3MulSub( distanceOutput.pointA, capsuleB->radius, delta ), distanceOutput.pointB ) ); + + // Manifold in frame A + manifold->normal = delta; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = distanceOutput.distance - capsuleB->radius; + pt->pair = b3FeaturePair_single; + return; + } + + // Deep penetration + + b3FaceQuery faceQuery = b3QueryFaceDirectionHullAndCapsule( hullA, capsuleB, transformBtoA ); + if ( faceQuery.separation > capsuleB->radius ) + { + // We found a separating axis + return; + } + + b3EdgeQuery edgeQuery = b3QueryEdgeDirectionHullAndCapsule( hullA, capsuleB, transformBtoA ); + if ( edgeQuery.separation > capsuleB->radius ) + { + // We found a separating axis + return; + } + + // Create face contact + float faceSeparation = faceQuery.separation - capsuleB->radius; + b3BuildHullFaceAndCapsuleContact( manifold, hullA, capsuleB, transformBtoA, faceQuery ); + if ( manifold->pointCount > 1 ) + { + // If ( Out.PointCount <= 1 ) -> Compare with unclipped separation + // If ( Out.PointCount > 1 ) -> Be aggressive and compare with clipped separation + // Face contact can be empty if it does not realize the axis of minimum penetration + faceSeparation = b3DeepestPointSeparation( manifold ); + } + B3_VALIDATE( faceSeparation <= 0.0f ); + + // Face contact can be empty if it does not realize the axis of minimum penetration. + // Create edge contact if face contact fails or edge contact is significantly better! + const float kRelEdgeTolerance = 0.90f; + const float kAbsTolerance = 0.5f * B3_LINEAR_SLOP; + float edgeSeparation = edgeQuery.separation - capsuleB->radius; + if ( manifold->pointCount == 0 || edgeSeparation > kRelEdgeTolerance * faceSeparation + kAbsTolerance ) + { + // Edge contact + b3BuildHullAndCapsuleEdgeContact( manifold, capacity, hullA, capsuleB, transformBtoA, edgeQuery ); + } +} + +static int b3BuildPolygon( b3ClipVertex* out, b3Transform transform, const b3HullData* hull, int incFace, b3Plane refPlane ) +{ + const b3HullFace* faces = b3GetHullFaces( hull ); + const b3HullHalfEdge* edges = b3GetHullEdges( hull ); + const b3Vec3* points = b3GetHullPoints( hull ); + + const b3HullFace* face = faces + incFace; + int edgeIndex = face->edge; + B3_ASSERT( edges[edgeIndex].face == incFace ); + + int outCount = 0; + + b3Matrix3 matrix = b3MakeMatrixFromQuat( transform.q ); + + do + { + const b3HullHalfEdge* edge = edges + edgeIndex; + + int nextEdgeIndex = edge->next; + const b3HullHalfEdge* next = edges + nextEdgeIndex; + + b3ClipVertex vertex; + vertex.position = b3Add( b3MulMV( matrix, points[next->origin] ), transform.p ); + vertex.separation = b3PlaneSeparation( refPlane, vertex.position ); + vertex.pair = b3MakeFeaturePair( b3_featureShapeB, edgeIndex, b3_featureShapeB, nextEdgeIndex ); + + out[outCount] = vertex; + outCount += 1; + + edgeIndex = nextEdgeIndex; + } + while ( edgeIndex != face->edge && outCount < B3_MAX_CLIP_POINTS ); + + B3_VALIDATE( b3ValidatePolygon( out, outCount ) ); + + return outCount; +} + +static bool b3BuildFaceAContact( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, + b3Transform transformBtoA, b3FaceQuery query, b3SATCache* cache ) +{ + const b3HullFace* facesA = b3GetHullFaces( hullA ); + const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3Vec3* pointsA = b3GetHullPoints( hullA ); + + // Reference face + int refFace = query.faceIndex; + b3Plane refPlane = planesA[refFace]; + + // Find incident face + b3Vec3 refNormalInB = b3InvRotateVector( transformBtoA.q, refPlane.normal ); + int incFace = b3FindIncidentFace( hullB, refNormalInB, query.vertexIndex ); + + // Build clip polygon from incident face in frame A + b3ClipVertex buffer1[B3_MAX_CLIP_POINTS], buffer2[B3_MAX_CLIP_POINTS]; + int pointCount = b3BuildPolygon( buffer1, transformBtoA, hullB, incFace, refPlane ); + + // Clip incident face against side planes of reference face + b3ClipVertex* input = buffer1; + b3ClipVertex* output = buffer2; + + const b3HullFace* face = facesA + refFace; + int edgeIndex = face->edge; + + do + { + const b3HullHalfEdge* edge = edgesA + edgeIndex; + int nextEdgeIndex = edge->next; + const b3HullHalfEdge* next = edgesA + nextEdgeIndex; + b3Vec3 vertex1 = pointsA[edge->origin]; + b3Vec3 vertex2 = pointsA[next->origin]; + b3Vec3 tangent = b3Normalize( b3Sub( vertex2, vertex1 ) ); + b3Vec3 binormal = b3Cross( tangent, refPlane.normal ); + + b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( binormal, vertex1 ); + + pointCount = b3ClipPolygon( output, input, pointCount, clipPlane, edgeIndex, refPlane ); + B3_ASSERT( pointCount <= B3_MAX_CLIP_POINTS ); + + B3_SWAP( output, input ); + + if ( pointCount < 3 ) + { + *cache = (b3SATCache){ 0 }; + return false; + } + + edgeIndex = nextEdgeIndex; + } + while ( edgeIndex != face->edge ); + + pointCount = b3MinInt( pointCount, B3_MAX_CLIP_POINTS ); + + b3LocalManifoldPoint points[B3_MAX_CLIP_POINTS]; + float minSeparation = FLT_MAX; + + manifold->normal = refPlane.normal; + + for ( int i = 0; i < pointCount; ++i ) + { + b3ClipVertex* clipPoint = input + i; + b3LocalManifoldPoint* pt = points + i; + *pt = (b3LocalManifoldPoint){ 0 }; + + // Using the half-way point keeps the points in the same position when swapping reference face from A to B. + b3Vec3 point = b3MulSub( clipPoint->position, 0.5f * clipPoint->separation, refPlane.normal ); + + // Old way of pushing onto the reference face. + // b3Vec3 point = clipPoint->position - clipPoint->separation * refPlane.normal; + + pt->point = point; + pt->separation = clipPoint->separation; + pt->pair = clipPoint->pair; + + minSeparation = b3MinFloat( minSeparation, clipPoint->separation ); + } + + if ( minSeparation >= B3_SPECULATIVE_DISTANCE ) + { + *cache = (b3SATCache){ 0 }; + return false; + } + + b3ReduceManifoldPoints( manifold, capacity, points, pointCount ); + + // Save cache + cache->separation = minSeparation; + cache->type = (uint8_t)b3_faceAxisA; + cache->indexA = (uint8_t)query.faceIndex; + cache->indexB = (uint8_t)query.vertexIndex; + + return true; +} + +static bool b3BuildFaceBContact( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, + b3Transform transformBtoA, b3FaceQuery query, b3SATCache* cache ) +{ + b3Transform transformAtoB = b3InvertTransform( transformBtoA ); + bool touching = b3BuildFaceAContact( manifold, capacity, hullB, hullA, transformAtoB, query, cache ); + if ( touching == false ) + { + return false; + } + + // Results are in frame B, need to transform them into frame A + b3Matrix3 matrix = b3MakeMatrixFromQuat( transformBtoA.q ); + + // Transform and flip normal so it points from A to B, even though the B has the reference face. + manifold->normal = b3Neg( b3MulMV( matrix, manifold->normal ) ); + cache->type = (uint8_t)b3_faceAxisB; + cache->indexA = (uint8_t)query.vertexIndex; + cache->indexB = (uint8_t)query.faceIndex; + + // Transform points from frame B to frame A. + // Also flip the pairs to ensure correct matches. + for ( int i = 0; i < manifold->pointCount; ++i ) + { + b3LocalManifoldPoint* pt = manifold->points + i; + pt->point = b3Add( b3MulMV( matrix, pt->point ), transformBtoA.p ); + pt->pair = b3FlipPair( pt->pair ); + } + + return true; +} + +static bool b3BuildEdgeContact( b3LocalManifold* manifold, const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA, + b3EdgeQuery query, b3SATCache* cache ) +{ + // Work in shapeA coordinates + const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); + const b3Vec3* pointsA = b3GetHullPoints( hullA ); + + const b3HullHalfEdge* edgesB = b3GetHullEdges( hullB ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + + // B3_VALIDATE( query.separation <= 2.0f * B3_SPECULATIVE_DISTANCE ); + + const b3HullHalfEdge* edgeA = edgesA + query.indexA; + const b3HullHalfEdge* twinA = edgesA + edgeA->twin; + b3Vec3 centerA = hullA->center; + b3Vec3 pA = pointsA[edgeA->origin]; + b3Vec3 qA = pointsA[twinA->origin]; + b3Vec3 eA = b3Sub( qA, pA ); + + const b3HullHalfEdge* edgeB = edgesB + query.indexB; + const b3HullHalfEdge* twinB = edgesB + edgeB->twin; + b3Vec3 pB = b3TransformPoint( transformBtoA, pointsB[edgeB->origin] ); + b3Vec3 qB = b3TransformPoint( transformBtoA, pointsB[twinB->origin] ); + b3Vec3 eB = b3Sub( qB, pB ); + + b3Vec3 normal = b3Cross( eA, eB ); + normal = b3Normalize( normal ); + + if ( b3Dot( normal, b3Sub( pA, centerA ) ) < 0.0f ) + { + normal = b3Neg( normal ); + } + + b3SegmentDistanceResult result = b3LineDistance( pA, eA, pB, eB ); + + if ( b3IsWithinSegments( &result ) == false ) + { + *cache = (b3SATCache){ 0 }; + return false; + } + + // This can slide off the end from caching + float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); + + // todo I suspect this could trip if the cache becomes invalid + // B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); + + b3Vec3 point = b3MulSV( 0.5f, b3Add( result.point1, result.point2 ) ); + + // Result in frame A + manifold->normal = normal; + manifold->pointCount = 1; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = separation; + pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB ); + + // Save cache + cache->separation = separation; + cache->type = (uint8_t)b3_edgePairAxis; + cache->indexA = (uint8_t)query.indexA; + cache->indexB = (uint8_t)query.indexB; + + return true; +} + +void b3CollideHulls( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, const b3HullData* hullB, b3Transform transformBtoA, + b3SATCache* cache ) +{ + manifold->pointCount = 0; + + if ( capacity < 4 ) + { + return; + } + + // Work in shapeA coordinates + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + + float linearSlop = B3_LINEAR_SLOP; + const b3HullHalfEdge* edgesA = b3GetHullEdges( hullA ); + const b3Plane* planesA = b3GetHullPlanes( hullA ); + const b3Vec3* pointsA = b3GetHullPoints( hullA ); + + const b3HullHalfEdge* edgesB = b3GetHullEdges( hullB ); + const b3Plane* planesB = b3GetHullPlanes( hullB ); + const b3Vec3* pointsB = b3GetHullPoints( hullB ); + + // Attempt to use the cache to speed up collision + switch ( cache->type ) + { + case b3_invalidAxis: + *cache = (b3SATCache){ 0 }; + break; + + case b3_faceAxisA: + { + B3_ASSERT( cache->indexA < hullA->faceCount ); + + // Check for separation using cached face + b3Plane plane = planesA[cache->indexA]; + b3Vec3 searchDirectionInB = b3Neg( b3InvRotateVector( transformBtoA.q, plane.normal ) ); + int vertexIndex = b3FindHullSupportVertex( hullB, searchDirectionInB ); + b3Vec3 support = b3TransformPoint( transformBtoA, pointsB[vertexIndex] ); + float separation = b3PlaneSeparation( plane, support ); + + if ( separation >= speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } + + // if ( cache->separation < speculativeDistance ) + { + // Attempt face contact using cached feature + b3FaceQuery faceQuery; + faceQuery.separation = 0.0f; + faceQuery.faceIndex = cache->indexA; + faceQuery.vertexIndex = vertexIndex; + + b3SATCache localCache = { 0 }; + bool touching = b3BuildFaceAContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, &localCache ); + if ( touching == true && b3AbsFloat( cache->separation - localCache.separation ) < linearSlop ) + { + // Cache hit, contact points generated + return; + } + } + } + break; + + case b3_faceAxisB: + { + B3_ASSERT( cache->indexB < hullB->faceCount ); + + // Check for separation using cached face + b3Plane plane = planesB[cache->indexB]; + b3Vec3 searchDirectionInA = b3Neg( b3RotateVector( transformBtoA.q, plane.normal ) ); + int vertexIndex = b3FindHullSupportVertex( hullA, searchDirectionInA ); + b3Vec3 support = b3InvTransformPoint( transformBtoA, pointsA[vertexIndex] ); + float separation = b3PlaneSeparation( plane, support ); + + if ( separation >= speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } + + // if ( cache->separation < speculativeDistance ) + { + // Attempt face contact using cached feature + b3FaceQuery faceQuery; + faceQuery.separation = 0.0f; + faceQuery.faceIndex = cache->indexB; + faceQuery.vertexIndex = vertexIndex; + + b3SATCache localCache = { 0 }; + bool touching = b3BuildFaceBContact( manifold, capacity, hullA, hullB, transformBtoA, faceQuery, &localCache ); + if ( touching == true && b3AbsFloat( cache->separation - localCache.separation ) < linearSlop ) + { + // Cache hit, contact points generated + return; + } + } + } + break; + + case b3_edgePairAxis: + { + int index1 = cache->indexA; + const b3HullHalfEdge* edge1 = edgesA + index1; + const b3HullHalfEdge* twin1 = edgesA + index1 + 1; + B3_ASSERT( edge1->twin == index1 + 1 && twin1->twin == index1 ); + + b3Vec3 p1 = pointsA[edge1->origin]; + b3Vec3 q1 = pointsA[twin1->origin]; + b3Vec3 e1 = b3Sub( q1, p1 ); + + b3Vec3 u1 = planesA[edge1->face].normal; + b3Vec3 v1 = planesA[twin1->face].normal; + + int index2 = cache->indexB; + const b3HullHalfEdge* edge2 = edgesB + index2; + const b3HullHalfEdge* twin2 = edgesB + index2 + 1; + B3_ASSERT( edge2->twin == index2 + 1 && twin2->twin == index2 ); + + b3Vec3 p2 = b3TransformPoint( transformBtoA, pointsB[edge2->origin] ); + b3Vec3 q2 = b3TransformPoint( transformBtoA, pointsB[twin2->origin] ); + b3Vec3 e2 = b3Sub( q2, p2 ); + + b3Vec3 u2 = b3RotateVector( transformBtoA.q, planesB[edge2->face].normal ); + b3Vec3 v2 = b3RotateVector( transformBtoA.q, planesB[twin2->face].normal ); + + // flipping the signs of u2 and v2 + // cross(v2, u2) == cross(-v2, -u2) + // so we still use -e2 + // but we can also use e1 = cross(u1, v1) and e2 = cross(u2, v2) + bool isMinkowski = b3IsMinkowskiFace( u1, v1, e1, b3Neg( u2 ), b3Neg( v2 ), e2 ); + if ( isMinkowski == true ) + { + // Transform reference center of the first hull into local space of the second hull + b3Vec3 c1 = hullA->center; + b3Vec3 c2 = b3TransformPoint( transformBtoA, hullB->center ); + + float separation = b3EdgeEdgeSeparation( p1, e1, c1, p2, e2, c2 ); + if ( separation > speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } + + // if ( cache->separation <= speculativeDistance ) + { + // Try to rebuild contact from last features + b3EdgeQuery edgeQuery; + edgeQuery.indexA = cache->indexA; + edgeQuery.indexB = cache->indexB; + edgeQuery.separation = 0.0f; + + b3SATCache localCache = { 0 }; + bool touching = b3BuildEdgeContact( manifold, hullA, hullB, transformBtoA, edgeQuery, &localCache ); + if ( touching && b3AbsFloat( cache->separation - localCache.separation ) < linearSlop ) + { + // Cache hit, contact point generated + return; + } + } + } + } + break; + + // This case is for testing + case b3_manualFaceAxisA: + { + b3FaceQuery faceQueryA = b3QueryFaceDirections( hullA, hullB, transformBtoA ); + b3BuildFaceAContact( manifold, capacity, hullA, hullB, transformBtoA, faceQueryA, cache ); + return; + } + + // This case is for testing + case b3_manualFaceAxisB: + { + b3FaceQuery faceQueryB = b3QueryFaceDirections( hullB, hullA, b3InvertTransform( transformBtoA ) ); + b3BuildFaceBContact( manifold, capacity, hullA, hullB, transformBtoA, faceQueryB, cache ); + return; + } + + // This case is for testing + case b3_manualEdgePairAxis: + { + b3EdgeQuery edgeQuery = b3QueryEdgeDirections( hullA, hullB, transformBtoA ); + if ( edgeQuery.indexA != B3_NULL_INDEX ) + { + b3BuildEdgeContact( manifold, hullA, hullB, transformBtoA, edgeQuery, cache ); + } + return; + } + + default: + B3_ASSERT( false ); + break; + } + + manifold->pointCount = 0; + *cache = (b3SATCache){ 0 }; + + // Find axis of minimum penetration + b3FaceQuery faceQueryA = b3QueryFaceDirections( hullA, hullB, transformBtoA ); + if ( faceQueryA.separation > speculativeDistance ) + { + B3_ASSERT( faceQueryA.faceIndex < hullA->faceCount ); + B3_ASSERT( faceQueryA.vertexIndex < hullB->vertexCount ); + + // We found a separating axis + cache->separation = faceQueryA.separation; + cache->type = (uint8_t)b3_faceAxisA; + cache->indexA = (uint8_t)faceQueryA.faceIndex; + cache->indexB = (uint8_t)faceQueryA.vertexIndex; + return; + } + + b3FaceQuery faceQueryB = b3QueryFaceDirections( hullB, hullA, b3InvertTransform( transformBtoA ) ); + if ( faceQueryB.separation > speculativeDistance ) + { + B3_ASSERT( faceQueryB.faceIndex < hullB->faceCount ); + B3_ASSERT( faceQueryB.vertexIndex < hullA->vertexCount ); + + // We found a separating axis + cache->separation = faceQueryB.separation; + cache->type = (uint8_t)b3_faceAxisB; + cache->indexA = (uint8_t)faceQueryB.vertexIndex; + cache->indexB = (uint8_t)faceQueryB.faceIndex; + return; + } + + b3EdgeQuery edgeQuery = b3QueryEdgeDirections( hullA, hullB, transformBtoA ); + if ( edgeQuery.separation > speculativeDistance ) + { + // We found a separating axis + cache->separation = edgeQuery.separation; + cache->type = (uint8_t)b3_edgePairAxis; + cache->indexA = (uint8_t)edgeQuery.indexA; + cache->indexB = (uint8_t)edgeQuery.indexB; + return; + } + + // Always build a face contact (e.g. Jenga problem) + float faceSeparationA = faceQueryA.separation; + float faceSeparationB = faceQueryB.separation; + B3_VALIDATE( faceSeparationA <= speculativeDistance && faceSeparationB <= speculativeDistance ); + + if ( faceSeparationB > faceSeparationA + 0.5f * linearSlop ) + { + // Face contact B + b3BuildFaceBContact( manifold, capacity, hullA, hullB, transformBtoA, faceQueryB, cache ); + } + else + { + // Face contact A + b3BuildFaceAContact( manifold, capacity, hullA, hullB, transformBtoA, faceQueryA, cache ); + } + + if ( edgeQuery.indexA == B3_NULL_INDEX ) + { + // There are no valid edge pairs (all edges parallel) + return; + } + + float clippedFaceSeparation = cache->separation; + + B3_VALIDATE( edgeQuery.separation <= speculativeDistance ); + + // todo get rid of relative tolerance + const float kRelEdgeTolerance = 0.90f; + // const float kRelFaceTolerance = 0.98f; + const float kAbsTolerance = 0.5f * linearSlop; + + // Face contact can be empty if it does not realize the axis of minimum penetration. + // Create edge contact if face contact fails or edge contact is significantly better! + if ( manifold->pointCount == 0 || edgeQuery.separation > kRelEdgeTolerance * clippedFaceSeparation + kAbsTolerance ) + { + // Edge contact + b3LocalManifold edgeManifold = { 0 }; + b3LocalManifoldPoint edgePoint = { 0 }; + edgeManifold.points = &edgePoint; + + b3BuildEdgeContact( &edgeManifold, hullA, hullB, transformBtoA, edgeQuery, cache ); + + // It is possible with speculation to have vertex-vertex collision that is missed by SAT. + // todo I doubt this backup scheme matters because I'm using the clipped face separation. + // B3_VALIDATE( edgeManifold.pointCount == 1 ); + + if ( edgeManifold.pointCount == 1 ) + { + // Copy edge manifold out, being careful to preserve manifold point buffer. + b3LocalManifoldPoint* points = manifold->points; + *manifold = edgeManifold; + manifold->points = points; + manifold->points[0] = edgePoint; + } + } +} diff --git a/vendor/box3d/src/src/core.c b/vendor/box3d/src/src/core.c new file mode 100644 index 000000000..6747594b5 --- /dev/null +++ b/vendor/box3d/src/src/core.c @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#if defined( B3_COMPILER_MSVC ) +// CRTDBG requires these to be included first +#define _CRTDBG_MAP_ALLOC +#include +#include +#else +#include +#endif + +#include "core.h" + +#include "box3d/constants.h" +#include "box3d/math_functions.h" + +#include +#include + +#ifdef BOX2D_PROFILE + +#include +#define b3TracyCAlloc( ptr, size ) TracyCAlloc( ptr, size ) +#define b3TracyCFree( ptr ) TracyCFree( ptr ) + +#else + +#define b3TracyCAlloc( ptr, size ) +#define b3TracyCFree( ptr ) + +#endif + +#include "platform.h" + +#include + +// This allows the user to change the length units at runtime +static float b3_lengthUnitsPerMeter = 1.0f; + +void b3SetLengthUnitsPerMeter( float lengthUnits ) +{ + B3_ASSERT( b3IsValidFloat( lengthUnits ) && lengthUnits > 0.0f ); + b3_lengthUnitsPerMeter = lengthUnits; +} + +float b3GetLengthUnitsPerMeter( void ) +{ + return b3_lengthUnitsPerMeter; +} + +static float b3_stallThreshold = FLT_MAX; + +void b3SetStallThreshold( float seconds ) +{ + B3_ASSERT( b3IsValidFloat( seconds ) && seconds > 0.0f ); + b3_stallThreshold = seconds; +} + +float b3GetStallThreshold( void ) +{ + return b3_stallThreshold; +} + +static int b3DefaultAssertFcn( const char* condition, const char* fileName, int lineNumber ) +{ + printf( "BOX3D ASSERTION: %s, %s, line %d\n", condition, fileName, lineNumber ); + + // return non-zero to break to debugger + return 1; +} + +b3AssertFcn* b3AssertHandler = b3DefaultAssertFcn; + +void b3SetAssertFcn( b3AssertFcn* assertFcn ) +{ + B3_ASSERT( assertFcn != NULL ); + b3AssertHandler = assertFcn; +} + +#if !defined( NDEBUG ) || defined( B3_ENABLE_ASSERT ) +int b3InternalAssert( const char* condition, const char* fileName, int lineNumber ) +{ + int result = b3AssertHandler( condition, fileName, lineNumber ); + if ( result ) + { + B3_BREAKPOINT; + } + return result; +} +#endif + +static void b3DefaultLogFcn( const char* message ) +{ + printf( "Box3D: %s\n", message ); +} + +b3LogFcn* b3LogHandler = b3DefaultLogFcn; + +void b3SetLogFcn( b3LogFcn* logFcn ) +{ + B3_ASSERT( logFcn != NULL ); + b3LogHandler = logFcn; +} + +void b3Log( const char* format, ... ) +{ + va_list args; + va_start( args, format ); + char buffer[512]; + vsnprintf( buffer, sizeof( buffer ), format, args ); + b3LogHandler( buffer ); + va_end( args ); +} + +b3Version b3GetVersion( void ) +{ + return (b3Version){ 0, 1, 0 }; +} + +bool b3IsDoublePrecision( void ) +{ +#if defined( BOX3D_DOUBLE_PRECISION ) + return true; +#else + return false; +#endif +} + +static b3AllocFcn* b3_allocFcn = NULL; +static b3FreeFcn* b3_freeFcn = NULL; + +b3AtomicInt b3_byteCount; + +void b3SetAllocator( b3AllocFcn* allocFcn, b3FreeFcn* freeFcn ) +{ + b3_allocFcn = allocFcn; + b3_freeFcn = freeFcn; +} + +void* b3Alloc( size_t size ) +{ + if ( size == 0 ) + { + return NULL; + } + + // This could cause some sharing issues, however Box3D rarely calls b3Alloc. + // todo this is not true, Box3D allocates a lot. + b3AtomicFetchAddInt( &b3_byteCount, (int)size ); + + // Allocation must be a multiple of B3_ALIGNMENT (required by spec). + // https://en.cppreference.com/w/c/memory/aligned_alloc + int alignedSize = ( ( (int)size - 1 ) | ( B3_ALIGNMENT - 1 ) ) + 1; + + if ( b3_allocFcn != NULL ) + { + void* ptr = b3_allocFcn( alignedSize, B3_ALIGNMENT ); + b3TracyCAlloc( ptr, size ); + + B3_ASSERT( ptr != NULL ); + B3_ASSERT( ( (uintptr_t)ptr & ( B3_ALIGNMENT - 1 ) ) == 0 ); + + return ptr; + } + +#ifdef B3_PLATFORM_WINDOWS + void* ptr = _aligned_malloc( alignedSize, B3_ALIGNMENT ); +#elif defined( B3_PLATFORM_ANDROID ) + void* ptr = NULL; + if ( posix_memalign( &ptr, B3_ALIGNMENT, alignedSize ) != 0 ) + { + // allocation failed, exit the application + exit( EXIT_FAILURE ); + } +#else + void* ptr = aligned_alloc( B3_ALIGNMENT, alignedSize ); +#endif + + b3TracyCAlloc( ptr, size ); + + B3_ASSERT( ptr != NULL ); + B3_ASSERT( ( (uintptr_t)ptr & ( B3_ALIGNMENT - 1 ) ) == 0 ); + + return ptr; +} + +void b3Free( void* mem, size_t size ) +{ + if ( mem == NULL ) + { + return; + } + + b3TracyCFree( mem ); + + if ( b3_freeFcn != NULL ) + { + b3_freeFcn( mem ); + } + else + { +#ifdef B3_PLATFORM_WINDOWS + _aligned_free( mem ); +#else + free( mem ); +#endif + } + + b3AtomicFetchAddInt( &b3_byteCount, -(int)size ); +} + +void* b3GrowAlloc( void* oldMem, int oldSize, int newSize ) +{ + B3_ASSERT( newSize > oldSize ); + void* newMem = b3Alloc( newSize ); + if ( oldSize > 0 ) + { + memcpy( newMem, oldMem, oldSize ); + b3Free( oldMem, oldSize ); + } + return newMem; +} + +int b3GetByteCount( void ) +{ + return b3AtomicLoadInt( &b3_byteCount ); +} + +void* b3AllocZeroed( size_t size ) +{ + void* mem = b3Alloc( size ); + memset( mem, 0, size ); + return mem; +} + +// Not used. Keeping around in case I need this. +void b3StrCpy( char* dst, int size, const char* src ) +{ + B3_ASSERT( size > 0 ); + + if ( src != NULL ) + { +#if defined( _MSC_VER ) + strncpy_s( dst, size, src, size - 1 ); +#else + strncpy( dst, src, size - 1 ); + dst[size - 1] = 0; +#endif + } + else + { + memset( dst, 0, size ); + } +} diff --git a/vendor/box3d/src/src/core.h b/vendor/box3d/src/src/core.h new file mode 100644 index 000000000..3aaf196b1 --- /dev/null +++ b/vendor/box3d/src/src/core.h @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/base.h" + +#include + +// clang-format off + +#ifdef NDEBUG + #define B3_DEBUG 0 +#else + #define B3_DEBUG 1 +#endif + +// Define platform +#if defined(_WIN32) || defined(_WIN64) + #define B3_PLATFORM_WINDOWS +#elif defined( __ANDROID__ ) + #define B3_PLATFORM_ANDROID +#elif defined( __linux__ ) + #define B3_PLATFORM_LINUX +#elif defined( __APPLE__ ) + #include + #if defined( TARGET_OS_IPHONE ) && !TARGET_OS_IPHONE + #define B3_PLATFORM_MACOS + #else + #define B3_PLATFORM_IOS + #endif +#elif defined( __EMSCRIPTEN__ ) + #define B3_PLATFORM_WASM +#else + #define B3_PLATFORM_UNKNOWN +#endif + +// Define CPU +#if defined( __x86_64__ ) || defined( _M_X64 ) || defined( __i386__ ) || defined( _M_IX86 ) + #define B3_CPU_X86_X64 +#elif defined( __aarch64__ ) || defined( _M_ARM64 ) || defined( __arm__ ) || defined( _M_ARM ) + #define B3_CPU_ARM +#elif defined( __EMSCRIPTEN__ ) + #define B3_CPU_WASM +#else + #define B3_CPU_UNKNOWN +#endif + +// Define SIMD +#if defined( BOX3D_DISABLE_SIMD ) + #define B3_SIMD_NONE + #define B3_SIMD_WIDTH 4 + //#pragma message("B3_SIMD_NONE") +#else + #if defined( B3_CPU_X86_X64 ) + #define B3_SIMD_SSE2 + #define B3_SIMD_WIDTH 4 + //#pragma message("B3_SIMD_SSE2") + #elif defined( B3_CPU_ARM ) + #define B3_SIMD_NEON + #define B3_SIMD_WIDTH 4 + //#pragma message("B3_SIMD_NEON") + #elif defined( B3_CPU_WASM ) + #define B3_CPU_WASM + #define B3_SIMD_SSE2 + #define B3_SIMD_WIDTH 4 + //#pragma message("B3_SIMD_SSE2") + #else + #define B3_SIMD_NONE + #define B3_SIMD_WIDTH 4 + //#pragma message("B3_SIMD_NONE") + #endif +#endif + +// Define compiler +#if defined( __clang__ ) + #define B3_COMPILER_CLANG +#elif defined( __GNUC__ ) + #define B3_COMPILER_GCC +#elif defined( _MSC_VER ) + #define B3_COMPILER_MSVC +#endif + +/// Tracy profiler instrumentation +/// https://github.com/wolfpld/tracy +#ifdef BOX3D_PROFILE + #include + #define b3TracyCZoneC( ctx, color, active ) TracyCZoneC( ctx, color, active ) + #define b3TracyCZoneNC( ctx, name, color, active ) TracyCZoneNC( ctx, name, color, active ) + #define b3TracyCZoneEnd( ctx ) TracyCZoneEnd( ctx ) + #define b3TracyCFrame TracyCFrameMark +#else + #define b3TracyCZoneC( ctx, color, active ) + #define b3TracyCZoneNC( ctx, name, color, active ) + #define b3TracyCZoneEnd( ctx ) + #define b3TracyCFrame +#endif + +// clang-format on + +typedef struct b3AtomicInt +{ + int value; +} b3AtomicInt; + +typedef struct b3AtomicU32 +{ + uint32_t value; +} b3AtomicU32; + +// Minimum memory alignment used for all allocations +#define B3_ALIGNMENT 16 + +// Returns the number of elements of an array +#define B3_ARRAY_COUNT( A ) (int)( sizeof( A ) / sizeof( A[0] ) ) + +// Used to prevent the compiler from warning about unused variables +#define B3_UNUSED( ... ) (void)sizeof( ( __VA_ARGS__, 0 ) ) + +// Use to validate definitions. Do not take my cookie. +#define B3_SECRET_COOKIE 1152023 + +#define B3_CHECK_DEF( DEF ) B3_ASSERT( DEF->internalValue == B3_SECRET_COOKIE ) +#define B3_CHECK_JOINT_DEF( DEF ) B3_ASSERT( DEF->base.internalValue == B3_SECRET_COOKIE ) + +// These macros help avoid sizeof bugs +#define B3_ALLOC( T, N ) (T*)b3Alloc( N * sizeof( T ) ); +#define B3_FREE( M, T, N ) b3Free( M, N * sizeof( T ) ); + +void* b3Alloc( size_t size ); +void* b3AllocZeroed( size_t size ); +void b3Free( void* mem, size_t size ); +void* b3GrowAlloc( void* oldMem, int oldSize, int newSize ); + +void b3Log( const char* format, ... ); + +// Geometry content hashes reserve zero to mean unhashed +static inline uint32_t b3NonZeroHash( uint32_t hash ) +{ + return hash != 0 ? hash : 1; +} + +typedef struct b3Mutex b3Mutex; +b3Mutex* b3CreateMutex( void ); +void b3DestroyMutex( b3Mutex* m ); +void b3LockMutex( b3Mutex* m ); +void b3UnlockMutex( b3Mutex* m ); + +typedef struct b3Semaphore b3Semaphore; +b3Semaphore* b3CreateSemaphore( int initCount ); +void b3DestroySemaphore( b3Semaphore* s ); +void b3WaitSemaphore( b3Semaphore* s ); +void b3SignalSemaphore( b3Semaphore* s ); + +typedef void b3ThreadFunction( void* context ); +typedef struct b3Thread b3Thread; +// Name may be NULL, otherwise it is copied. +b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name ); +void b3JoinThread( b3Thread* t ); + +void b3StrCpy( char* dst, int size, const char* src ); diff --git a/vendor/box3d/src/src/ctz.h b/vendor/box3d/src/src/ctz.h new file mode 100644 index 000000000..483ce4bec --- /dev/null +++ b/vendor/box3d/src/src/ctz.h @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/base.h" + +#include +#include + +#if defined( _MSC_VER ) && !defined( __clang__ ) + +#include + +// https://en.wikipedia.org/wiki/Find_first_set + +static inline uint32_t b3CTZ32( uint32_t block ) +{ + unsigned long index; + _BitScanForward( &index, block ); + return index; +} + +// This function doesn't need to be fast, so using the Ivy Bridge fallback. +static inline uint32_t b3CLZ32( uint32_t value ) +{ +#if 1 + + // Use BSR (Bit Scan Reverse) which is available on Ivy Bridge + unsigned long index; + if ( _BitScanReverse( &index, value ) ) + { + // BSR gives the index of the most significant 1-bit + // We need to invert this to get the number of leading zeros + return 31 - index; + } + else + { + // If x is 0, BSR sets the zero flag and doesn't modify index + // LZCNT should return 32 for an input of 0 + return 32; + } + +#else + + return __lzcnt( value ); + +#endif +} + +static inline uint32_t b3CTZ64( uint64_t block ) +{ + unsigned long index; + +#ifdef _WIN64 + _BitScanForward64( &index, block ); +#else + // 32-bit fall back + if ( (uint32_t)block != 0 ) + { + _BitScanForward( &index, (uint32_t)block ); + } + else + { + _BitScanForward( &index, (uint32_t)( block >> 32 ) ); + index += 32; + } +#endif + + return index; +} + +static inline int b3PopCount64( uint64_t block ) +{ + return (int)__popcnt64( block ); +} + +#else + +static inline uint32_t b3CTZ32( uint32_t block ) +{ + return __builtin_ctz( block ); +} + +static inline uint32_t b3CLZ32( uint32_t value ) +{ + return __builtin_clz( value ); +} + +static inline uint32_t b3CTZ64( uint64_t block ) +{ + return __builtin_ctzll( block ); +} + +static inline int b3PopCount64( uint64_t block ) +{ + return __builtin_popcountll( block ); +} + +#endif + +static inline bool b3IsPowerOf2( int x ) +{ + return ( x & ( x - 1 ) ) == 0; +} + +static inline int b3BoundingPowerOf2( int x ) +{ + if ( x <= 1 ) + { + return 1; + } + + return 32 - (int)b3CLZ32( (uint32_t)x - 1 ); +} + +static inline int b3RoundUpPowerOf2( int x ) +{ + if ( x <= 1 ) + { + return 1; + } + + return 1 << ( 32 - (int)b3CLZ32( (uint32_t)x - 1 ) ); +} + +static inline int b3LowerPowerOf2Exponent( int x ) +{ + B3_ASSERT( x > 0 ); + int clz = (int)b3CLZ32( (uint32_t)x ); + + // Position of most significant bit = floor(log2(M)) + return 31 - clz; +} diff --git a/vendor/box3d/src/src/distance.c b/vendor/box3d/src/src/distance.c new file mode 100644 index 000000000..3d558ab34 --- /dev/null +++ b/vendor/box3d/src/src/distance.c @@ -0,0 +1,1904 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// Dirk Gregorius contributed portions of this code + +#include "math_internal.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" + +#define B3_MAX_SIMPLEX_VERTICES 4 +#define B3_MAX_GJK_ITERATIONS 32 + +int b3GetProxySupport( const b3ShapeProxy* proxy, b3Vec3 axis ) +{ + int count = proxy->count; + const b3Vec3* points = proxy->points; + + B3_ASSERT( count > 0 ); + B3_ASSERT( points != NULL ); + + // We move the first vertex into the origin for improved precision. + // This is necessary since we don't have shape transforms and + // vertices can potentially be far away from the origin (large). + b3Vec3 origin = points[0]; + int maxIndex = 0; + float maxProjection = 0.0f; + + for ( int index = 1; index < count; ++index ) + { + // We subtract the first vertex since we are shifting into the origin. + float projection = b3Dot( axis, b3Sub( points[index], origin ) ); + if ( projection > maxProjection ) + { + maxIndex = index; + maxProjection = projection; + } + } + + return maxIndex; +} + +int b3GetPointSupport( const b3Vec3* points, int count, b3Vec3 axis ) +{ + B3_ASSERT( count > 0 ); + B3_ASSERT( points != NULL ); + + // We move the first vertex into the origin for improved precision. + // This is necessary since we don't have shape transforms and + // vertices can potentially be far away from the origin (large). + b3Vec3 origin = points[0]; + int maxIndex = 0; + float maxProjection = 0.0f; + + for ( int index = 1; index < count; ++index ) + { + // We subtract the first vertex since we are shifting into the origin. + float projection = b3Dot( axis, b3Sub( points[index], origin ) ); + if ( projection > maxProjection ) + { + maxIndex = index; + maxProjection = projection; + } + } + + return maxIndex; +} + +static void b3BarycentricCoordsEdge( float out[3], b3Vec3 a, b3Vec3 b ) +{ + b3Vec3 ab = b3Sub( b, a ); + + // Last element is divisor + float divisor = b3Dot( ab, ab ); + + out[0] = b3Dot( b, ab ); + out[1] = -b3Dot( a, ab ); + out[2] = divisor; +} + +static void b3BarycentricCoordsTri( float out[4], b3Vec3 a, b3Vec3 b, b3Vec3 c ) +{ + b3Vec3 ab = b3Sub( b, a ); + b3Vec3 ac = b3Sub( c, a ); + + b3Vec3 bXC = b3Cross( b, c ); + b3Vec3 cXA = b3Cross( c, a ); + b3Vec3 aXB = b3Cross( a, b ); + + b3Vec3 abXAc = b3Cross( ab, ac ); + + // Last element is divisor + float divisor = b3Dot( abXAc, abXAc ); + + out[0] = b3Dot( bXC, abXAc ); + out[1] = b3Dot( cXA, abXAc ); + out[2] = b3Dot( aXB, abXAc ); + out[3] = divisor; +} + +static void b3BarycentricCoordsTet( float out[5], b3Vec3 a, b3Vec3 b, b3Vec3 c, b3Vec3 d ) +{ + b3Vec3 ab = b3Sub( b, a ); + b3Vec3 ac = b3Sub( c, a ); + b3Vec3 ad = b3Sub( d, a ); + + // Last element is divisor (forced to be positive) + float divisor = b3ScalarTripleProduct( ab, ac, ad ); + + float sign = divisor < 0.0f ? -1.0f : 1.0f; + out[0] = sign * b3ScalarTripleProduct( b, c, d ); + out[1] = sign * b3ScalarTripleProduct( a, d, c ); + out[2] = sign * b3ScalarTripleProduct( a, b, d ); + out[3] = sign * b3ScalarTripleProduct( a, c, b ); + out[4] = sign * divisor; +} + +static float b3GetMetric( const b3Simplex* simplex ) +{ + int count = simplex->count; + B3_ASSERT( 1 <= count && count <= 4 ); + + const b3SimplexVertex* vertices = simplex->vertices; + + switch ( count ) + { + case 1: + { + return 0.0f; + } + + case 2: + { + b3Vec3 a = vertices[0].w; + b3Vec3 b = vertices[1].w; + return b3Distance( a, b ); + } + + case 3: + { + b3Vec3 a = vertices[0].w; + b3Vec3 b = vertices[1].w; + b3Vec3 c = vertices[2].w; + return b3Length( b3Cross( b3Sub( b, a ), b3Sub( c, a ) ) ) / 2.0f; + } + + case 4: + { + b3Vec3 a = vertices[0].w; + b3Vec3 b = vertices[1].w; + b3Vec3 c = vertices[2].w; + b3Vec3 d = vertices[3].w; + return b3ScalarTripleProduct( b3Sub( b, a ), b3Sub( c, a ), b3Sub( d, a ) ) / 6.0f; + } + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + return 0.0f; +} + +static void b3WriteCache( b3SimplexCache* cache, const b3Simplex* simplex ) +{ + int count = simplex->count; + cache->metric = b3GetMetric( simplex ); + cache->count = (uint16_t)count; + for ( int index = 0; index < count; ++index ) + { + cache->indexA[index] = (uint8_t)simplex->vertices[index].indexA; + cache->indexB[index] = (uint8_t)simplex->vertices[index].indexB; + } +} + +static bool b3SolveSimplex2( b3Simplex* simplex ) +{ + b3SimplexVertex* vs = simplex->vertices; + B3_ASSERT( simplex->count == 2 ); + + // Vertex regions + //float wAB[3]; + + b3Vec3 a = vs[0].w; + b3Vec3 b = vs[1].w; + b3Vec3 ab = b3Sub( b, a ); + + // Last element is divisor + float divisor = b3Dot( ab, ab ); + + float u = b3Dot( b, ab ); + float v = -b3Dot( a, ab ); + //wAB[2] = divisor; + + // V( A ) + if ( v <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0].a = 1.0f; + + return true; + } + + // V( B ) + if ( u <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = vs[1]; + vs[0].a = 1.0f; + + return true; + } + + // Edge region + if ( divisor <= 0.0f ) + { + return false; + } + + // VR( AB ) + float denominator = 1.0f / divisor; + vs[0].a = denominator * u; + vs[1].a = denominator * v; + + return true; +} + +static bool b3SolveSimplex3( b3Simplex* simplex ) +{ + b3SimplexVertex* vs = simplex->vertices; + B3_ASSERT( simplex->count == 3 ); + + // Get simplex (be aware of aliasing here!) + b3SimplexVertex v1 = vs[0]; + b3SimplexVertex v2 = vs[1]; + b3SimplexVertex v3 = vs[2]; + + // Vertex regions + float wAB[3], wBC[3], wCA[3]; + b3BarycentricCoordsEdge( wAB, v1.w, v2.w ); + b3BarycentricCoordsEdge( wBC, v2.w, v3.w ); + b3BarycentricCoordsEdge( wCA, v3.w, v1.w ); + + // VR( A ) + if ( wAB[1] <= 0.0f && wCA[0] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = v1; + vs[0].a = 1.0f; + + return true; + } + + // VR( B ) + if ( wBC[1] <= 0.0f && wAB[0] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = v2; + vs[0].a = 1.0f; + + return true; + } + + // VR( C ) + if ( wCA[1] <= 0.0f && wBC[0] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = v3; + vs[0].a = 1.0f; + + return true; + } + + // Edge regions + float wABC[4]; + b3BarycentricCoordsTri( wABC, v1.w, v2.w, v3.w ); + + // VR( AB ) + if ( wABC[2] <= 0.0f && wAB[0] > 0.0f && wAB[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = v1; + vs[1] = v2; + + // Normalize + float divisor = wAB[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wAB[0] / divisor; + vs[1].a = wAB[1] / divisor; + + return true; + } + + // VR( BC ) + if ( wABC[0] <= 0.0f && wBC[0] > 0.0f && wBC[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = v2; + vs[1] = v3; + + // Normalize + float divisor = wBC[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wBC[0] / divisor; + vs[1].a = wBC[1] / divisor; + + return true; + } + + // VR( CA ) + if ( wABC[1] <= 0.0f && wCA[0] > 0.0f && wCA[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = v3; + vs[1] = v1; + + // Normalize + float divisor = wCA[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wCA[0] / divisor; + vs[1].a = wCA[1] / divisor; + + return true; + } + + // Face region + float divisor = wABC[3]; + if ( divisor <= 0.0f ) + { + return false; + } + + // VR( ABC ) + vs[0].a = wABC[0] / divisor; + vs[1].a = wABC[1] / divisor; + vs[2].a = wABC[2] / divisor; + + return true; +} + +static bool b3SolveSimplex4( b3Simplex* simplex ) +{ + b3SimplexVertex* vs = simplex->vertices; + + // Get simplex (be aware of aliasing here!) + B3_ASSERT( simplex->count == 4 ); + b3SimplexVertex vertexA = vs[0]; + b3SimplexVertex vertexB = vs[1]; + b3SimplexVertex vertexC = vs[2]; + b3SimplexVertex vertexD = vs[3]; + + // Vertex region + float wAB[3], wAC[3], wAD[3], wBC[3], wCD[3], wDB[3]; + b3BarycentricCoordsEdge( wAB, vertexA.w, vertexB.w ); + b3BarycentricCoordsEdge( wAC, vertexA.w, vertexC.w ); + b3BarycentricCoordsEdge( wAD, vertexA.w, vertexD.w ); + b3BarycentricCoordsEdge( wBC, vertexB.w, vertexC.w ); + b3BarycentricCoordsEdge( wCD, vertexC.w, vertexD.w ); + b3BarycentricCoordsEdge( wDB, vertexD.w, vertexB.w ); + + // VR( A ) + if ( wAB[1] <= 0.0f && wAC[1] <= 0.0f && wAD[1] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = vertexA; + + vs[0].a = 1.0f; + + return true; + } + + // VR( B ) + if ( wAB[0] <= 0.0f && wDB[0] <= 0.0f && wBC[1] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = vertexB; + + vs[0].a = 1.0f; + + return true; + } + + // VR( C ) + if ( wAC[0] <= 0.0f && wBC[0] <= 0.0f && wCD[1] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = vertexC; + + vs[0].a = 1.0f; + + return true; + } + + // VR( D ) + if ( wAD[0] <= 0.0f && wCD[0] <= 0.0f && wDB[1] <= 0.0f ) + { + // Reduce simplex + simplex->count = 1; + vs[0] = vertexD; + + vs[0].a = 1.0f; + + return true; + } + + // Edge region + float wACB[4], wABD[4], wADC[4], wBCD[4]; + b3BarycentricCoordsTri( wACB, vertexA.w, vertexC.w, vertexB.w ); + b3BarycentricCoordsTri( wABD, vertexA.w, vertexB.w, vertexD.w ); + b3BarycentricCoordsTri( wADC, vertexA.w, vertexD.w, vertexC.w ); + b3BarycentricCoordsTri( wBCD, vertexB.w, vertexC.w, vertexD.w ); + + // VR( AB ) + if ( wABD[2] <= 0.0f && wACB[1] <= 0.0f && wAB[0] > 0.0f && wAB[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = vertexA; + vs[1] = vertexB; + + // Normalize + float divisor = wAB[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wAB[0] / divisor; + vs[1].a = wAB[1] / divisor; + + return true; + } + + // VR( AC ) + if ( wACB[2] <= 0.0f && wADC[1] <= 0.0f && wAC[0] > 0.0f && wAC[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = vertexA; + vs[1] = vertexC; + + // Normalize + float divisor = wAC[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wAC[0] / divisor; + vs[1].a = wAC[1] / divisor; + + return true; + } + + // VR( AD ) + if ( wADC[2] <= 0.0f && wABD[1] <= 0.0f && wAD[0] > 0.0f && wAD[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = vertexA; + vs[1] = vertexD; + + // Normalize + float divisor = wAD[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wAD[0] / divisor; + vs[1].a = wAD[1] / divisor; + + return true; + } + + // VR( BC ) + if ( wACB[0] <= 0.0f && wBCD[2] <= 0.0f && wBC[0] > 0.0f && wBC[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = vertexB; + vs[1] = vertexC; + + // Normalize + float divisor = wBC[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wBC[0] / divisor; + vs[1].a = wBC[1] / divisor; + + return true; + } + + // VR( CD ) + if ( wADC[0] <= 0.0f && wBCD[0] <= 0.0f && wCD[0] > 0.0f && wCD[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = vertexC; + vs[1] = vertexD; + + // Normalize + float divisor = wCD[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wCD[0] / divisor; + vs[1].a = wCD[1] / divisor; + + return true; + } + + // VR( DB ) + if ( wABD[0] <= 0.0f && wBCD[1] <= 0.0f && wDB[0] > 0.0f && wDB[1] > 0.0f ) + { + // Reduce simplex + simplex->count = 2; + vs[0] = vertexD; + vs[1] = vertexB; + + // Normalize + float divisor = wDB[2]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wDB[0] / divisor; + vs[1].a = wDB[1] / divisor; + + return true; + } + + // Face regions + float wABCD[5]; + b3BarycentricCoordsTet( wABCD, vertexA.w, vertexB.w, vertexC.w, vertexD.w ); + + // VR( ACB ) + if ( wABCD[3] < 0.0f && wACB[0] > 0.0f && wACB[1] > 0.0f && wACB[2] > 0.0f ) + { + // Reduce simplex + simplex->count = 3; + vs[0] = vertexA; + vs[1] = vertexC; + vs[2] = vertexB; + + // Normalize + float divisor = wACB[3]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wACB[0] / divisor; + vs[1].a = wACB[1] / divisor; + vs[2].a = wACB[2] / divisor; + + return true; + } + + // VR( ABD ) + if ( wABCD[2] < 0.0f && wABD[0] > 0.0f && wABD[1] > 0.0f && wABD[2] > 0.0f ) + { + // Reduce simplex + simplex->count = 3; + vs[0] = vertexA; + vs[1] = vertexB; + vs[2] = vertexD; + + // Normalize + float divisor = wABD[3]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wABD[0] / divisor; + vs[1].a = wABD[1] / divisor; + vs[2].a = wABD[2] / divisor; + + return true; + } + + // VR( ADC ) + if ( wABCD[1] < 0.0f && wADC[0] > 0.0f && wADC[1] > 0.0f && wADC[2] > 0.0f ) + { + // Reduce simplex + simplex->count = 3; + vs[0] = vertexA; + vs[1] = vertexD; + vs[2] = vertexC; + + // Normalize + float divisor = wADC[3]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wADC[0] / divisor; + vs[1].a = wADC[1] / divisor; + vs[2].a = wADC[2] / divisor; + + return true; + } + + // VR( BCD ) + if ( wABCD[0] < 0.0f && wBCD[0] > 0.0f && wBCD[1] > 0.0f && wBCD[2] > 0.0f ) + { + // Reduce simplex + simplex->count = 3; + vs[0] = vertexB; + vs[1] = vertexC; + vs[2] = vertexD; + + // Normalize + float divisor = wBCD[3]; + if ( divisor <= 0.0f ) + { + return false; + } + + vs[0].a = wBCD[0] / divisor; + vs[1].a = wBCD[1] / divisor; + vs[2].a = wBCD[2] / divisor; + + return true; + } + + // *** Inside tetrahedron *** + float divisor = wABCD[4]; + if ( divisor <= 0.0f ) + { + return false; + } + + // VR( ABCD ) + vs[0].a = wABCD[0] / divisor; + vs[1].a = wABCD[1] / divisor; + vs[2].a = wABCD[2] / divisor; + vs[3].a = wABCD[3] / divisor; + + return true; +} + +static void b3ComputeWitnessPoints( const b3Simplex* simplex, b3Vec3* vertexA, b3Vec3* vertexB ) +{ + const b3SimplexVertex* vs = simplex->vertices; + int count = simplex->count; + B3_ASSERT( 1 <= count && count <= 4 ); + + switch ( count ) + { + case 1: + *vertexA = vs[0].wA; + *vertexB = vs[0].wB; + break; + + case 2: + *vertexA = b3Blend2( vs[0].a, vs[0].wA, vs[1].a, vs[1].wA ); + *vertexB = b3Blend2( vs[0].a, vs[0].wB, vs[1].a, vs[1].wB ); + break; + + case 3: + *vertexA = b3Blend3( vs[0].a, vs[0].wA, vs[1].a, vs[1].wA, vs[2].a, vs[2].wA ); + *vertexB = b3Blend3( vs[0].a, vs[0].wB, vs[1].a, vs[1].wB, vs[2].a, vs[2].wB ); + break; + + case 4: + { + // Force identical points and *zero* distance + b3Vec3 sum = b3Add( b3Blend2( vs[0].a, vs[0].wA, vs[1].a, vs[1].wA ), + b3Blend2( vs[2].a, vs[2].wA, vs[3].a, vs[3].wA ) ); + *vertexA = sum; + *vertexB = sum; + } + break; + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } +} + +b3DistanceOutput b3ShapeDistance( const b3DistanceInput* input, b3SimplexCache* cache, b3Simplex* simplexes, int simplexCapacity ) +{ + // The query runs in frame A using the relative pose of B in A. + b3Transform xf = input->transform; + + // Use matrices for faster math + b3Matrix3 m = b3MakeMatrixFromQuat( xf.q ); + b3Matrix3 mt = b3Transpose( m ); + + const b3ShapeProxy* proxyA = &input->proxyA; + const b3ShapeProxy* proxyB = &input->proxyB; + + // Compute initial simplex from cache + B3_ASSERT( cache->count <= B3_MAX_SIMPLEX_VERTICES ); + + b3Simplex simplex = { 0 }; + b3SimplexVertex* vs = simplex.vertices; + + simplex.count = cache->count; + for ( int i = 0; i < cache->count; ++i ) + { + int index1 = cache->indexA[i]; + int index2 = cache->indexB[i]; + + B3_ASSERT( 0 <= index1 && index1 < proxyA->count ); + B3_ASSERT( 0 <= index2 && index2 < proxyB->count ); + + b3Vec3 vertex1 = proxyA->points[index1]; + b3Vec3 vertex2 = b3Add( b3MulMV( m, proxyB->points[index2] ), xf.p ); + + vs[i].indexA = index1; + vs[i].indexB = index2; + vs[i].wA = vertex1; + vs[i].wB = vertex2; + vs[i].w = b3Sub( vertex2, vertex1 ); + vs[i].a = 0.0f; + } + + // Compute the new simplex metric, if it is substantially + // different than the old metric flush the simplex. + if ( simplex.count > 0 ) + { + float metric1 = cache->metric; + float metric2 = b3GetMetric( &simplex ); + + // todo the tetrahedron metric can be negative + if ( 2.0f * metric1 < metric2 || metric2 < 0.5f * metric1 || metric2 < FLT_EPSILON ) + { + // Flush the simplex + simplex.count = 0; + } + } + + // If the cache is invalid or empty + if ( simplex.count == 0 ) + { + b3Vec3 vertex1 = proxyA->points[0]; + b3Vec3 vertex2 = b3Add( b3MulMV( m, proxyB->points[0] ), xf.p ); + + simplex.count = 1; + simplex.vertices[0].indexA = 0; + simplex.vertices[0].indexB = 0; + simplex.vertices[0].wA = vertex1; + simplex.vertices[0].wB = vertex2; + simplex.vertices[0].w = b3Sub( vertex2, vertex1 ); + simplex.vertices[0].a = 0.0f; + } + + b3Simplex backup = { 0 }; + + int simplexIndex = 0; + if ( simplexes != NULL && simplexIndex < simplexCapacity ) + { + simplexes[simplexIndex] = simplex; + simplexIndex += 1; + } + + b3DistanceOutput distanceOutput = { 0 }; + + // Keep track of squared distance + float distanceSq = FLT_MAX; + + b3Vec3 normal = b3Vec3_zero; + + // Run GJK + int iteration = 0; + for ( ; iteration < B3_MAX_GJK_ITERATIONS; ++iteration ) + { + // Solve simplex + bool solved = false; + switch ( simplex.count ) + { + case 1: + simplex.vertices[0].a = 1.0f; + solved = true; + break; + + case 2: + solved = b3SolveSimplex2( &simplex ); + break; + + case 3: + solved = b3SolveSimplex3( &simplex ); + break; + + case 4: + solved = b3SolveSimplex4( &simplex ); + break; + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + if ( solved == false ) + { + // No progress - reconstruct last simplex + B3_ASSERT( backup.count != 0 ); + simplex = backup; + break; + } + + if ( simplexes != NULL && simplexIndex < simplexCapacity ) + { + simplexes[simplexIndex] = simplex; + simplexIndex += 1; + distanceOutput.iterations = iteration; + distanceOutput.simplexCount = simplexIndex; + } + + if ( simplex.count == B3_MAX_SIMPLEX_VERTICES ) + { + // Overlap + b3Vec3 localPointA, localPointB; + b3ComputeWitnessPoints( &simplex, &localPointA, &localPointB ); + distanceOutput.pointA = localPointA; + distanceOutput.pointB = localPointB; + return distanceOutput; + } + + // Assure distance progression + float oldDistanceSq = distanceSq; + + // Compute closest point + b3Vec3 closestPoint = { 0 }; + + switch ( simplex.count ) + { + case 1: + closestPoint = vs[0].w; + break; + + case 2: + closestPoint = b3Blend2( vs[0].a, vs[0].w, vs[1].a, vs[1].w ); + break; + + case 3: + closestPoint = b3Blend3( vs[0].a, vs[0].w, vs[1].a, vs[1].w, vs[2].a, vs[2].w ); + break; + + case 4: + closestPoint = b3Add( b3Blend2( vs[0].a, vs[0].w, vs[1].a, vs[1].w ), + b3Blend2( vs[2].a, vs[2].w, vs[3].a, vs[3].w ) ); + break; + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + distanceSq = b3Dot( closestPoint, closestPoint ); + + if ( distanceSq >= oldDistanceSq ) + { + // No progress - reconstruct last simplex + B3_ASSERT( backup.count != 0 ); + simplex = backup; + break; + } + + // Build new tentative support point + b3Vec3 searchDirection = { 0 }; + + switch ( simplex.count ) + { + case 1: + { + // v = -A + searchDirection = b3Neg( vs[0].w ); + } + break; + + case 2: + { + // v = (AB x AO) x AB + b3Vec3 a = vs[0].w; + b3Vec3 b = vs[1].w; + + b3Vec3 ab = b3Sub( b, a ); + + searchDirection = b3Cross( b3Cross( ab, b3Neg( a ) ), ab ); + } + break; + + case 3: + { + // v = AB x AC or v = AC x AB + b3Vec3 a = vs[0].w; + b3Vec3 b = vs[1].w; + b3Vec3 c = vs[2].w; + + b3Vec3 ab = b3Sub( b, a ); + b3Vec3 ac = b3Sub( c, a ); + + b3Vec3 n = b3Cross( ab, ac ); + + searchDirection = b3Dot( n, a ) < 0.0f ? n : b3Neg( n ); + } + break; + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + if ( b3LengthSquared( searchDirection ) < 1000.0f * FLT_MIN ) + { + // The origin is probably contained by a line segment or triangle. + // Thus the shapes are overlapped. + b3Vec3 localPointA, localPointB; + b3ComputeWitnessPoints( &simplex, &localPointA, &localPointB ); + distanceOutput.pointA = localPointA; + distanceOutput.pointB = localPointB; + B3_VALIDATE( b3Distance( localPointA, localPointB ) < FLT_EPSILON ); + return distanceOutput; + } + + normal = b3Neg( searchDirection ); + + // Get new support points + b3Vec3 searchDirection1 = searchDirection; + int indexA = b3GetProxySupport( &input->proxyA, b3Neg( searchDirection1 ) ); + b3Vec3 supportA = input->proxyA.points[indexA]; + b3Vec3 searchDirection2 = b3MulMV( mt, searchDirection ); + int indexB = b3GetProxySupport( &input->proxyB, searchDirection2 ); + b3Vec3 supportB = b3Add( b3MulMV( m, input->proxyB.points[indexB] ), xf.p ); + + // Save current simplex and add new vertex - this can fail if we detect cycling + backup = simplex; + + // Check for duplicate support points. This is the main termination criteria. + bool duplicate = false; + for ( int i = 0; i < simplex.count; ++i ) + { + if ( vs[i].indexA == indexA && vs[i].indexB == indexB ) + { + duplicate = true; + break; + } + } + + if ( duplicate ) + { + break; + } + + vs[simplex.count].indexA = indexA; + vs[simplex.count].indexB = indexB; + vs[simplex.count].wA = supportA; + vs[simplex.count].wB = supportB; + vs[simplex.count].w = b3Sub( supportB, supportA ); + simplex.count += 1; + } + + normal = b3Normalize( normal ); + if ( b3IsNormalized( normal ) == false ) + { + // Treat as overlap + return distanceOutput; + } + + // Build witness points and safe cache + b3Vec3 localPointA, localPointB; + b3ComputeWitnessPoints( &simplex, &localPointA, &localPointB ); + b3WriteCache( cache, &simplex ); + + // Results stay in frame A + distanceOutput.pointA = localPointA; + distanceOutput.pointB = localPointB; + distanceOutput.distance = b3Distance( localPointA, localPointB ); + distanceOutput.normal = normal; + distanceOutput.iterations = iteration; + distanceOutput.simplexCount = simplexIndex; + + // Apply radii if requested + if ( input->useRadii ) + { + float rA = input->proxyA.radius; + float rB = input->proxyB.radius; + distanceOutput.distance = b3MaxFloat( 0.0f, distanceOutput.distance - rA - rB ); + + // Keep closest points on perimeter even if overlapped, this way the points move smoothly. + distanceOutput.pointA = b3Add( distanceOutput.pointA, b3MulSV( rA, normal ) ); + distanceOutput.pointB = b3Sub( distanceOutput.pointB, b3MulSV( rB, normal ) ); + } + + return distanceOutput; +} + + +// Separation function: +// f(t) = (c2 + t * dp2 - c1 - t * dp1 ) * n + +// Root finding : f(t) - target = 0 +// (c2 + t * dp2 - c1 - t * dp1 ) * n - target = 0 +// (c2 - c1) * n + t * (dp2 - dp1) * n - target = 0 +// t = [target - (c2 - c1) * n] / [(dp2 - dp1) * n] +// t = (target - d) / [(dp2 - dp1) * n] + +b3CastOutput b3ShapeCast( const b3ShapeCastPairInput* input ) +{ + // Compute tolerance + float linearSlop = B3_LINEAR_SLOP; + float totalRadius = input->proxyA.radius + input->proxyB.radius; + float target = b3MaxFloat( linearSlop, totalRadius - linearSlop ); + float tolerance = 0.25f * linearSlop; + + B3_ASSERT( target > tolerance ); + + // Prepare input for distance query + b3SimplexCache cache = { 0 }; + + float alpha = 0.0f; + + b3DistanceInput distanceInput = { 0 }; + distanceInput.proxyA = input->proxyA; + distanceInput.proxyB = input->proxyB; + distanceInput.useRadii = false; + + // The whole cast runs in frame A. Advance the relative pose of B in float each iteration, + // which keeps the math near the local origin and avoids re-relativizing world poses. + distanceInput.transform = input->transform; + + b3Vec3 delta2 = input->translationB; + b3DistanceOutput distanceOutput = { 0 }; + b3CastOutput output = { 0 }; + output.triangleIndex = B3_NULL_INDEX; + + int iteration = 0; + const int maxIterations = 20; + + for ( ; iteration < maxIterations; ++iteration ) + { + output.iterations += 1; + + distanceOutput = b3ShapeDistance( &distanceInput, &cache, NULL, 0 ); + + if ( distanceOutput.distance < target + tolerance ) + { + if ( iteration == 0 ) + { + if ( input->canEncroach && distanceOutput.distance > 2.0f * linearSlop ) + { + target = distanceOutput.distance - linearSlop; + } + else + { + // Initial overlap + output.hit = true; + + // Compute a common point + b3Vec3 c1 = b3MulAdd( distanceOutput.pointA, input->proxyA.radius, distanceOutput.normal ); + b3Vec3 c2 = b3MulAdd( distanceOutput.pointB, -input->proxyB.radius, distanceOutput.normal ); + output.point = b3Lerp( c1, c2, 0.5f ); + return output; + } + } + else + { + // Logging for bad input data + if ( distanceOutput.distance > 0.0f && b3IsNormalized( distanceOutput.normal ) == false ) + { + for ( int i = 0; i < input->proxyA.count; ++i ) + { + b3Vec3 p = input->proxyA.points[i]; + b3Log( "pointA[%d] = {%.9f, %.9f, %.9f}", i, p.x, p.y, p.z ); + } + b3Log( "radiusA = %.9f", input->proxyA.radius ); + + for ( int i = 0; i < input->proxyB.count; ++i ) + { + b3Vec3 p = input->proxyB.points[i]; + b3Log( "pointB[%d] = {%.9f, %.9f, %.9f}", i, p.x, p.y, p.z ); + } + b3Log( "radiusB = %.9f", input->proxyB.radius ); + + { + b3Transform xf = input->transform; + b3Log( "transform = {{%.9f, %.9f, %.9f}, {{%.9f, %.9f, %.9f}, %.9f}", xf.p.x, xf.p.y, xf.p.z, xf.q.v.x, + xf.q.v.y, xf.q.v.z, xf.q.s ); + } + + { + b3Vec3 t = input->translationB; + b3Log( "t = {%.9f, %.9f, %.9f}", t.x, t.y, t.z ); + } + + b3Log( "maxFraction = %.9f, canEncroach = %d", input->maxFraction, input->canEncroach ); + + // Numerical problem. Likely extreme input. + return output; + } + + // Hitting this assert implies that the algorithm brought the shapes too close. + // B3_ASSERT( distanceOutput.distance > 0.0f && b3IsNormalized( distanceOutput.normal ) ); + + output.fraction = alpha; + output.point = b3MulAdd( distanceOutput.pointA, input->proxyA.radius, distanceOutput.normal ); + output.normal = distanceOutput.normal; + output.hit = true; + return output; + } + } + + B3_ASSERT( distanceOutput.distance > 0.0f ); + B3_ASSERT( b3IsNormalized( distanceOutput.normal ) ); + + // Check if shapes are approaching each other + float denominator = b3Dot( delta2, distanceOutput.normal ); + if ( denominator >= 0.0f ) + { + // Miss + return output; + } + + // Advance sweep + alpha += ( target - distanceOutput.distance ) / denominator; + if ( alpha >= input->maxFraction ) + { + // Success! + return output; + } + + distanceInput.transform.p = b3MulAdd( input->transform.p, alpha, delta2 ); + } + + // Failure! + return output; +} + +b3Transform b3GetSweepTransform( const b3Sweep* sweep, float time ) +{ + b3Transform transform; + transform.q = b3NLerp( sweep->q1, sweep->q2, time ); + transform.p = b3Sub( b3Lerp( sweep->c1, sweep->c2, time ), b3RotateVector( transform.q, sweep->localCenter ) ); + return transform; +} + +static inline b3Transform b3GetFinalSweepTransform( const b3Sweep* sweep ) +{ + b3Transform transform; + transform.q = sweep->q2; + transform.p = b3Sub( sweep->c2, b3RotateVector( transform.q, sweep->localCenter ) ); + return transform; +} + +static int b3UniqueCount( int vertexCount, int vertices[3] ) +{ + B3_ASSERT( 1 <= vertexCount && vertexCount <= 3 ); + + switch ( vertexCount ) + { + case 1: + return 1; + + case 2: + return vertices[0] != vertices[1] ? 2 : 1; + + case 3: + if ( vertices[0] != vertices[1] && vertices[0] != vertices[2] && vertices[1] != vertices[2] ) + { + // All different + return 3; + } + + if ( vertices[0] == vertices[1] && vertices[0] == vertices[2] && vertices[1] == vertices[2] ) + { + // All equal + return 1; + } + + return 2; + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + return 0; +} + +// This checks if the cross product of two edges switches direction. +static inline bool b3CheckFastEdges( b3Transform xfA, b3Vec3 localEdgeA, b3Transform xfB, b3Vec3 localEdgeB, b3Vec3 axis0 ) +{ + // By taking the local witness axes we make sure that we + // get the correct orientations (e.g. if one axis was flipped)! + b3Vec3 edgeA = b3RotateVector( xfA.q, localEdgeA ); + b3Vec3 edgeB = b3RotateVector( xfB.q, localEdgeB ); + b3Vec3 axis = b3Cross( edgeA, edgeB ); + return b3Dot( axis, axis0 ) < 0.0f; +} + +typedef enum b3SeparationType +{ + b3_separationUnknown = 0, + b3_separationVertices, + b3_separationEdges, + b3_separationFaceA, + b3_separationFaceB, +} b3SeparationType; + +typedef struct b3SeparationFunction +{ + const b3ShapeProxy* proxyA; + const b3ShapeProxy* proxyB; + b3Sweep sweepA; + b3Sweep sweepB; + + // These are associated with different bodies depending on the separation function type. + // It could be two local vectors/points on the same body (for example, both on bodyA). + b3Vec3 witness1; + b3Vec3 witness2; + + b3SeparationType type; +} b3SeparationFunction; + +static b3SeparationFunction b3MakeSeparationFunction( const b3SimplexCache cache, const b3ShapeProxy* proxyA, + const b3Sweep* sweepA, const b3ShapeProxy* proxyB, const b3Sweep* sweepB, + b3Vec3 worldNormal, float t1 ) +{ + B3_ASSERT( 1 <= cache.count && cache.count <= 3 ); + B3_VALIDATE( b3IsNormalized( worldNormal ) ); + + b3SeparationFunction fcn = { 0 }; + fcn.proxyA = proxyA; + fcn.proxyB = proxyB; + fcn.sweepA = *sweepA; + fcn.sweepB = *sweepB; + fcn.type = b3_separationUnknown; + + int indexA[3] = { cache.indexA[0], cache.indexA[1], cache.indexA[2] }; + int indexB[3] = { cache.indexB[0], cache.indexB[1], cache.indexB[2] }; + + int uniqueCountA = b3UniqueCount( cache.count, indexA ); + int uniqueCountB = b3UniqueCount( cache.count, indexB ); + + b3Transform xfA1 = b3GetSweepTransform( sweepA, t1 ); + b3Transform xfB1 = b3GetSweepTransform( sweepB, t1 ); + + b3Quat qA = xfA1.q; + b3Quat qB = xfB1.q; + + // Minimize round-off + b3Vec3 deltaP = b3Sub( xfB1.p, xfA1.p ); + + switch ( cache.count ) + { + case 1: + { + // Witness is the world space direction + fcn.type = b3_separationVertices; + fcn.witness1 = worldNormal; + } + break; + + case 2: + { + if ( uniqueCountA == 2 && uniqueCountB == 2 ) + { + // Edge/Edge + b3Vec3 vA1 = proxyA->points[indexA[0]]; + b3Vec3 localEdgeA = b3Sub( proxyA->points[indexA[1]], vA1 ); + localEdgeA = b3Normalize( localEdgeA ); + b3Vec3 edgeA = b3RotateVector( qA, localEdgeA ); + + b3Vec3 vB1 = proxyB->points[indexB[0]]; + b3Vec3 localEdgeB = b3Sub( proxyB->points[indexB[1]], vB1 ); + localEdgeB = b3Normalize( localEdgeB ); + b3Vec3 edgeB = b3RotateVector( qB, localEdgeB ); + + b3Vec3 axis = b3Cross( edgeA, edgeB ); + float lengthSquared = b3LengthSquared( axis ); + + // Skip near parallel edges: |e1 x e1| = sin(alpha) * |e1| * |e2| + const float kToleranceSquared = 0.05f * 0.05f; + if ( lengthSquared < kToleranceSquared ) + { + // The axis is not safe to normalize so we use a world axis instead! + fcn.type = b3_separationVertices; + fcn.witness1 = worldNormal; + } + else + { + b3Vec3 delta = b3Add( b3Sub( b3RotateVector( qB, vB1 ), b3RotateVector( qA, vA1 ) ), deltaP ); + if ( b3Dot( delta, axis ) < 0.0f ) + { + // Make axis point from A to B + axis = b3Neg( axis ); + localEdgeB = b3Neg( localEdgeB ); + } + + // Check for possible sign flip in edge/edge cross product + b3Transform xfA2 = b3GetFinalSweepTransform( sweepA ); + b3Transform xfB2 = b3GetFinalSweepTransform( sweepB ); + bool fastEdges = b3CheckFastEdges( xfA2, localEdgeA, xfB2, localEdgeB, axis ); + if ( fastEdges == true ) + { + // Not safe to use local edges, fall back to initial world space axis instead + fcn.type = b3_separationVertices; + fcn.witness1 = b3Normalize( axis ); + } + else + { + // Edge cross product is safe. This converges faster than a fixed axis. + fcn.type = b3_separationEdges; + fcn.witness1 = localEdgeA; + fcn.witness2 = localEdgeB; + } + } + } + else + { + B3_VALIDATE( b3IsNormalized( worldNormal ) ); + + // Vertex versus edge, use world axis witness + fcn.type = b3_separationVertices; + fcn.witness1 = worldNormal; + } + } + break; + + case 3: + { + if ( uniqueCountA == 3 ) + { + b3Vec3 vA1 = proxyA->points[indexA[0]]; + b3Vec3 vA2 = proxyA->points[indexA[1]]; + b3Vec3 vA3 = proxyA->points[indexA[2]]; + b3Vec3 localAxisA = b3Cross( b3Sub( vA2, vA1 ), b3Sub( vA3, vA1 ) ); + localAxisA = b3Normalize( localAxisA ); + b3Vec3 axisA = b3RotateVector( qA, localAxisA ); + + b3Vec3 localPointA = b3MulSV( 1.0f / 3.0f, b3Add( b3Add( vA1, vA2 ), vA3 ) ); + b3Vec3 localPointB = proxyB->points[indexB[0]]; + b3Vec3 delta = b3Add( b3Sub( b3RotateVector( qB, localPointB ), b3RotateVector( qA, localPointA ) ), deltaP ); + + if ( b3Dot( delta, axisA ) < 0.0f ) + { + // Make axis point from A to B + localAxisA = b3Neg( localAxisA ); + } + + // Witness is the local plane of faceA + fcn.type = b3_separationFaceA; + fcn.witness1 = localAxisA; + fcn.witness2 = localPointA; + } + else if ( uniqueCountB == 3 ) + { + b3Vec3 vB1 = proxyB->points[indexB[0]]; + b3Vec3 vB2 = proxyB->points[indexB[1]]; + b3Vec3 vB3 = proxyB->points[indexB[2]]; + b3Vec3 localAxisB = b3Cross( b3Sub( vB2, vB1 ), b3Sub( vB3, vB1 ) ); + localAxisB = b3Normalize( localAxisB ); + b3Vec3 axisB = b3RotateVector( qB, localAxisB ); + + b3Vec3 localPointA = proxyA->points[indexA[0]]; + b3Vec3 localPointB = b3MulSV( 1.0f / 3.0f, b3Add( b3Add( vB1, vB2 ), vB3 ) ); + b3Vec3 delta = b3Sub( b3Sub( b3RotateVector( qA, localPointA ), b3RotateVector( qB, localPointB ) ), deltaP ); + + if ( b3Dot( delta, axisB ) < 0.0f ) + { + // Make axis point from B to A + localAxisB = b3Neg( localAxisB ); + } + + // Witness is the local plane of faceB + fcn.type = b3_separationFaceB; + fcn.witness1 = localAxisB; + fcn.witness2 = localPointB; + } + else + { + B3_ASSERT( uniqueCountA == 2 && uniqueCountB == 2 ); + + if ( indexA[0] == indexA[1] ) + { + // Make first two indices are unique + indexA[1] = indexA[2]; + B3_ASSERT( indexA[0] != indexA[1] ); + } + + b3Vec3 vA1 = proxyA->points[indexA[0]]; + b3Vec3 vA2 = proxyA->points[indexA[1]]; + b3Vec3 localEdgeA = b3Normalize( b3Sub( vA2, vA1 ) ); + b3Vec3 edgeA = b3RotateVector( qA, localEdgeA ); + + if ( indexB[0] == indexB[1] ) + { + // Make first two indices are unique + indexB[1] = indexB[2]; + B3_ASSERT( indexB[0] != indexB[1] ); + } + + b3Vec3 vB1 = proxyB->points[indexB[0]]; + b3Vec3 vB2 = proxyB->points[indexB[1]]; + b3Vec3 localEdgeB = b3Normalize( b3Sub( vB2, vB1 ) ); + b3Vec3 edgeB = b3RotateVector( qB, localEdgeB ); + + b3Vec3 axis = b3Cross( edgeA, edgeB ); + float lengthSquared = b3LengthSquared( axis ); + + // Skip near parallel edges: |e1 x e1| = sin(alpha) * |e1| * |e2| + const float kToleranceSquared = 0.005f * 0.005f; + if ( lengthSquared < kToleranceSquared ) + { + // The axis is not safe to normalize so we use a world axis instead! + fcn.type = b3_separationVertices; + fcn.witness1 = worldNormal; + } + else + { + b3Vec3 delta = b3Add( b3Sub( b3RotateVector( qB, vB1 ), b3RotateVector( qA, vA1 ) ), deltaP ); + if ( b3Dot( delta, axis ) < 0.0f ) + { + // Make axis point from A to B + axis = b3Neg( axis ); + localEdgeB = b3Neg( localEdgeB ); + } + + // Check for possible sign flip in edge/edge cross product + b3Transform xfA2 = b3GetFinalSweepTransform( sweepA ); + b3Transform xfB2 = b3GetFinalSweepTransform( sweepB ); + bool fastEdges = b3CheckFastEdges( xfA2, localEdgeA, xfB2, localEdgeB, axis ); + if ( fastEdges ) + { + // Not safe to use local edges, fall back to initial world space axis instead + fcn.type = b3_separationVertices; + fcn.witness1 = b3Normalize( axis ); + } + else + { + // Edge cross product is safe. This converges faster than a fixed axis. + fcn.type = b3_separationEdges; + fcn.witness1 = localEdgeA; + fcn.witness2 = localEdgeB; + } + } + } + } + break; + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + return fcn; +} + +static float b3FindMinSeparation( b3SeparationFunction* fcn, int* indexA, int* indexB, float t ) +{ + b3Transform xfA = b3GetSweepTransform( &fcn->sweepA, t ); + b3Transform xfB = b3GetSweepTransform( &fcn->sweepB, t ); + + switch ( fcn->type ) + { + case b3_separationVertices: + { + b3Vec3 axis = fcn->witness1; + + b3Vec3 localAxisA = b3InvRotateVector( xfA.q, axis ); + b3Vec3 localAxisB = b3InvRotateVector( xfB.q, b3Neg( axis ) ); + + *indexA = b3GetPointSupport( fcn->proxyA->points, fcn->proxyA->count, localAxisA ); + *indexB = b3GetPointSupport( fcn->proxyB->points, fcn->proxyB->count, localAxisB ); + + b3Vec3 deltaP = b3Sub( xfB.p, xfA.p ); + b3Vec3 localPointA = fcn->proxyA->points[*indexA]; + b3Vec3 localPointB = fcn->proxyB->points[*indexB]; + b3Vec3 delta = b3Add( b3Sub( b3RotateVector( xfB.q, localPointB ), b3RotateVector( xfA.q, localPointA ) ), deltaP ); + return b3Dot( delta, axis ); + } + + case b3_separationEdges: + { + b3Vec3 edgeA = b3RotateVector( xfA.q, fcn->witness1 ); + b3Vec3 edgeB = b3RotateVector( xfB.q, fcn->witness2 ); + b3Vec3 axis = b3Cross( edgeA, edgeB ); + B3_ASSERT( axis.x != 0.0f || axis.y != 0.0f || axis.z != 0.0f ); + axis = b3Normalize( axis ); + + b3Vec3 axisA = b3InvRotateVector( xfA.q, axis ); + *indexA = b3GetPointSupport( fcn->proxyA->points, fcn->proxyA->count, axisA ); + + b3Vec3 axisB = b3InvRotateVector( xfB.q, axis ); + *indexB = b3GetPointSupport( fcn->proxyB->points, fcn->proxyB->count, b3Neg( axisB ) ); + + b3Vec3 deltaP = b3Sub( xfB.p, xfA.p ); + b3Vec3 localPointA = fcn->proxyA->points[*indexA]; + b3Vec3 localPointB = fcn->proxyB->points[*indexB]; + b3Vec3 delta = b3Add( b3Sub( b3RotateVector( xfB.q, localPointB ), b3RotateVector( xfA.q, localPointA ) ), deltaP ); + + return b3Dot( delta, axis ); + } + + case b3_separationFaceA: + { + b3Vec3 normal = b3RotateVector( xfA.q, fcn->witness1 ); + *indexA = -1; + b3Vec3 pointA = b3TransformPoint( xfA, fcn->witness2 ); + + b3Vec3 axisB = b3InvRotateVector( xfB.q, normal ); + *indexB = b3GetPointSupport( fcn->proxyB->points, fcn->proxyB->count, b3Neg( axisB ) ); + b3Vec3 pointB = b3TransformPoint( xfB, fcn->proxyB->points[*indexB] ); + + return b3Dot( b3Sub( pointB, pointA ), normal ); + } + + case b3_separationFaceB: + { + b3Vec3 normal = b3RotateVector( xfB.q, fcn->witness1 ); + + b3Vec3 axisA = b3InvRotateVector( xfA.q, normal ); + *indexA = b3GetPointSupport( fcn->proxyA->points, fcn->proxyA->count, b3Neg( axisA ) ); + b3Vec3 pointA = b3TransformPoint( xfA, fcn->proxyA->points[*indexA] ); + + *indexB = -1; + b3Vec3 pointB = b3TransformPoint( xfB, fcn->witness2 ); + + return b3Dot( b3Sub( pointA, pointB ), normal ); + } + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + return 0.0f; +} + +static float b3EvaluateSeparation( b3SeparationFunction* fcn, int index1, int index2, float beta ) +{ + b3Transform transform1 = b3GetSweepTransform( &fcn->sweepA, beta ); + b3Transform transform2 = b3GetSweepTransform( &fcn->sweepB, beta ); + + switch ( fcn->type ) + { + case b3_separationVertices: + { + b3Vec3 axis = fcn->witness1; + + b3Vec3 point1 = b3TransformPoint( transform1, fcn->proxyA->points[index1] ); + b3Vec3 point2 = b3TransformPoint( transform2, fcn->proxyB->points[index2] ); + + return b3Dot( b3Sub( point2, point1 ), axis ); + } + + case b3_separationEdges: + { + b3Vec3 edge1 = b3RotateVector( transform1.q, fcn->witness1 ); + b3Vec3 edge2 = b3RotateVector( transform2.q, fcn->witness2 ); + b3Vec3 axis = b3Cross( edge1, edge2 ); + axis = b3Normalize( axis ); + + b3Vec3 point1 = b3TransformPoint( transform1, fcn->proxyA->points[index1] ); + b3Vec3 point2 = b3TransformPoint( transform2, fcn->proxyB->points[index2] ); + + return b3Dot( b3Sub( point2, point1 ), axis ); + } + + case b3_separationFaceA: + { + b3Vec3 axis = b3RotateVector( transform1.q, fcn->witness1 ); + + b3Vec3 point1 = b3TransformPoint( transform1, fcn->witness2 ); + b3Vec3 point2 = b3TransformPoint( transform2, fcn->proxyB->points[index2] ); + + return b3Dot( b3Sub( point2, point1 ), axis ); + } + + case b3_separationFaceB: + { + b3Vec3 axis = b3RotateVector( transform2.q, fcn->witness1 ); + + b3Vec3 point1 = b3TransformPoint( transform1, fcn->proxyA->points[index1] ); + b3Vec3 point2 = b3TransformPoint( transform2, fcn->witness2 ); + + return b3Dot( b3Sub( point1, point2 ), axis ); + } + + default: + B3_ASSERT( !"Should never get here!" ); + break; + } + + return 0.0f; +} + +static void b3ForceFixedAxis( b3SeparationFunction* fcn, float beta ) +{ + B3_ASSERT( fcn->type == b3_separationEdges ); + + b3Transform transform1 = b3GetSweepTransform( &fcn->sweepA, beta ); + b3Transform transform2 = b3GetSweepTransform( &fcn->sweepB, beta ); + + b3Vec3 edge1 = b3RotateVector( transform1.q, fcn->witness1 ); + b3Vec3 edge2 = b3RotateVector( transform2.q, fcn->witness2 ); + b3Vec3 axis = b3Cross( edge1, edge2 ); + axis = b3Normalize( axis ); + + fcn->type = b3_separationVertices; + fcn->witness1 = axis; + fcn->witness2 = b3Vec3_zero; +} + +// Time of Impact using root finding +b3TOIOutput b3TimeOfImpact( const b3TOIInput* input ) +{ + b3TOIOutput output = { 0 }; + + // Set these to invalid values so they can be validated on exit + output.state = b3_toiStateUnknown; + output.fraction = -1.0f; + + b3Sweep sweepA = input->sweepA; + b3Sweep sweepB = input->sweepB; + + // Shift to origin + b3Vec3 origin = sweepA.c1; + sweepA.c1 = b3Vec3_zero; + sweepA.c2 = b3Sub( sweepA.c2, origin ); + sweepB.c1 = b3Sub( sweepB.c1, origin ); + sweepB.c2 = b3Sub( sweepB.c2, origin ); + + b3ShapeProxy proxyA = input->proxyA; + b3ShapeProxy proxyB = input->proxyB; + + int maxPushBackIterations = proxyA.count + proxyB.count; + float tMax = input->maxFraction; + + // Setup target distance and tolerance + float linearSlop = B3_LINEAR_SLOP; + float totalRadius = proxyA.radius + proxyB.radius; + float target = b3MaxFloat( linearSlop, totalRadius - linearSlop ); + float tolerance = 0.25f * linearSlop; + B3_ASSERT( target > tolerance ); + + float t1 = 0.0f; + const int maxIterations = 25; + int distanceIterations = 0; + + // Prepare input for distance query. + b3SimplexCache cache = { 0 }; + b3DistanceInput distanceInput = { 0 }; + distanceInput.proxyA = proxyA; + distanceInput.proxyB = proxyB; + distanceInput.useRadii = false; + + // The outer loop progressively attempts to compute new separating axes. + // This loop terminates when an axis is repeated (no progress is made). + for ( ;; ) + { + // Get the distance between shapes. We can also use the results to get a separating axis. + b3Transform xfA = b3GetSweepTransform( &sweepA, t1 ); + b3Transform xfB = b3GetSweepTransform( &sweepB, t1 ); + distanceInput.transform = b3InvMulTransforms( xfA, xfB ); + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, &cache, NULL, 0 ); + output.distance = distanceOutput.distance; + + // The distance query runs in frame A, project the witness data back to the shifted world + b3Vec3 worldNormal = b3RotateVector( xfA.q, distanceOutput.normal ); + b3Vec3 worldPointA = b3TransformPoint( xfA, distanceOutput.pointA ); + b3Vec3 worldPointB = b3TransformPoint( xfA, distanceOutput.pointB ); + + output.distanceIterations += 1; + distanceIterations += 1; + + // If the shapes are overlapped, we give up on continuous collision. + if ( distanceOutput.distance <= 0.0f ) + { + output.state = b3_toiStateOverlapped; + output.fraction = 0.0f; + break; + } + + if ( distanceOutput.distance <= target + tolerance ) + { + // Success! + output.state = b3_toiStateHit; + + // Averaged hit point + b3Vec3 pA = b3MulAdd( worldPointA, proxyA.radius, worldNormal ); + b3Vec3 pB = b3MulAdd( worldPointB, -proxyB.radius, worldNormal ); + output.point = b3Lerp( pA, pB, 0.5f ); + output.point = b3Add( output.point, origin ); + output.normal = worldNormal; + output.fraction = t1; + break; + } + + if ( distanceIterations == maxIterations ) + { + // Progress too slow. This can happen when a capsule rotates around a + // triangle vertex. + output.state = b3_toiStateFailed; + output.fraction = t1; + + // Averaged hit point + b3Vec3 pA = b3MulAdd( worldPointA, input->proxyA.radius, worldNormal ); + b3Vec3 pB = b3MulAdd( worldPointB, -input->proxyB.radius, worldNormal ); + output.point = b3Lerp( pA, pB, 0.5f ); + output.point = b3Add( output.point, origin ); + output.normal = worldNormal; + break; + } + + // Initialize the separating axis. + b3SeparationFunction function = + b3MakeSeparationFunction( cache, &proxyA, &sweepA, &proxyB, &sweepB, worldNormal, t1 ); + +#if B3_ENABLE_VALIDATION && 0 + // todo this can give a negative value for diagonal edge contact on faces, typical GJK problem + // to fix this I think the separation function would need to identify faces + { + int index1, index2; + float minSeparation = b3FindMinSeparation( &function, &index1, &index2, t1 ); + // SAT should give a closer result than GJK + B3_VALIDATE( minSeparation > target - tolerance && minSeparation - distanceOutput.distance < 0.1f * B3_LINEAR_SLOP ); + } +#endif + + // Compute the TOI on the separating axis. We do this by successively resolving the deepest point. + bool done = false; + float t2 = tMax; + int pushBackIterations = 0; + for ( ;; ) + { + int indexA, indexB; + float s2 = b3FindMinSeparation( &function, &indexA, &indexB, t2 ); + + // Dump the function seen by the root finder + // for ( int Step = 0; Step <= 100; ++Step ) + // { + // float Alpha = 0.01f * Step; + // float Separation = Function.Evaluate( Index1, Index2, Alpha ); + // + // b3Report( "s(%4.2g) = %g\n", Alpha, Separation ); + // } + + // Is the final configuration separated? + if ( s2 - target > tolerance ) + { + // Success! + output.state = b3_toiStateSeparated; + output.fraction = input->maxFraction; + done = true; + break; + } + + // Has the separation reached tolerance? + if ( s2 >= target - tolerance ) + { + // Advance the sweeps + t1 = t2; + break; + } + + // Compute the initial separation of the witness points + float s1 = b3EvaluateSeparation( &function, indexA, indexB, t1 ); + + // Check for overlap. This might happen if the root finder runs out of iterations. + if ( s1 < target - tolerance ) + { + // Failed! + B3_VALIDATE( false ); + output.state = b3_toiStateFailed; + output.fraction = t1; + done = true; + break; + } + + // Has the separation reached tolerance? + if ( s1 <= target + tolerance ) + { + // Success! t1 should hold the TOI (could be 0.0) + output.state = b3_toiStateHit; + output.fraction = t1; + done = true; + break; + } + + // Compute 1D root of: f(x) - target = 0 + int rootIterationCount = 0; + int maxRootIterations = 50; + float a1 = t1; + float a2 = t2; + for ( ;; ) + { + // Use a mix of false position and bisection. + float t; + if ( rootIterationCount & 1 ) + { + // False position to improve convergence. + t = a1 + ( target - s1 ) * ( a2 - a1 ) / ( s2 - s1 ); + } + else + { + // Bisection to guarantee progress. + t = 0.5f * ( a1 + a2 ); + } + + output.rootIterations += 1; + rootIterationCount += 1; + + float s = b3EvaluateSeparation( &function, indexA, indexB, t ); + + // Has the separation reached tolerance? + if ( b3AbsFloat( s - target ) <= tolerance ) + { + // t2 holds a tentative value for t1 + t2 = t; + break; + } + + // Ensure we continue to bracket the root. + if ( s > target ) + { + a1 = t; + s1 = s; + } + else + { + a2 = t; + s2 = s; + } + + if ( rootIterationCount == maxRootIterations ) + { + B3_VALIDATE( false ); + break; + } + } + + // Restart the inner loop if we have a failing edge case. + if ( rootIterationCount == maxRootIterations - 1 && function.type == b3_separationEdges ) + { + B3_VALIDATE( false ); + + rootIterationCount = 0; + t2 = input->maxFraction; + b3ForceFixedAxis( &function, t1 ); + B3_ASSERT( function.type != b3_separationEdges ); + } + + output.pushBackIterations += 1; + pushBackIterations += 1; + + if ( pushBackIterations == maxPushBackIterations ) + { + break; + } + } + + if ( done ) + { + // Averaged hit point + b3Vec3 pA = b3MulAdd( worldPointA, input->proxyA.radius, worldNormal ); + b3Vec3 pB = b3MulAdd( worldPointB, -input->proxyB.radius, worldNormal ); + output.point = b3Lerp( pA, pB, 0.5f ); + output.point = b3Add( output.point, origin ); + output.normal = worldNormal; + break; + } + } + + // It is expected that the state and fraction are set before reaching this + B3_ASSERT( output.state != b3_toiStateUnknown ); + B3_ASSERT( output.fraction >= 0.0f ); + + return output; +} diff --git a/vendor/box3d/src/src/distance_joint.c b/vendor/box3d/src/src/distance_joint.c new file mode 100644 index 000000000..9185010ae --- /dev/null +++ b/vendor/box3d/src/src/distance_joint.c @@ -0,0 +1,579 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "core.h" +#include "joint.h" +#include "physics_world.h" +#include "recording.h" +#include "solver.h" +#include "solver_set.h" + +// needed for dll export +#include "box3d/box3d.h" + +void b3DistanceJoint_SetLength( b3JointId jointId, float length ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetLength, jointId, length ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + + joint->length = b3ClampFloat( length, B3_LINEAR_SLOP, B3_HUGE ); + joint->impulse = 0.0f; + joint->lowerImpulse = 0.0f; + joint->upperImpulse = 0.0f; +} + +float b3DistanceJoint_GetLength( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + return joint->length; +} + +void b3DistanceJoint_EnableLimit( b3JointId jointId, bool enableLimit ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointEnableLimit, jointId, enableLimit ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + joint->enableLimit = enableLimit; +} + +bool b3DistanceJoint_IsLimitEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + return joint->distanceJoint.enableLimit; +} + +void b3DistanceJoint_SetLengthRange( b3JointId jointId, float minLength, float maxLength ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetLengthRange, jointId, minLength, maxLength ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + + minLength = b3ClampFloat( minLength, B3_LINEAR_SLOP, B3_HUGE ); + maxLength = b3ClampFloat( maxLength, B3_LINEAR_SLOP, B3_HUGE ); + joint->minLength = b3MinFloat( minLength, maxLength ); + joint->maxLength = b3MaxFloat( minLength, maxLength ); + joint->impulse = 0.0f; + joint->lowerImpulse = 0.0f; + joint->upperImpulse = 0.0f; +} + +float b3DistanceJoint_GetMinLength( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + return joint->minLength; +} + +float b3DistanceJoint_GetMaxLength( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + return joint->maxLength; +} + +float b3DistanceJoint_GetCurrentLength( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + + b3World* world = b3GetUnlockedWorld( jointId.world0 ); + if ( world == NULL ) + { + return 0.0f; + } + + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB ); + + b3Pos pA = b3TransformWorldPoint( transformA, base->localFrameA.p ); + b3Pos pB = b3TransformWorldPoint( transformB, base->localFrameB.p ); + b3Vec3 d = b3SubPos( pB, pA ); + float length = b3Length( d ); + return length; +} + +void b3DistanceJoint_EnableSpring( b3JointId jointId, bool enableSpring ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointEnableSpring, jointId, enableSpring ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + base->distanceJoint.enableSpring = enableSpring; +} + +bool b3DistanceJoint_IsSpringEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + return base->distanceJoint.enableSpring; +} + + +void b3DistanceJoint_SetSpringForceRange( b3JointId jointId, float lowerForce, float upperForce ) +{ + B3_ASSERT( lowerForce <= upperForce ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetSpringForceRange, jointId, lowerForce, upperForce ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + base->distanceJoint.lowerSpringForce = lowerForce; + base->distanceJoint.upperSpringForce = upperForce; +} + +void b3DistanceJoint_GetSpringForceRange( b3JointId jointId, float* lowerForce, float* upperForce ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + *lowerForce = base->distanceJoint.lowerSpringForce; + *upperForce = base->distanceJoint.upperSpringForce; +} + +void b3DistanceJoint_SetSpringHertz( b3JointId jointId, float hertz ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetSpringHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + base->distanceJoint.hertz = hertz; +} + +void b3DistanceJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetSpringDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + base->distanceJoint.dampingRatio = dampingRatio; +} + +float b3DistanceJoint_GetSpringHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + return joint->hertz; +} + +float b3DistanceJoint_GetSpringDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + b3DistanceJoint* joint = &base->distanceJoint; + return joint->dampingRatio; +} + +void b3DistanceJoint_EnableMotor( b3JointId jointId, bool enableMotor ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointEnableMotor, jointId, enableMotor ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + if ( enableMotor != joint->distanceJoint.enableMotor ) + { + joint->distanceJoint.enableMotor = enableMotor; + joint->distanceJoint.motorImpulse = 0.0f; + } +} + +bool b3DistanceJoint_IsMotorEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + return joint->distanceJoint.enableMotor; +} + +void b3DistanceJoint_SetMotorSpeed( b3JointId jointId, float motorSpeed ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetMotorSpeed, jointId, motorSpeed ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + joint->distanceJoint.motorSpeed = motorSpeed; +} + +float b3DistanceJoint_GetMotorSpeed( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + return joint->distanceJoint.motorSpeed; +} + +float b3DistanceJoint_GetMotorForce( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + return world->inv_h * base->distanceJoint.motorImpulse; +} + +void b3DistanceJoint_SetMaxMotorForce( b3JointId jointId, float force ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, DistanceJointSetMaxMotorForce, jointId, force ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + joint->distanceJoint.maxMotorForce = force; +} + +float b3DistanceJoint_GetMaxMotorForce( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_distanceJoint ); + return joint->distanceJoint.maxMotorForce; +} + +b3Vec3 b3GetDistanceJointForce( b3World* world, b3JointSim* base ) +{ + b3DistanceJoint* joint = &base->distanceJoint; + + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB ); + + b3Pos pA = b3TransformWorldPoint( transformA, base->localFrameA.p ); + b3Pos pB = b3TransformWorldPoint( transformB, base->localFrameB.p ); + b3Vec3 d = b3SubPos( pB, pA ); + b3Vec3 axis = b3Normalize( d ); + float force = ( joint->impulse + joint->lowerImpulse - joint->upperImpulse + joint->motorImpulse ) * world->inv_h; + return b3MulSV( force, axis ); +} + +// 1-D constrained system +// m (v2 - v1) = lambda +// v2 + (beta/h) * x1 + gamma * lambda = 0, gamma has units of inverse mass. +// x2 = x1 + h * v2 + +// 1-D mass-damper-spring system +// m (v2 - v1) + h * d * v2 + h * k * + +// C = norm(p2 - p1) - L +// u = (p2 - p1) / norm(p2 - p1) +// Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1)) +// J = [-u -cross(r1, u) u cross(r2, u)] +// K = J * invM * JT +// = invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2 + +void b3PrepareDistanceJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_distanceJoint ); + + // chase body id to the solver set where the body lives + int idA = base->bodyIdA; + int idB = base->bodyIdB; + + b3World* world = context->world; + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ); + + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + float mA = bodySimA->invMass; + b3Matrix3 iA = bodySimA->invInertiaWorld; + float mB = bodySimB->invMass; + b3Matrix3 iB = bodySimB->invInertiaWorld; + + base->invMassA = mA; + base->invMassB = mB; + base->invIA = iA; + base->invIB = iB; + + b3DistanceJoint* joint = &base->distanceJoint; + + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // initial anchors in world space + joint->anchorA = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->anchorB = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + + b3Vec3 rA = joint->anchorA; + b3Vec3 rB = joint->anchorB; + b3Vec3 separation = b3Add( b3Sub( rB, rA ), joint->deltaCenter ); + b3Vec3 axis = b3Normalize( separation ); + + // compute effective mass + b3Vec3 crA = b3Cross( rA, axis ); + b3Vec3 crB = b3Cross( rB, axis ); + float k = mA + mB + b3Dot( crA, b3MulMV( iA, crA ) ) + b3Dot( crB, b3MulMV( iB, crB ) ); + joint->axialMass = k > 0.0f ? 1.0f / k : 0.0f; + + joint->distanceSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h ); + + if ( context->enableWarmStarting == false ) + { + joint->impulse = 0.0f; + joint->lowerImpulse = 0.0f; + joint->upperImpulse = 0.0f; + joint->motorImpulse = 0.0f; + } +} + +void b3WarmStartDistanceJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_distanceJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3DistanceJoint* joint = &base->distanceJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->anchorA ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->anchorB ); + + b3Vec3 ds = b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), b3Sub( rB, rA ) ); + b3Vec3 separation = b3Add( joint->deltaCenter, ds ); + b3Vec3 axis = b3Normalize( separation ); + + float axialImpulse = joint->impulse + joint->lowerImpulse - joint->upperImpulse + joint->motorImpulse; + b3Vec3 P = b3MulSV( axialImpulse, axis ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = b3MulSub( stateA->linearVelocity, mA, P ); + stateA->angularVelocity = b3Sub( stateA->angularVelocity, b3MulMV( iA, b3Cross( rA, P ) ) ); + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = b3MulAdd( stateB->linearVelocity, mB, P ); + stateB->angularVelocity = b3Add( stateB->angularVelocity, b3MulMV( iB, b3Cross( rB, P ) ) ); + } +} + +void b3SolveDistanceJoint( b3JointSim* base, b3StepContext* context, bool useBias ) +{ + B3_ASSERT( base->type == b3_distanceJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3DistanceJoint* joint = &base->distanceJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + // current anchors + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->anchorA ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->anchorB ); + + // current separation + b3Vec3 ds = b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), b3Sub( rB, rA ) ); + b3Vec3 separation = b3Add( joint->deltaCenter, ds ); + + float length = b3Length( separation ); + b3Vec3 axis = b3Normalize( separation ); + + // joint is soft if + // - spring is enabled + // - and (joint limit is disabled or limits are not equal) + if ( joint->enableSpring && ( joint->minLength < joint->maxLength || joint->enableLimit == false ) ) + { + // spring + if ( joint->hertz > 0.0f ) + { + // Cdot = dot(u, v + cross(w, r)) + b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) ); + float Cdot = b3Dot( axis, vr ); + float C = length - joint->length; + float bias = joint->distanceSoftness.biasRate * C; + + float m = joint->distanceSoftness.massScale * joint->axialMass; + float oldImpulse = joint->impulse; + float impulse = -m * ( Cdot + bias ) - joint->distanceSoftness.impulseScale * oldImpulse; + float h = context->h; + joint->impulse = b3ClampFloat( joint->impulse + impulse, joint->lowerSpringForce * h, joint->upperSpringForce * h ); + impulse = joint->impulse - oldImpulse; + + b3Vec3 P = b3MulSV( impulse, axis ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + + if ( joint->enableLimit ) + { + // lower limit + { + b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) ); + float Cdot = b3Dot( axis, vr ); + + float C = length - joint->minLength; + + float bias = 0.0f; + float massCoeff = 1.0f; + float impulseCoeff = 0.0f; + if ( C > 0.0f ) + { + // speculative + bias = C * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * C; + massCoeff = base->constraintSoftness.massScale; + impulseCoeff = base->constraintSoftness.impulseScale; + } + + float impulse = -massCoeff * joint->axialMass * ( Cdot + bias ) - impulseCoeff * joint->lowerImpulse; + float newImpulse = b3MaxFloat( 0.0f, joint->lowerImpulse + impulse ); + impulse = newImpulse - joint->lowerImpulse; + joint->lowerImpulse = newImpulse; + + b3Vec3 P = b3MulSV( impulse, axis ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + + // upper + { + b3Vec3 vr = b3Add( b3Sub( vA, vB ), b3Sub( b3Cross( wA, rA ), b3Cross( wB, rB ) ) ); + float Cdot = b3Dot( axis, vr ); + + float C = joint->maxLength - length; + + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( C > 0.0f ) + { + // speculative + bias = C * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * C; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * joint->upperImpulse; + float newImpulse = b3MaxFloat( 0.0f, joint->upperImpulse + impulse ); + impulse = newImpulse - joint->upperImpulse; + joint->upperImpulse = newImpulse; + + b3Vec3 P = b3MulSV( -impulse, axis ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + } + + if ( joint->enableMotor ) + { + b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) ); + float Cdot = b3Dot( axis, vr ); + float impulse = joint->axialMass * ( joint->motorSpeed - Cdot ); + float oldImpulse = joint->motorImpulse; + float maxImpulse = context->h * joint->maxMotorForce; + joint->motorImpulse = b3ClampFloat( joint->motorImpulse + impulse, -maxImpulse, maxImpulse ); + impulse = joint->motorImpulse - oldImpulse; + + b3Vec3 P = b3MulSV( impulse, axis ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + } + else + { + // rigid constraint + b3Vec3 vr = b3Add( b3Sub( vB, vA ), b3Sub( b3Cross( wB, rB ), b3Cross( wA, rA ) ) ); + float Cdot = b3Dot( axis, vr ); + + float C = length - joint->length; + + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias ) + { + bias = base->constraintSoftness.biasRate * C; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + float impulse = -massScale * joint->axialMass * ( Cdot + bias ) - impulseScale * joint->impulse; + joint->impulse += impulse; + + b3Vec3 P = b3MulSV( impulse, axis ); + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, P ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, P ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3DrawDistanceJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB ) +{ + B3_ASSERT( base->type == b3_distanceJoint ); + + b3DistanceJoint* joint = &base->distanceJoint; + + b3Pos pA = b3TransformWorldPoint( transformA, base->localFrameA.p ); + b3Pos pB = b3TransformWorldPoint( transformB, base->localFrameB.p ); + + b3Vec3 axis = b3Normalize( b3SubPos( pB, pA ) ); + + if ( joint->minLength < joint->maxLength && joint->enableLimit ) + { + b3Pos pMin = b3OffsetPos( pA, b3MulSV( joint->minLength, axis ) ); + b3Pos pMax = b3OffsetPos( pA, b3MulSV( joint->maxLength, axis ) ); + + if ( joint->minLength > B3_LINEAR_SLOP ) + { + draw->DrawPointFcn( pMin, 6.0f, b3_colorLightGreen, draw->context ); + } + + if ( joint->maxLength < B3_HUGE ) + { + draw->DrawPointFcn(pMax, 6.0f, b3_colorRed, draw->context); + } + + if ( joint->minLength > B3_LINEAR_SLOP && joint->maxLength < B3_HUGE ) + { + draw->DrawSegmentFcn( pMin, pMax, b3_colorGray, draw->context ); + } + } + + draw->DrawSegmentFcn( pA, pB, b3_colorWhite, draw->context ); + draw->DrawPointFcn( pA, 4.0f, b3_colorWhite, draw->context ); + draw->DrawPointFcn( pB, 4.0f, b3_colorWhite, draw->context ); + + if ( joint->hertz > 0.0f && joint->enableSpring ) + { + b3Pos pRest = b3OffsetPos( pA, b3MulSV( joint->length, axis ) ); + draw->DrawPointFcn( pRest, 4.0f, b3_colorBlue, draw->context ); + } +} diff --git a/vendor/box3d/src/src/dynamic_tree.c b/vendor/box3d/src/src/dynamic_tree.c new file mode 100644 index 000000000..98370dc05 --- /dev/null +++ b/vendor/box3d/src/src/dynamic_tree.c @@ -0,0 +1,2186 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "aabb.h" +#include "core.h" +#include "joint.h" +#include "simd.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" +#include "box3d/math_functions.h" + +#include +#include +#include + +#define B3_TREE_STACK_SIZE 1024 + +static b3TreeNode b3_defaultTreeNode = { + .aabb = { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }, + .categoryBits = B3_DEFAULT_CATEGORY_BITS, + .children = + { + .child1 = B3_NULL_INDEX, + .child2 = B3_NULL_INDEX, + }, + .parent = B3_NULL_INDEX, + .height = 0, + .flags = b3_allocatedNode, +}; + +static inline bool b3IsLeaf( const b3TreeNode* node ) +{ + return node->flags & b3_leafNode; +} + +static inline bool b3IsAllocated( const b3TreeNode* node ) +{ + return node->flags & b3_allocatedNode; +} + +static inline uint16_t b3MaxUInt16( uint16_t a, uint16_t b ) +{ + return a > b ? a : b; +} + +b3DynamicTree b3DynamicTree_Create( int proxyCapacity ) +{ + int capacity = b3MaxInt( proxyCapacity, 16 ); + + b3DynamicTree tree; + + // memset needed for deterministic serialization + memset( &tree, 0, sizeof( b3DynamicTree ) ); + + tree.version = B3_DYNAMIC_TREE_VERSION; + tree.root = B3_NULL_INDEX; + + // maximum node count for a full binary tree is 2 * leafCount - 1 + tree.nodeCapacity = 2 * capacity - 1; + tree.nodeCount = 0; + + tree.nodes = (b3TreeNode*)b3Alloc( tree.nodeCapacity * sizeof( b3TreeNode ) ); + + memset( tree.nodes, 0, tree.nodeCapacity * sizeof( b3TreeNode ) ); + + // Build a linked list for the free list. + // todo use a bump allocator until the capacity is consumed (see b3PoolAllocator) + for ( int i = 0; i < tree.nodeCapacity - 1; ++i ) + { + tree.nodes[i].next = i + 1; + } + + tree.nodes[tree.nodeCapacity - 1].next = B3_NULL_INDEX; + tree.freeList = 0; + + tree.proxyCount = 0; + + tree.leafIndices = NULL; + tree.leafBoxes = NULL; + tree.leafCenters = NULL; + tree.binIndices = NULL; + tree.rebuildCapacity = 0; + + return tree; +} + +void b3DynamicTree_Destroy( b3DynamicTree* tree ) +{ + b3Free( tree->nodes, tree->nodeCapacity * sizeof( b3TreeNode ) ); + b3Free( tree->leafIndices, tree->rebuildCapacity * sizeof( int ) ); + b3Free( tree->leafBoxes, tree->rebuildCapacity * sizeof( b3AABB ) ); + b3Free( tree->leafCenters, tree->rebuildCapacity * sizeof( b3Vec3 ) ); + b3Free( tree->binIndices, tree->rebuildCapacity * sizeof( int ) ); + + memset( tree, 0, sizeof( b3DynamicTree ) ); +} + +// Allocate a node from the pool. Grow the pool if necessary. +static int b3AllocateNode( b3DynamicTree* tree ) +{ + // Expand the node pool as needed. + if ( tree->freeList == B3_NULL_INDEX ) + { + B3_ASSERT( tree->nodeCount == tree->nodeCapacity ); + + // The free list is empty. Rebuild a bigger pool. + b3TreeNode* oldNodes = tree->nodes; + int oldCapacity = tree->nodeCapacity; + tree->nodeCapacity += oldCapacity >> 1; + tree->nodes = (b3TreeNode*)b3Alloc( tree->nodeCapacity * sizeof( b3TreeNode ) ); + B3_ASSERT( oldNodes != NULL ); + memcpy( tree->nodes, oldNodes, tree->nodeCount * sizeof( b3TreeNode ) ); + memset( tree->nodes + tree->nodeCount, 0, ( tree->nodeCapacity - tree->nodeCount ) * sizeof( b3TreeNode ) ); + b3Free( oldNodes, oldCapacity * sizeof( b3TreeNode ) ); + + // Build a linked list for the free list. The parent pointer becomes the "next" pointer. + // todo avoid building freelist? + for ( int i = tree->nodeCount; i < tree->nodeCapacity - 1; ++i ) + { + tree->nodes[i].next = i + 1; + } + + tree->nodes[tree->nodeCapacity - 1].next = B3_NULL_INDEX; + tree->freeList = tree->nodeCount; + } + + // Peel a node off the free list. + int nodeIndex = tree->freeList; + b3TreeNode* node = tree->nodes + nodeIndex; + tree->freeList = node->next; + *node = b3_defaultTreeNode; + ++tree->nodeCount; + return nodeIndex; +} + +// Return a node to the pool. +static void b3FreeNode( b3DynamicTree* tree, int nodeId ) +{ + B3_ASSERT( 0 <= nodeId && nodeId < tree->nodeCapacity ); + B3_ASSERT( 0 < tree->nodeCount ); + tree->nodes[nodeId].next = tree->freeList; + tree->nodes[nodeId].flags = 0; + tree->freeList = nodeId; + --tree->nodeCount; +} + +// Greedy algorithm for sibling selection using the SAH +// We have three nodes A-(B,C) and want to add a leaf D, there are three choices. +// 1: make a new parent for A and D : E-(A-(B,C), D) +// 2: associate D with B +// a: B is a leaf : A-(E-(B,D), C) +// b: B is an internal node: A-(B{D},C) +// 3: associate D with C +// a: C is a leaf : A-(B, E-(C,D)) +// b: C is an internal node: A-(B, C{D}) +// All of these have a clear cost except when B or C is an internal node. Hence we need to be greedy. + +// The cost for cases 1, 2a, and 3a can be computed using the sibling cost formula. +// cost of sibling H = area(union(H, D)) + increased area of ancestors + +// Suppose B (or C) is an internal node, then the lowest cost would be one of two cases: +// case1: D becomes a sibling of B +// case2: D becomes a descendant of B along with a new internal node of area(D). +static int b3FindBestSibling( const b3DynamicTree* tree, b3AABB boxD ) +{ + b3Vec3 centerD = b3AABB_Center( boxD ); + float areaD = b3Perimeter( boxD ); + + const b3TreeNode* nodes = tree->nodes; + int rootIndex = tree->root; + + b3AABB rootBox = nodes[rootIndex].aabb; + + // Area of current node + float areaBase = b3Perimeter( rootBox ); + + // Area of inflated node + float directCost = b3Perimeter( b3AABB_Union( rootBox, boxD ) ); + float inheritedCost = 0.0f; + + int bestSibling = rootIndex; + float bestCost = directCost; + + // Descend the tree from root, following a single greedy path. + int index = rootIndex; + while ( b3IsLeaf( nodes + index ) == false ) + { + int child1 = nodes[index].children.child1; + int child2 = nodes[index].children.child2; + + // Cost of creating a new parent for this node and the new leaf + float cost = directCost + inheritedCost; + + // Sometimes there are multiple identical costs within tolerance. + // This breaks the ties using the centroid distance. + if ( cost < bestCost ) + { + bestSibling = index; + bestCost = cost; + } + + // Inheritance cost seen by children + inheritedCost += directCost - areaBase; + + bool leaf1 = b3IsLeaf( nodes + child1 ); + bool leaf2 = b3IsLeaf( nodes + child2 ); + + // Cost of descending into child 1 + float lowerCost1 = FLT_MAX; + b3AABB box1 = nodes[child1].aabb; + float directCost1 = b3Perimeter( b3AABB_Union( box1, boxD ) ); + float area1 = 0.0f; + if ( leaf1 ) + { + // Child 1 is a leaf + // Cost of creating new node and increasing area of node P + float cost1 = directCost1 + inheritedCost; + + // Need this here due to while condition above + if ( cost1 < bestCost ) + { + bestSibling = child1; + bestCost = cost1; + } + } + else + { + // Child 1 is an internal node + area1 = b3Perimeter( box1 ); + + // Lower bound cost of inserting under child 1. + lowerCost1 = inheritedCost + directCost1 + b3MinFloat( areaD - area1, 0.0f ); + } + + // Cost of descending into child 2 + float lowerCost2 = FLT_MAX; + b3AABB box2 = nodes[child2].aabb; + float directCost2 = b3Perimeter( b3AABB_Union( box2, boxD ) ); + float area2 = 0.0f; + if ( leaf2 ) + { + // Child 2 is a leaf + // Cost of creating new node and increasing area of node P + float cost2 = directCost2 + inheritedCost; + + // Need this here due to while condition above + if ( cost2 < bestCost ) + { + bestSibling = child2; + bestCost = cost2; + } + } + else + { + // Child 2 is an internal node + area2 = b3Perimeter( box2 ); + + // Lower bound cost of inserting under child 2. This is not the cost + // of child 2, it is the best we can hope for under child 2. + lowerCost2 = inheritedCost + directCost2 + b3MinFloat( areaD - area2, 0.0f ); + } + + if ( leaf1 && leaf2 ) + { + break; + } + + // Can the cost possibly be decreased? + if ( bestCost <= lowerCost1 && bestCost <= lowerCost2 ) + { + break; + } + + if ( lowerCost1 == lowerCost2 && leaf1 == false ) + { + B3_ASSERT( lowerCost1 < FLT_MAX ); + B3_ASSERT( lowerCost2 < FLT_MAX ); + + // No clear choice based on lower bound surface area. This can happen when both + // children fully contain D. Fall back to node distance. + b3Vec3 d1 = b3Sub( b3AABB_Center( box1 ), centerD ); + b3Vec3 d2 = b3Sub( b3AABB_Center( box2 ), centerD ); + lowerCost1 = b3LengthSquared( d1 ); + lowerCost2 = b3LengthSquared( d2 ); + } + + // Descend + if ( lowerCost1 < lowerCost2 && leaf1 == false ) + { + index = child1; + areaBase = area1; + directCost = directCost1; + } + else + { + index = child2; + areaBase = area2; + directCost = directCost2; + } + + B3_ASSERT( b3IsLeaf( nodes + index ) == false ); + } + + return bestSibling; +} + +enum b3RotateType +{ + b3_rotateNone, + b3_rotateBF, + b3_rotateBG, + b3_rotateCD, + b3_rotateCE +}; + +// Perform a left or right rotation if node A is imbalanced. +// Returns the new root index. +static void b3RotateNodes( b3DynamicTree* tree, int iA ) +{ + B3_ASSERT( iA != B3_NULL_INDEX ); + + b3TreeNode* nodes = tree->nodes; + + b3TreeNode* A = nodes + iA; + if ( b3IsLeaf( A ) == true ) + { + return; + } + + int iB = A->children.child1; + int iC = A->children.child2; + B3_ASSERT( 0 <= iB && iB < tree->nodeCapacity ); + B3_ASSERT( 0 <= iC && iC < tree->nodeCapacity ); + + b3TreeNode* B = nodes + iB; + b3TreeNode* C = nodes + iC; + + bool isLeafB = b3IsLeaf( B ); + bool isLeafC = b3IsLeaf( C ); + + if ( isLeafB == true && isLeafC == false ) + { + int iF = C->children.child1; + int iG = C->children.child2; + b3TreeNode* F = nodes + iF; + b3TreeNode* G = nodes + iG; + B3_ASSERT( 0 <= iF && iF < tree->nodeCapacity ); + B3_ASSERT( 0 <= iG && iG < tree->nodeCapacity ); + + // Base cost + float costBase = b3Perimeter( C->aabb ); + + // Cost of swapping B and F + b3AABB aabbBG = b3AABB_Union( B->aabb, G->aabb ); + float costBF = b3Perimeter( aabbBG ); + + // Cost of swapping B and G + b3AABB aabbBF = b3AABB_Union( B->aabb, F->aabb ); + float costBG = b3Perimeter( aabbBF ); + + if ( costBase < costBF && costBase < costBG ) + { + // Rotation does not improve cost + return; + } + + if ( costBF < costBG ) + { + // Swap B and F + A->children.child1 = iF; + C->children.child1 = iB; + + B->parent = iC; + F->parent = iA; + + C->aabb = aabbBG; + + C->height = 1 + b3MaxUInt16( B->height, G->height ); + A->height = 1 + b3MaxUInt16( C->height, F->height ); + C->categoryBits = B->categoryBits | G->categoryBits; + A->categoryBits = C->categoryBits | F->categoryBits; + C->flags |= ( B->flags | G->flags ) & b3_enlargedNode; + A->flags |= ( C->flags | F->flags ) & b3_enlargedNode; + } + else + { + // Swap B and G + A->children.child1 = iG; + C->children.child2 = iB; + + B->parent = iC; + G->parent = iA; + + C->aabb = aabbBF; + + C->height = 1 + b3MaxUInt16( B->height, F->height ); + A->height = 1 + b3MaxUInt16( C->height, G->height ); + C->categoryBits = B->categoryBits | F->categoryBits; + A->categoryBits = C->categoryBits | G->categoryBits; + C->flags |= ( B->flags | F->flags ) & b3_enlargedNode; + A->flags |= ( C->flags | G->flags ) & b3_enlargedNode; + } + } + else if ( isLeafC == true && isLeafB == false ) + { + // C is a leaf and B is internal + + int iD = B->children.child1; + int iE = B->children.child2; + b3TreeNode* D = nodes + iD; + b3TreeNode* E = nodes + iE; + B3_ASSERT( 0 <= iD && iD < tree->nodeCapacity ); + B3_ASSERT( 0 <= iE && iE < tree->nodeCapacity ); + + // Base cost + float costBase = b3Perimeter( B->aabb ); + + // Cost of swapping C and D + b3AABB aabbCE = b3AABB_Union( C->aabb, E->aabb ); + float costCD = b3Perimeter( aabbCE ); + + // Cost of swapping C and E + b3AABB aabbCD = b3AABB_Union( C->aabb, D->aabb ); + float costCE = b3Perimeter( aabbCD ); + + if ( costBase < costCD && costBase < costCE ) + { + // Rotation does not improve cost + return; + } + + if ( costCD < costCE ) + { + // Swap C and D + A->children.child2 = iD; + B->children.child1 = iC; + + C->parent = iB; + D->parent = iA; + + B->aabb = aabbCE; + + B->height = 1 + b3MaxUInt16( C->height, E->height ); + A->height = 1 + b3MaxUInt16( B->height, D->height ); + B->categoryBits = C->categoryBits | E->categoryBits; + A->categoryBits = B->categoryBits | D->categoryBits; + B->flags |= ( C->flags | E->flags ) & b3_enlargedNode; + A->flags |= ( B->flags | D->flags ) & b3_enlargedNode; + } + else + { + // Swap C and E + A->children.child2 = iE; + B->children.child2 = iC; + + C->parent = iB; + E->parent = iA; + + B->aabb = aabbCD; + + B->height = 1 + b3MaxUInt16( C->height, D->height ); + A->height = 1 + b3MaxUInt16( B->height, E->height ); + B->categoryBits = C->categoryBits | D->categoryBits; + A->categoryBits = B->categoryBits | E->categoryBits; + B->flags |= ( C->flags | D->flags ) & b3_enlargedNode; + A->flags |= ( B->flags | E->flags ) & b3_enlargedNode; + } + } + else if ( isLeafB == false && isLeafC == false ) + { + // All grand children exist so there are many options for rotation + int iD = B->children.child1; + int iE = B->children.child2; + int iF = C->children.child1; + int iG = C->children.child2; + + B3_ASSERT( 0 <= iD && iD < tree->nodeCapacity ); + B3_ASSERT( 0 <= iE && iE < tree->nodeCapacity ); + B3_ASSERT( 0 <= iF && iF < tree->nodeCapacity ); + B3_ASSERT( 0 <= iG && iG < tree->nodeCapacity ); + + b3TreeNode* D = nodes + iD; + b3TreeNode* E = nodes + iE; + b3TreeNode* F = nodes + iF; + b3TreeNode* G = nodes + iG; + + // Base cost + float areaB = b3Perimeter( B->aabb ); + float areaC = b3Perimeter( C->aabb ); + float costBase = areaB + areaC; + enum b3RotateType bestRotation = b3_rotateNone; + float bestCost = costBase; + + // Cost of swapping B and F + b3AABB aabbBG = b3AABB_Union( B->aabb, G->aabb ); + float costBF = areaB + b3Perimeter( aabbBG ); + if ( costBF < bestCost ) + { + bestRotation = b3_rotateBF; + bestCost = costBF; + } + + // Cost of swapping B and G + b3AABB aabbBF = b3AABB_Union( B->aabb, F->aabb ); + float costBG = areaB + b3Perimeter( aabbBF ); + if ( costBG < bestCost ) + { + bestRotation = b3_rotateBG; + bestCost = costBG; + } + + // Cost of swapping C and D + b3AABB aabbCE = b3AABB_Union( C->aabb, E->aabb ); + float costCD = areaC + b3Perimeter( aabbCE ); + if ( costCD < bestCost ) + { + bestRotation = b3_rotateCD; + bestCost = costCD; + } + + // Cost of swapping C and E + b3AABB aabbCD = b3AABB_Union( C->aabb, D->aabb ); + float costCE = areaC + b3Perimeter( aabbCD ); + if ( costCE < bestCost ) + { + bestRotation = b3_rotateCE; + // bestCost = costCE; + } + + switch ( bestRotation ) + { + case b3_rotateNone: + break; + + case b3_rotateBF: + A->children.child1 = iF; + C->children.child1 = iB; + + B->parent = iC; + F->parent = iA; + + C->aabb = aabbBG; + + C->height = 1 + b3MaxUInt16( B->height, G->height ); + A->height = 1 + b3MaxUInt16( C->height, F->height ); + C->categoryBits = B->categoryBits | G->categoryBits; + A->categoryBits = C->categoryBits | F->categoryBits; + C->flags |= ( B->flags | G->flags ) & b3_enlargedNode; + A->flags |= ( C->flags | F->flags ) & b3_enlargedNode; + break; + + case b3_rotateBG: + A->children.child1 = iG; + C->children.child2 = iB; + + B->parent = iC; + G->parent = iA; + + C->aabb = aabbBF; + + C->height = 1 + b3MaxUInt16( B->height, F->height ); + A->height = 1 + b3MaxUInt16( C->height, G->height ); + C->categoryBits = B->categoryBits | F->categoryBits; + A->categoryBits = C->categoryBits | G->categoryBits; + C->flags |= ( B->flags | F->flags ) & b3_enlargedNode; + A->flags |= ( C->flags | G->flags ) & b3_enlargedNode; + break; + + case b3_rotateCD: + A->children.child2 = iD; + B->children.child1 = iC; + + C->parent = iB; + D->parent = iA; + + B->aabb = aabbCE; + + B->height = 1 + b3MaxUInt16( C->height, E->height ); + A->height = 1 + b3MaxUInt16( B->height, D->height ); + B->categoryBits = C->categoryBits | E->categoryBits; + A->categoryBits = B->categoryBits | D->categoryBits; + B->flags |= ( C->flags | E->flags ) & b3_enlargedNode; + A->flags |= ( B->flags | D->flags ) & b3_enlargedNode; + break; + + case b3_rotateCE: + A->children.child2 = iE; + B->children.child2 = iC; + + C->parent = iB; + E->parent = iA; + + B->aabb = aabbCD; + + B->height = 1 + b3MaxUInt16( C->height, D->height ); + A->height = 1 + b3MaxUInt16( B->height, E->height ); + B->categoryBits = C->categoryBits | D->categoryBits; + A->categoryBits = B->categoryBits | E->categoryBits; + B->flags |= ( C->flags | D->flags ) & b3_enlargedNode; + A->flags |= ( B->flags | E->flags ) & b3_enlargedNode; + break; + + default: + B3_ASSERT( false ); + break; + } + } +} + +// It would be nicer if the root had zero height but maintaining this would drastically increase +// insertion cost because whole sub-trees would need the height to be updated. +static void b3InsertLeaf( b3DynamicTree* tree, int leaf, bool shouldRotate ) +{ + if ( tree->root == B3_NULL_INDEX ) + { + tree->root = leaf; + tree->nodes[tree->root].parent = B3_NULL_INDEX; + return; + } + + // Stage 1: find the best sibling for this node + b3AABB leafAABB = tree->nodes[leaf].aabb; + int sibling = b3FindBestSibling( tree, leafAABB ); + + // Stage 2: create a new parent for the leaf and sibling + int oldParent = tree->nodes[sibling].parent; + int newParent = b3AllocateNode( tree ); + + // warning: node pointer can change after allocation + b3TreeNode* nodes = tree->nodes; + nodes[newParent].parent = oldParent; + nodes[newParent].userData = UINT64_MAX; + nodes[newParent].aabb = b3AABB_Union( leafAABB, nodes[sibling].aabb ); + nodes[newParent].categoryBits = nodes[leaf].categoryBits | nodes[sibling].categoryBits; + nodes[newParent].height = nodes[sibling].height + 1; + + if ( oldParent != B3_NULL_INDEX ) + { + // The sibling was not the root. + if ( nodes[oldParent].children.child1 == sibling ) + { + nodes[oldParent].children.child1 = newParent; + } + else + { + nodes[oldParent].children.child2 = newParent; + } + + nodes[newParent].children.child1 = sibling; + nodes[newParent].children.child2 = leaf; + nodes[sibling].parent = newParent; + nodes[leaf].parent = newParent; + } + else + { + // The sibling was the root. + nodes[newParent].children.child1 = sibling; + nodes[newParent].children.child2 = leaf; + nodes[sibling].parent = newParent; + nodes[leaf].parent = newParent; + tree->root = newParent; + } + + // Stage 3: walk back up the tree fixing heights and AABBs + int index = nodes[leaf].parent; + while ( index != B3_NULL_INDEX ) + { + int child1 = nodes[index].children.child1; + int child2 = nodes[index].children.child2; + + B3_ASSERT( child1 != B3_NULL_INDEX ); + B3_ASSERT( child2 != B3_NULL_INDEX ); + + nodes[index].aabb = b3AABB_Union( nodes[child1].aabb, nodes[child2].aabb ); + nodes[index].categoryBits = nodes[child1].categoryBits | nodes[child2].categoryBits; + nodes[index].height = 1 + b3MaxUInt16( nodes[child1].height, nodes[child2].height ); + nodes[index].flags |= ( nodes[child1].flags | nodes[child2].flags ) & b3_enlargedNode; + + if ( shouldRotate ) + { + b3RotateNodes( tree, index ); + } + + index = nodes[index].parent; + } +} + +static void b3RemoveLeaf( b3DynamicTree* tree, int leaf ) +{ + if ( leaf == tree->root ) + { + tree->root = B3_NULL_INDEX; + return; + } + + b3TreeNode* nodes = tree->nodes; + + int parent = nodes[leaf].parent; + int grandParent = nodes[parent].parent; + int sibling; + if ( nodes[parent].children.child1 == leaf ) + { + sibling = nodes[parent].children.child2; + } + else + { + sibling = nodes[parent].children.child1; + } + + if ( grandParent != B3_NULL_INDEX ) + { + // Destroy parent and connect sibling to grandParent. + if ( nodes[grandParent].children.child1 == parent ) + { + nodes[grandParent].children.child1 = sibling; + } + else + { + nodes[grandParent].children.child2 = sibling; + } + nodes[sibling].parent = grandParent; + b3FreeNode( tree, parent ); + + // Adjust ancestor bounds. + int index = grandParent; + while ( index != B3_NULL_INDEX ) + { + b3TreeNode* node = nodes + index; + b3TreeNode* child1 = nodes + node->children.child1; + b3TreeNode* child2 = nodes + node->children.child2; + + // Fast union using SSE + //__m128 aabb1 = _mm_load_ps(&child1->aabb.lowerBound.x); + //__m128 aabb2 = _mm_load_ps(&child2->aabb.lowerBound.x); + //__m128 lower = _mm_min_ps(aabb1, aabb2); + //__m128 upper = _mm_max_ps(aabb1, aabb2); + //__m128 aabb = _mm_shuffle_ps(lower, upper, _MM_SHUFFLE(3, 2, 1, 0)); + //_mm_store_ps(&node->aabb.lowerBound.x, aabb); + + node->aabb = b3AABB_Union( child1->aabb, child2->aabb ); + node->categoryBits = child1->categoryBits | child2->categoryBits; + node->height = 1 + b3MaxUInt16( child1->height, child2->height ); + + index = node->parent; + } + } + else + { + tree->root = sibling; + tree->nodes[sibling].parent = B3_NULL_INDEX; + b3FreeNode( tree, parent ); + } +} + +// Create a proxy in the tree as a leaf node. We return the index of the node instead of a pointer so that we can grow +// the node pool. +int b3DynamicTree_CreateProxy( b3DynamicTree* tree, b3AABB aabb, uint64_t categoryBits, uint64_t userData ) +{ + B3_ASSERT( b3IsValidAABB( aabb ) ); + + int proxyId = b3AllocateNode( tree ); + b3TreeNode* node = tree->nodes + proxyId; + + node->aabb = aabb; + node->userData = userData; + node->categoryBits = categoryBits; + node->height = 0; + node->flags = b3_allocatedNode | b3_leafNode; + + bool shouldRotate = true; + b3InsertLeaf( tree, proxyId, shouldRotate ); + + tree->proxyCount += 1; + + return proxyId; +} + +void b3DynamicTree_DestroyProxy( b3DynamicTree* tree, int proxyId ) +{ + B3_ASSERT( 0 <= proxyId && proxyId < tree->nodeCapacity ); + B3_ASSERT( b3IsLeaf( tree->nodes + proxyId ) ); + + b3RemoveLeaf( tree, proxyId ); + b3FreeNode( tree, proxyId ); + + B3_ASSERT( tree->proxyCount > 0 ); + tree->proxyCount -= 1; +} + +int b3DynamicTree_GetProxyCount( const b3DynamicTree* tree ) +{ + return tree->proxyCount; +} + +void b3DynamicTree_MoveProxy( b3DynamicTree* tree, int proxyId, b3AABB aabb ) +{ + B3_ASSERT( b3IsValidAABB( aabb ) ); + B3_ASSERT( 0 <= proxyId && proxyId < tree->nodeCapacity ); + B3_ASSERT( b3IsLeaf( tree->nodes + proxyId ) ); + + b3RemoveLeaf( tree, proxyId ); + + tree->nodes[proxyId].aabb = aabb; + + bool shouldRotate = false; + b3InsertLeaf( tree, proxyId, shouldRotate ); +} + +void b3DynamicTree_EnlargeProxy( b3DynamicTree* tree, int proxyId, b3AABB aabb ) +{ + b3TreeNode* nodes = tree->nodes; + B3_VALIDATE( b3IsValidAABB( aabb ) ); + B3_ASSERT( 0 <= proxyId && proxyId < tree->nodeCapacity ); + B3_VALIDATE( b3IsLeaf( tree->nodes + proxyId ) ); + + // Caller must ensure this + B3_VALIDATE( b3AABB_Contains( nodes[proxyId].aabb, aabb ) == false ); + + b3TreeNode* node = nodes + proxyId; + node->aabb = aabb; + + int parentIndex = node->parent; + while ( parentIndex != B3_NULL_INDEX ) + { + node = nodes + parentIndex; + bool changed = b3EnlargeAABB( &node->aabb, aabb ); + + // todo not sure why this node is marked as enlarged even if it didn't change + node->flags |= b3_enlargedNode; + + parentIndex = node->parent; + + if ( changed == false ) + { + break; + } + } + + while ( parentIndex != B3_NULL_INDEX ) + { + node = nodes + parentIndex; + if ( node->flags & b3_enlargedNode ) + { + // early out because this ancestor was previously ascended and marked as enlarged + break; + } + + node->flags |= b3_enlargedNode; + parentIndex = node->parent; + } +} + +void b3DynamicTree_SetCategoryBits( b3DynamicTree* tree, int proxyId, uint64_t categoryBits ) +{ + b3TreeNode* nodes = tree->nodes; + + B3_ASSERT( b3IsLeaf( nodes + proxyId ) ); + + nodes[proxyId].categoryBits = categoryBits; + + // Fix up category bits in ancestor internal nodes + int nodeIndex = nodes[proxyId].parent; + while ( nodeIndex != B3_NULL_INDEX ) + { + b3TreeNode* node = nodes + nodeIndex; + int child1 = node->children.child1; + B3_ASSERT( child1 != B3_NULL_INDEX ); + int child2 = node->children.child2; + B3_ASSERT( child2 != B3_NULL_INDEX ); + node->categoryBits = nodes[child1].categoryBits | nodes[child2].categoryBits; + + nodeIndex = node->parent; + } +} + +uint64_t b3DynamicTree_GetCategoryBits( b3DynamicTree* tree, int proxyId ) +{ + B3_ASSERT( 0 <= proxyId && proxyId < tree->nodeCapacity ); + return tree->nodes[proxyId].categoryBits; +} + +int b3DynamicTree_GetHeight( const b3DynamicTree* tree ) +{ + if ( tree->root == B3_NULL_INDEX ) + { + return 0; + } + + return tree->nodes[tree->root].height; +} + +float b3DynamicTree_GetAreaRatio( const b3DynamicTree* tree ) +{ + if ( tree->root == B3_NULL_INDEX ) + { + return 0.0f; + } + + const b3TreeNode* root = tree->nodes + tree->root; + float rootArea = b3Perimeter( root->aabb ); + + float totalArea = 0.0f; + for ( int i = 0; i < tree->nodeCapacity; ++i ) + { + const b3TreeNode* node = tree->nodes + i; + if ( b3IsAllocated( node ) == false || b3IsLeaf( node ) || i == tree->root ) + { + continue; + } + + totalArea += b3Perimeter( node->aabb ); + } + + return totalArea / rootArea; +} + +b3AABB b3DynamicTree_GetRootBounds( const b3DynamicTree* tree ) +{ + if ( tree->root != B3_NULL_INDEX ) + { + return tree->nodes[tree->root].aabb; + } + + b3AABB empty = { b3Vec3_zero, b3Vec3_zero }; + return empty; +} + +#if B3_ENABLE_VALIDATION +// Compute the height of a sub-tree. +static int b3ComputeHeightRecurse( const b3DynamicTree* tree, int nodeId ) +{ + B3_ASSERT( 0 <= nodeId && nodeId < tree->nodeCapacity ); + b3TreeNode* node = tree->nodes + nodeId; + + if ( b3IsLeaf( node ) ) + { + return 0; + } + + int height1 = b3ComputeHeightRecurse( tree, node->children.child1 ); + int height2 = b3ComputeHeightRecurse( tree, node->children.child2 ); + return 1 + b3MaxInt( height1, height2 ); +} + +static int b3ComputeHeight( const b3DynamicTree* tree ) +{ + int height = b3ComputeHeightRecurse( tree, tree->root ); + return height; +} + +static void b3ValidateStructure( const b3DynamicTree* tree, int index ) +{ + if ( index == B3_NULL_INDEX ) + { + return; + } + + if ( index == tree->root ) + { + B3_ASSERT( tree->nodes[index].parent == B3_NULL_INDEX ); + } + + const b3TreeNode* node = tree->nodes + index; + + B3_ASSERT( node->flags == 0 || ( node->flags & b3_allocatedNode ) != 0 ); + + if ( b3IsLeaf( node ) ) + { + B3_ASSERT( node->height == 0 ); + return; + } + + int child1 = node->children.child1; + int child2 = node->children.child2; + + B3_ASSERT( 0 <= child1 && child1 < tree->nodeCapacity ); + B3_ASSERT( 0 <= child2 && child2 < tree->nodeCapacity ); + + B3_ASSERT( tree->nodes[child1].parent == index ); + B3_ASSERT( tree->nodes[child2].parent == index ); + + if ( ( tree->nodes[child1].flags | tree->nodes[child2].flags ) & b3_enlargedNode ) + { + B3_ASSERT( node->flags & b3_enlargedNode ); + } + + b3ValidateStructure( tree, child1 ); + b3ValidateStructure( tree, child2 ); +} + +static void b3ValidateMetrics( const b3DynamicTree* tree, int index ) +{ + if ( index == B3_NULL_INDEX ) + { + return; + } + + const b3TreeNode* node = tree->nodes + index; + + B3_VALIDATE( b3IsValidAABB( node->aabb ) ); + + if ( b3IsLeaf( node ) ) + { + B3_ASSERT( node->height == 0 ); + return; + } + + int child1 = node->children.child1; + int child2 = node->children.child2; + + B3_ASSERT( 0 <= child1 && child1 < tree->nodeCapacity ); + B3_ASSERT( 0 <= child2 && child2 < tree->nodeCapacity ); + + int height1 = tree->nodes[child1].height; + int height2 = tree->nodes[child2].height; + int height = 1 + b3MaxInt( height1, height2 ); + B3_ASSERT( node->height == height ); + + // b3AABB aabb = b3AABB_Union(tree->nodes[child1].aabb, tree->nodes[child2].aabb); + + B3_ASSERT( b3AABB_Contains( node->aabb, tree->nodes[child1].aabb ) ); + B3_ASSERT( b3AABB_Contains( node->aabb, tree->nodes[child2].aabb ) ); + + // B3_ASSERT(aabb.lowerBound.x == node->aabb.lowerBound.x); + // B3_ASSERT(aabb.lowerBound.y == node->aabb.lowerBound.y); + // B3_ASSERT(aabb.upperBound.x == node->aabb.upperBound.x); + // B3_ASSERT(aabb.upperBound.y == node->aabb.upperBound.y); + + uint64_t categoryBits = tree->nodes[child1].categoryBits | tree->nodes[child2].categoryBits; + B3_ASSERT( node->categoryBits == categoryBits ); + + b3ValidateMetrics( tree, child1 ); + b3ValidateMetrics( tree, child2 ); +} +#endif + +void b3DynamicTree_Validate( const b3DynamicTree* tree ) +{ +#if B3_ENABLE_VALIDATION + if ( tree->root == B3_NULL_INDEX ) + { + return; + } + + b3ValidateStructure( tree, tree->root ); + b3ValidateMetrics( tree, tree->root ); + + int freeCount = 0; + int freeIndex = tree->freeList; + while ( freeIndex != B3_NULL_INDEX ) + { + B3_ASSERT( 0 <= freeIndex && freeIndex < tree->nodeCapacity ); + freeIndex = tree->nodes[freeIndex].next; + ++freeCount; + } + + int height = b3DynamicTree_GetHeight( tree ); + int computedHeight = b3ComputeHeight( tree ); + B3_ASSERT( height == computedHeight ); + + B3_ASSERT( tree->nodeCount + freeCount == tree->nodeCapacity ); +#else + B3_UNUSED( tree ); +#endif +} + +void b3DynamicTree_ValidateNoEnlarged( const b3DynamicTree* tree ) +{ +#if B3_ENABLE_VALIDATION == 1 + int capacity = tree->nodeCapacity; + const b3TreeNode* nodes = tree->nodes; + for ( int i = 0; i < capacity; ++i ) + { + const b3TreeNode* node = nodes + i; + if ( node->flags & b3_allocatedNode ) + { + B3_ASSERT( ( node->flags & b3_enlargedNode ) == 0 ); + } + } +#else + B3_UNUSED( tree ); +#endif +} + +int b3DynamicTree_GetByteCount( const b3DynamicTree* tree ) +{ + size_t size = sizeof( b3DynamicTree ) + sizeof( b3TreeNode ) * tree->nodeCapacity + + tree->rebuildCapacity * ( sizeof( int ) + sizeof( b3AABB ) + sizeof( b3Vec3 ) + sizeof( int ) ); + + return (int)size; +} + +b3TreeStats b3DynamicTree_Query( const b3DynamicTree* tree, b3AABB aabb, uint64_t maskBits, bool requireAllBits, + b3TreeQueryCallbackFcn* callback, void* context ) +{ + b3TreeStats result = { 0 }; + + if ( tree->nodeCount == 0 ) + { + return result; + } + + int stack[B3_TREE_STACK_SIZE]; + int stackCount = 0; + stack[stackCount++] = tree->root; + + while ( stackCount > 0 ) + { + int nodeId = stack[--stackCount]; + if ( nodeId == B3_NULL_INDEX ) + { + // todo huh? + B3_ASSERT( false ); + continue; + } + + const b3TreeNode* node = tree->nodes + nodeId; + result.nodeVisits += 1; + + // Assuming branch prediction deals with requireAllBits well + uint64_t bitMatch = requireAllBits ? ( node->categoryBits & maskBits ) == maskBits : ( node->categoryBits & maskBits ); + + if ( bitMatch && b3AABB_Overlaps( node->aabb, aabb ) ) + { + if ( b3IsLeaf( node ) ) + { + // callback to user code with proxy id + bool proceed = callback( nodeId, node->userData, context ); + result.leafVisits += 1; + + if ( proceed == false ) + { + return result; + } + } + else + { + B3_ASSERT( stackCount < B3_TREE_STACK_SIZE - 1 ); + if ( stackCount < B3_TREE_STACK_SIZE - 1 ) + { + stack[stackCount++] = node->children.child1; + stack[stackCount++] = node->children.child2; + } + } + } + } + + return result; +} + +B3_FORCE_INLINE float b3DistanceToNodeSqr( b3Vec3 point, const b3TreeNode* node ) +{ + b3Vec3 r = b3Sub( point, b3Clamp( point, node->aabb.lowerBound, node->aabb.upperBound ) ); + return b3Dot( r, r ); +} + +struct b3QueryClosestItem +{ + int nodeIndex; + float distanceToNodeSqr; +}; + +b3TreeStats b3DynamicTree_QueryClosest( const b3DynamicTree* tree, b3Vec3 point, uint64_t maskBits, bool requireAllBits, + b3TreeQueryClosestCallbackFcn* callback, void* context, float* minDistanceSqr ) +{ + b3TreeStats result = { 0 }; + + if ( tree->nodeCount == 0 ) + { + return result; + } + + float minSqr = *minDistanceSqr; + struct b3QueryClosestItem stack[B3_TREE_STACK_SIZE]; + int stackCount = 0; + + float rootDistanceSqr = b3DistanceToNodeSqr( point, tree->nodes + tree->root ); + stack[stackCount++] = (struct b3QueryClosestItem){ + .nodeIndex = tree->root, + .distanceToNodeSqr = rootDistanceSqr, + }; + + while ( stackCount > 0 ) + { + struct b3QueryClosestItem item = stack[--stackCount]; + const b3TreeNode* node = tree->nodes + item.nodeIndex; + result.nodeVisits += 1; + + uint64_t bitMatch = requireAllBits ? ( node->categoryBits & maskBits ) == maskBits : ( node->categoryBits & maskBits ); + + if ( bitMatch ) + { + if ( item.distanceToNodeSqr < minSqr ) + { + if ( b3IsLeaf( node ) ) + { + // callback to user code with minimum distance squared so far and proxy id + float dd = callback( minSqr, item.nodeIndex, node->userData, context ); + + if ( dd < minSqr ) + { + minSqr = dd; + } + + result.leafVisits += 1; + } + else + { + B3_ASSERT( stackCount < B3_TREE_STACK_SIZE - 1 ); + if ( stackCount < B3_TREE_STACK_SIZE - 1 ) + { + int child1 = node->children.child1; + int child2 = node->children.child2; + + // Store the distance to node in the stack instead of recomputing after pop + struct b3QueryClosestItem item1 = { + .nodeIndex = child1, + .distanceToNodeSqr = b3DistanceToNodeSqr( point, tree->nodes + child1 ), + }; + + struct b3QueryClosestItem item2 = { + .nodeIndex = child2, + .distanceToNodeSqr = b3DistanceToNodeSqr( point, tree->nodes + child2 ), + }; + + // Ensure we iterate the closest child first as we pop off the stack + if ( item2.distanceToNodeSqr < item1.distanceToNodeSqr ) + { + stack[stackCount++] = item1; + stack[stackCount++] = item2; + } + else + { + stack[stackCount++] = item2; + stack[stackCount++] = item1; + } + } + } + } + } + } + + *minDistanceSqr = minSqr; + + return result; +} + +b3TreeStats b3DynamicTree_RayCast( const b3DynamicTree* tree, const b3RayCastInput* input, uint64_t maskBits, bool requireAllBits, + b3TreeRayCastCallbackFcn* callback, void* context ) +{ + b3TreeStats result = { 0 }; + + if ( tree->nodeCount == 0 ) + { + return result; + } + + b3Vec3 p1 = input->origin; + b3Vec3 d = input->translation; + + b3V32 pv1 = b3LoadV( &p1.x ); + b3V32 dv = b3LoadV( &d.x ); + + float maxFraction = input->maxFraction; + + b3Vec3 p2 = b3MulAdd( p1, maxFraction, d ); + + // Build a bounding box for the segment. + b3AABB segmentAABB = { b3Min( p1, p2 ), b3Max( p1, p2 ) }; + + int stack[B3_TREE_STACK_SIZE]; + int stackCount = 0; + stack[stackCount++] = tree->root; + + const b3TreeNode* nodes = tree->nodes; + + b3RayCastInput subInput = *input; + + while ( stackCount > 0 ) + { + int nodeId = stack[--stackCount]; + if ( nodeId == B3_NULL_INDEX ) + { + // todo is this possible? + B3_ASSERT( false ); + continue; + } + + const b3TreeNode* node = nodes + nodeId; + result.nodeVisits += 1; + + b3AABB nodeAABB = node->aabb; + + // todo look at disassembly + uint64_t bitMatch = requireAllBits ? ( node->categoryBits & maskBits ) == maskBits : ( node->categoryBits & maskBits ); + + if ( bitMatch == 0 || b3AABB_Overlaps( nodeAABB, segmentAABB ) == false ) + { + continue; + } + + b3V32 lower = b3LoadV( &nodeAABB.lowerBound.x ); + b3V32 upper = b3LoadV( &nodeAABB.upperBound.x ); + + bool edgeOverlap = b3TestBoundsRayOverlap( lower, upper, pv1, dv ); + if ( edgeOverlap == false ) + { + continue; + } + + if ( b3IsLeaf( node ) ) + { + subInput.maxFraction = maxFraction; + + float value = callback( &subInput, nodeId, node->userData, context ); + result.leafVisits += 1; + + // The user may return -1 to indicate this shape should be skipped + + if ( value == 0.0f ) + { + // The client has terminated the ray cast. + return result; + } + + if ( 0.0f < value && value <= maxFraction ) + { + // Update segment bounding box. + maxFraction = value; + p2 = b3MulAdd( p1, maxFraction, d ); + segmentAABB.lowerBound = b3Min( p1, p2 ); + segmentAABB.upperBound = b3Max( p1, p2 ); + } + } + else + { + B3_ASSERT( stackCount < B3_TREE_STACK_SIZE - 1 ); + if ( stackCount < B3_TREE_STACK_SIZE - 1 ) + { + b3Vec3 c1 = b3AABB_Center( nodes[node->children.child1].aabb ); + b3Vec3 c2 = b3AABB_Center( nodes[node->children.child2].aabb ); + if ( b3DistanceSquared( c1, p1 ) < b3DistanceSquared( c2, p1 ) ) + { + stack[stackCount++] = node->children.child2; + stack[stackCount++] = node->children.child1; + } + else + { + stack[stackCount++] = node->children.child1; + stack[stackCount++] = node->children.child2; + } + } + } + } + + return result; +} + +b3TreeStats b3DynamicTree_BoxCast( const b3DynamicTree* tree, const b3BoxCastInput* input, uint64_t maskBits, bool requireAllBits, + b3TreeBoxCastCallbackFcn* callback, void* context ) +{ + b3TreeStats stats = { 0 }; + + if ( tree->nodeCount == 0 ) + { + return stats; + } + + // The caller folds the shape radius and the world origin into the box + b3AABB originAABB = input->box; + + b3Vec3 p1 = b3AABB_Center( originAABB ); + b3Vec3 extension = b3AABB_Extents( originAABB ); + + b3Vec3 d = input->translation; + + b3V32 pv1 = b3LoadV( &p1.x ); + b3V32 dv = b3LoadV( &d.x ); + b3V32 ev = b3LoadV( &extension.x ); + + float maxFraction = input->maxFraction; + + // Build total box for the cast + b3Vec3 t = b3MulSV( maxFraction, input->translation ); + b3AABB totalAABB = { + b3Min( originAABB.lowerBound, b3Add( originAABB.lowerBound, t ) ), + b3Max( originAABB.upperBound, b3Add( originAABB.upperBound, t ) ), + }; + + b3BoxCastInput subInput = *input; + const b3TreeNode* nodes = tree->nodes; + + int stack[B3_TREE_STACK_SIZE]; + int stackCount = 0; + stack[stackCount++] = tree->root; + + while ( stackCount > 0 ) + { + int nodeId = stack[--stackCount]; + if ( nodeId == B3_NULL_INDEX ) + { + B3_ASSERT( false ); + continue; + } + + const b3TreeNode* node = nodes + nodeId; + stats.nodeVisits += 1; + + uint64_t bitMatch = requireAllBits ? ( node->categoryBits & maskBits ) == maskBits : ( node->categoryBits & maskBits ); + + if ( bitMatch == 0 || b3AABB_Overlaps( node->aabb, totalAABB ) == false ) + { + continue; + } + + // radius extension is added to the node in this case + b3V32 lower = b3SubV( b3LoadV( &node->aabb.lowerBound.x ), ev ); + b3V32 upper = b3AddV( b3LoadV( &node->aabb.upperBound.x ), ev ); + bool edgeOverlap = b3TestBoundsRayOverlap( lower, upper, pv1, dv ); + if ( edgeOverlap == false ) + { + continue; + } + + if ( b3IsLeaf( node ) ) + { + subInput.maxFraction = maxFraction; + + float value = callback( &subInput, nodeId, node->userData, context ); + stats.leafVisits += 1; + + if ( value == 0.0f ) + { + // The client has terminated the cast. + return stats; + } + + if ( 0.0f < value && value < maxFraction ) + { + maxFraction = value; + t = b3MulSV( maxFraction, input->translation ); + totalAABB.lowerBound = b3Min( originAABB.lowerBound, b3Add( originAABB.lowerBound, t ) ); + totalAABB.upperBound = b3Max( originAABB.upperBound, b3Add( originAABB.upperBound, t ) ); + } + } + else + { + B3_ASSERT( stackCount < B3_TREE_STACK_SIZE - 1 ); + if ( stackCount < B3_TREE_STACK_SIZE - 1 ) + { + b3Vec3 c1 = b3AABB_Center( nodes[node->children.child1].aabb ); + b3Vec3 c2 = b3AABB_Center( nodes[node->children.child2].aabb ); + if ( b3DistanceSquared( c1, p1 ) < b3DistanceSquared( c2, p1 ) ) + { + stack[stackCount++] = node->children.child2; + stack[stackCount++] = node->children.child1; + } + else + { + stack[stackCount++] = node->children.child1; + stack[stackCount++] = node->children.child2; + } + } + } + } + + return stats; +} + +// Median split == 0, Surface area heuristic == 1 +#define B3_TREE_HEURISTIC 0 + +#if B3_TREE_HEURISTIC == 0 + +// Median split heuristic +static int b3PartitionMid( int* indices, b3Vec3* centers, int count ) +{ + // Handle trivial case + if ( count <= 2 ) + { + return count / 2; + } + + b3Vec3 lowerBound = centers[0]; + b3Vec3 upperBound = centers[0]; + + for ( int i = 1; i < count; ++i ) + { + lowerBound = b3Min( lowerBound, centers[i] ); + upperBound = b3Max( upperBound, centers[i] ); + } + + b3Vec3 d = b3Sub( upperBound, lowerBound ); + b3Vec3 c = b3MulSV( 0.5f, b3Add( lowerBound, upperBound ) ); + + // Partition longest axis using the Hoare partition scheme + // https://en.wikipedia.org/wiki/Quicksort + // https://nicholasvadivelu.com/2021/01/11/array-partition/ + int i1 = 0, i2 = count; + if ( d.x >= d.y && d.x >= d.z ) + { + float pivot = c.x; + + while ( i1 < i2 ) + { + while ( i1 < i2 && centers[i1].x < pivot ) + { + i1 += 1; + }; + + while ( i1 < i2 && centers[i2 - 1].x >= pivot ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap indices + { + int temp = indices[i1]; + indices[i1] = indices[i2 - 1]; + indices[i2 - 1] = temp; + } + + // Swap centers + { + b3Vec3 temp = centers[i1]; + centers[i1] = centers[i2 - 1]; + centers[i2 - 1] = temp; + } + + i1 += 1; + i2 -= 1; + } + } + } + else if ( d.y >= d.z ) + { + float pivot = c.y; + + while ( i1 < i2 ) + { + while ( i1 < i2 && centers[i1].y < pivot ) + { + i1 += 1; + }; + + while ( i1 < i2 && centers[i2 - 1].y >= pivot ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap indices + { + int temp = indices[i1]; + indices[i1] = indices[i2 - 1]; + indices[i2 - 1] = temp; + } + + // Swap centers + { + b3Vec3 temp = centers[i1]; + centers[i1] = centers[i2 - 1]; + centers[i2 - 1] = temp; + } + + i1 += 1; + i2 -= 1; + } + } + } + else + { + float pivot = c.z; + + while ( i1 < i2 ) + { + while ( i1 < i2 && centers[i1].z < pivot ) + { + i1 += 1; + }; + + while ( i1 < i2 && centers[i2 - 1].z >= pivot ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap indices + { + int temp = indices[i1]; + indices[i1] = indices[i2 - 1]; + indices[i2 - 1] = temp; + } + + // Swap centers + { + b3Vec3 temp = centers[i1]; + centers[i1] = centers[i2 - 1]; + centers[i2 - 1] = temp; + } + + i1 += 1; + i2 -= 1; + } + } + } + B3_ASSERT( i1 == i2 ); + + if ( i1 > 0 && i1 < count ) + { + return i1; + } + + return count / 2; +} + +#else + +#define B3_BIN_COUNT 8 + +typedef struct b3TreeBin +{ + b3AABB aabb; + int count; +} b3TreeBin; + +typedef struct b3TreePlane +{ + b3AABB leftAABB; + b3AABB rightAABB; + int leftCount; + int rightCount; +} b3TreePlane; + +// "On Fast Construction of SAH-based Bounding Volume Hierarchies" by Ingo Wald +// Returns the left child count +static int b3PartitionSAH( int* indices, int* binIndices, b3AABB* boxes, int count ) +{ + B3_ASSERT( count > 0 ); + + b3TreeBin bins[B3_BIN_COUNT]; + b3TreePlane planes[B3_BIN_COUNT - 1]; + + b3Vec3 center = b3AABB_Center( boxes[0] ); + b3AABB centroidAABB; + centroidAABB.lowerBound = center; + centroidAABB.upperBound = center; + + for ( int i = 1; i < count; ++i ) + { + center = b3AABB_Center( boxes[i] ); + centroidAABB.lowerBound = b3Min( centroidAABB.lowerBound, center ); + centroidAABB.upperBound = b3Max( centroidAABB.upperBound, center ); + } + + b3Vec3 d = b3Sub( centroidAABB.upperBound, centroidAABB.lowerBound ); + + // Find longest axis + int axisIndex; + float invD; + if ( d.x > d.y ) + { + axisIndex = 0; + invD = d.x; + } + else + { + axisIndex = 1; + invD = d.y; + } + + invD = invD > 0.0f ? 1.0f / invD : 0.0f; + + // Initialize bin bounds and count + for ( int i = 0; i < B3_BIN_COUNT; ++i ) + { + bins[i].aabb.lowerBound = (b3Vec3){ FLT_MAX, FLT_MAX }; + bins[i].aabb.upperBound = (b3Vec3){ -FLT_MAX, -FLT_MAX }; + bins[i].count = 0; + } + + // Assign boxes to bins and compute bin boxes + // TODO_ERIN optimize + float binCount = B3_BIN_COUNT; + float lowerBoundArray[2] = { centroidAABB.lowerBound.x, centroidAABB.lowerBound.y }; + float minC = lowerBoundArray[axisIndex]; + for ( int i = 0; i < count; ++i ) + { + b3Vec3 c = b3AABB_Center( boxes[i] ); + float cArray[2] = { c.x, c.y }; + int binIndex = (int)( binCount * ( cArray[axisIndex] - minC ) * invD ); + binIndex = b3ClampInt( binIndex, 0, B3_BIN_COUNT - 1 ); + binIndices[i] = binIndex; + bins[binIndex].count += 1; + bins[binIndex].aabb = b3AABB_Union( bins[binIndex].aabb, boxes[i] ); + } + + int planeCount = B3_BIN_COUNT - 1; + + // Prepare all the left planes, candidates for left child + planes[0].leftCount = bins[0].count; + planes[0].leftAABB = bins[0].aabb; + for ( int i = 1; i < planeCount; ++i ) + { + planes[i].leftCount = planes[i - 1].leftCount + bins[i].count; + planes[i].leftAABB = b3AABB_Union( planes[i - 1].leftAABB, bins[i].aabb ); + } + + // Prepare all the right planes, candidates for right child + planes[planeCount - 1].rightCount = bins[planeCount].count; + planes[planeCount - 1].rightAABB = bins[planeCount].aabb; + for ( int i = planeCount - 2; i >= 0; --i ) + { + planes[i].rightCount = planes[i + 1].rightCount + bins[i + 1].count; + planes[i].rightAABB = b3AABB_Union( planes[i + 1].rightAABB, bins[i + 1].aabb ); + } + + // Find best split to minimize SAH + float minCost = FLT_MAX; + int bestPlane = 0; + for ( int i = 0; i < planeCount; ++i ) + { + float leftArea = b3Perimeter( planes[i].leftAABB ); + float rightArea = b3Perimeter( planes[i].rightAABB ); + int leftCount = planes[i].leftCount; + int rightCount = planes[i].rightCount; + + float cost = leftCount * leftArea + rightCount * rightArea; + if ( cost < minCost ) + { + bestPlane = i; + minCost = cost; + } + } + + // Partition node indices and boxes using the Hoare partition scheme + // https://en.wikipedia.org/wiki/Quicksort + // https://nicholasvadivelu.com/2021/01/11/array-partition/ + int i1 = 0, i2 = count; + while ( i1 < i2 ) + { + while ( i1 < i2 && binIndices[i1] < bestPlane ) + { + i1 += 1; + }; + + while ( i1 < i2 && binIndices[i2 - 1] >= bestPlane ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap indices + { + int temp = indices[i1]; + indices[i1] = indices[i2 - 1]; + indices[i2 - 1] = temp; + } + + // Swap boxes + { + b3AABB temp = boxes[i1]; + boxes[i1] = boxes[i2 - 1]; + boxes[i2 - 1] = temp; + } + + i1 += 1; + i2 -= 1; + } + } + B3_ASSERT( i1 == i2 ); + + if ( i1 > 0 && i1 < count ) + { + return i1; + } + else + { + return count / 2; + } +} + +#endif + +// Temporary data used to track the rebuild of a tree node +struct b3RebuildItem +{ + int nodeIndex; + int childCount; + + // Leaf indices + int startIndex; + int splitIndex; + int endIndex; +}; + +// Returns root node index +static int b3BuildTree( b3DynamicTree* tree, int leafCount ) +{ + b3TreeNode* nodes = tree->nodes; + int* leafIndices = tree->leafIndices; + + if ( leafCount == 1 ) + { + nodes[leafIndices[0]].parent = B3_NULL_INDEX; + return leafIndices[0]; + } + +#if B3_TREE_HEURISTIC == 0 + b3Vec3* leafCenters = tree->leafCenters; +#else + b3AABB* leafBoxes = tree->leafBoxes; + int* binIndices = tree->binIndices; +#endif + + // todo large stack item + struct b3RebuildItem stack[B3_TREE_STACK_SIZE]; + int top = 0; + + stack[0].nodeIndex = b3AllocateNode( tree ); + stack[0].childCount = -1; + stack[0].startIndex = 0; + stack[0].endIndex = leafCount; +#if B3_TREE_HEURISTIC == 0 + stack[0].splitIndex = b3PartitionMid( leafIndices, leafCenters, leafCount ); +#else + stack[0].splitIndex = b3PartitionSAH( leafIndices, binIndices, leafBoxes, leafCount ); +#endif + + while ( true ) + { + struct b3RebuildItem* item = stack + top; + + item->childCount += 1; + + if ( item->childCount == 2 ) + { + // This internal node has both children established + + if ( top == 0 ) + { + // all done + break; + } + + struct b3RebuildItem* parentItem = stack + ( top - 1 ); + b3TreeNode* parentNode = nodes + parentItem->nodeIndex; + + if ( parentItem->childCount == 0 ) + { + B3_ASSERT( parentNode->children.child1 == B3_NULL_INDEX ); + parentNode->children.child1 = item->nodeIndex; + } + else + { + B3_ASSERT( parentItem->childCount == 1 ); + B3_ASSERT( parentNode->children.child2 == B3_NULL_INDEX ); + parentNode->children.child2 = item->nodeIndex; + } + + b3TreeNode* node = nodes + item->nodeIndex; + + B3_ASSERT( node->parent == B3_NULL_INDEX ); + node->parent = parentItem->nodeIndex; + + B3_ASSERT( node->children.child1 != B3_NULL_INDEX ); + B3_ASSERT( node->children.child2 != B3_NULL_INDEX ); + b3TreeNode* child1 = nodes + node->children.child1; + b3TreeNode* child2 = nodes + node->children.child2; + + node->aabb = b3AABB_Union( child1->aabb, child2->aabb ); + node->height = 1 + b3MaxUInt16( child1->height, child2->height ); + node->categoryBits = child1->categoryBits | child2->categoryBits; + + // Pop stack + top -= 1; + } + else + { + int startIndex, endIndex; + if ( item->childCount == 0 ) + { + startIndex = item->startIndex; + endIndex = item->splitIndex; + } + else + { + B3_ASSERT( item->childCount == 1 ); + startIndex = item->splitIndex; + endIndex = item->endIndex; + } + + int count = endIndex - startIndex; + + if ( count == 1 ) + { + int childIndex = leafIndices[startIndex]; + b3TreeNode* node = nodes + item->nodeIndex; + + if ( item->childCount == 0 ) + { + B3_ASSERT( node->children.child1 == B3_NULL_INDEX ); + node->children.child1 = childIndex; + } + else + { + B3_ASSERT( item->childCount == 1 ); + B3_ASSERT( node->children.child2 == B3_NULL_INDEX ); + node->children.child2 = childIndex; + } + + b3TreeNode* childNode = nodes + childIndex; + B3_ASSERT( childNode->parent == B3_NULL_INDEX ); + childNode->parent = item->nodeIndex; + } + else + { + B3_ASSERT( count > 0 ); + B3_ASSERT( top < B3_TREE_STACK_SIZE ); + + top += 1; + struct b3RebuildItem* newItem = stack + top; + newItem->nodeIndex = b3AllocateNode( tree ); + newItem->childCount = -1; + newItem->startIndex = startIndex; + newItem->endIndex = endIndex; +#if B3_TREE_HEURISTIC == 0 + newItem->splitIndex = b3PartitionMid( leafIndices + startIndex, leafCenters + startIndex, count ); +#else + newItem->splitIndex = + b3PartitionSAH( leafIndices + startIndex, binIndices + startIndex, leafBoxes + startIndex, count ); +#endif + newItem->splitIndex += startIndex; + } + } + } + + b3TreeNode* rootNode = nodes + stack[0].nodeIndex; + B3_ASSERT( rootNode->parent == B3_NULL_INDEX ); + B3_ASSERT( rootNode->children.child1 != B3_NULL_INDEX ); + B3_ASSERT( rootNode->children.child2 != B3_NULL_INDEX ); + + b3TreeNode* child1 = nodes + rootNode->children.child1; + b3TreeNode* child2 = nodes + rootNode->children.child2; + + rootNode->aabb = b3AABB_Union( child1->aabb, child2->aabb ); + rootNode->height = 1 + b3MaxUInt16( child1->height, child2->height ); + rootNode->categoryBits = child1->categoryBits | child2->categoryBits; + + return stack[0].nodeIndex; +} + +// Not safe to access tree during this operation because it may grow +int b3DynamicTree_Rebuild( b3DynamicTree* tree, bool fullBuild ) +{ + int proxyCount = tree->proxyCount; + if ( proxyCount == 0 ) + { + return 0; + } + + // Ensure capacity for rebuild space + if ( proxyCount > tree->rebuildCapacity ) + { + int newCapacity = proxyCount + proxyCount / 2; + + b3Free( tree->leafIndices, tree->rebuildCapacity * sizeof( int ) ); + tree->leafIndices = (int*)b3Alloc( newCapacity * sizeof( int ) ); + +#if B3_TREE_HEURISTIC == 0 + b3Free( tree->leafCenters, tree->rebuildCapacity * sizeof( b3Vec3 ) ); + tree->leafCenters = (b3Vec3*)b3Alloc( newCapacity * sizeof( b3Vec3 ) ); +#else + b3Free( tree->leafBoxes, tree->rebuildCapacity * sizeof( b3AABB ) ); + tree->leafBoxes = (b3AABB*)b3Alloc( newCapacity * sizeof( b3AABB ) ); + b3Free( tree->binIndices, tree->rebuildCapacity * sizeof( int ) ); + tree->binIndices = (int*)b3Alloc( newCapacity * sizeof( int ) ); +#endif + tree->rebuildCapacity = newCapacity; + } + + int leafCount = 0; + int stack[B3_TREE_STACK_SIZE]; + int stackCount = 0; + + int nodeIndex = tree->root; + b3TreeNode* nodes = tree->nodes; + b3TreeNode* node = nodes + nodeIndex; + + // These are the nodes that get sorted to rebuild the tree. + // I'm using indices because the node pool may grow during the build. + int* leafIndices = tree->leafIndices; + +#if B3_TREE_HEURISTIC == 0 + b3Vec3* leafCenters = tree->leafCenters; +#else + b3AABB* leafBoxes = tree->leafBoxes; +#endif + + // Gather all proxy nodes that have grown and all internal nodes that haven't grown. Both are + // considered leaves in the tree rebuild. + // Free all internal nodes that have grown. + // todo use a node growth metric instead of simply enlarged to reduce rebuild size and frequency + // this should be weighed against B3_MAX_AABB_MARGIN + while ( true ) + { + if ( b3IsLeaf( node ) == true || ( ( node->flags & b3_enlargedNode ) == 0 && fullBuild == false ) ) + { + leafIndices[leafCount] = nodeIndex; +#if B3_TREE_HEURISTIC == 0 + leafCenters[leafCount] = b3AABB_Center( node->aabb ); +#else + leafBoxes[leafCount] = node->aabb; +#endif + leafCount += 1; + + // Detach + node->parent = B3_NULL_INDEX; + } + else + { + int doomedNodeIndex = nodeIndex; + + // Handle children + nodeIndex = node->children.child1; + + B3_ASSERT( stackCount < B3_TREE_STACK_SIZE ); + if ( stackCount < B3_TREE_STACK_SIZE ) + { + stack[stackCount++] = node->children.child2; + } + + node = nodes + nodeIndex; + + // Remove doomed node + b3FreeNode( tree, doomedNodeIndex ); + + continue; + } + + if ( stackCount == 0 ) + { + break; + } + + nodeIndex = stack[--stackCount]; + node = nodes + nodeIndex; + } + +#if B3_ENABLE_VALIDATION == 1 + int capacity = tree->nodeCapacity; + for ( int i = 0; i < capacity; ++i ) + { + if ( nodes[i].flags & b3_allocatedNode ) + { + B3_ASSERT( ( nodes[i].flags & b3_enlargedNode ) == 0 ); + } + } +#endif + + B3_ASSERT( leafCount <= proxyCount ); + + tree->root = b3BuildTree( tree, leafCount ); + + b3DynamicTree_Validate( tree ); + + return leafCount; +} + +static FILE* b3OpenTreeFile( const char* fileName, const char* mode ) +{ + FILE* file = NULL; + +#if defined( _MSC_VER ) + errno_t e = fopen_s( &file, fileName, mode ); + if ( e != 0 ) + { + return NULL; + } +#else + file = fopen( fileName, mode ); + if ( file == NULL ) + { + return NULL; + } +#endif + + return file; +} + +void b3DynamicTree_Save( const b3DynamicTree* tree, const char* fileName ) +{ + FILE* file = b3OpenTreeFile( fileName, "wb" ); + if ( file == NULL ) + { + return; + } + + // Copy to allow setting some fields to zero + b3DynamicTree temp = *tree; + + // Zero pointers and temp data + temp.nodes = NULL; + temp.leafIndices = NULL; + temp.leafBoxes = NULL; + temp.leafCenters = NULL; + temp.binIndices = NULL; + temp.rebuildCapacity = 0; + + // Write tree struct + fwrite( &temp, sizeof( b3DynamicTree ), 1, file ); + + // Write the node array, this includes the free list + if ( tree->nodeCapacity > 0 && tree->nodes != NULL ) + { + fwrite( tree->nodes, sizeof( b3TreeNode ), tree->nodeCapacity, file ); + } + + fclose( file ); +} + +b3DynamicTree b3DynamicTree_Load( const char* fileName, float scale ) +{ + b3DynamicTree tree = { 0 }; + + FILE* file = b3OpenTreeFile( fileName, "rb" ); + if ( file == NULL ) + { + return tree; + } + + int readCount = (int)fread( &tree, sizeof( b3DynamicTree ), 1, file ); + if ( readCount != 1 ) + { + fclose( file ); + return tree; + } + + if ( tree.version != B3_DYNAMIC_TREE_VERSION ) + { + fclose( file ); + memset( &tree, 0, sizeof( b3DynamicTree ) ); + return tree; + } + + if ( tree.nodeCapacity > 0 ) + { + tree.nodes = (b3TreeNode*)b3Alloc( tree.nodeCapacity * sizeof( b3TreeNode ) ); + readCount = (int)fread( tree.nodes, sizeof( b3TreeNode ), tree.nodeCapacity, file ); + if ( readCount != tree.nodeCapacity ) + { + b3Free( tree.nodes, tree.nodeCapacity * sizeof( b3TreeNode ) ); + fclose( file ); + memset( &tree, 0, sizeof( b3DynamicTree ) ); + return tree; + } + + for ( int i = 0; i < tree.nodeCapacity; ++i ) + { + b3TreeNode* node = tree.nodes + i; + node->aabb.lowerBound = b3MulSV( scale, node->aabb.lowerBound ); + node->aabb.upperBound = b3MulSV( scale, node->aabb.upperBound ); + } + } + else + { + tree.nodes = NULL; + } + + // Zero temp data fields + tree.leafIndices = NULL; + tree.leafBoxes = NULL; + tree.leafCenters = NULL; + tree.binIndices = NULL; + tree.rebuildCapacity = 0; + + fclose( file ); + + return tree; +} diff --git a/vendor/box3d/src/src/height_field.c b/vendor/box3d/src/src/height_field.c new file mode 100644 index 000000000..1b0307fe9 --- /dev/null +++ b/vendor/box3d/src/src/height_field.c @@ -0,0 +1,1583 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "aabb.h" +#include "algorithm.h" +#include "body.h" +#include "core.h" +#include "shape.h" +#include "simd.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" +#include "box3d/math_functions.h" + +#include +#include +#include + +/* + Convention + + index = row * columnCount + column + height = minHeight + heightScale * compressedHeights[index]; + + column = index % columnCount; + row = index / columnCount; + + x-axis : columns + z-axis : rows + + 00 --- 01 --- 02 --- 03 X + | 0 | 1 | 2 | + 04 --- 05 --- 06 --- 07 + | 3 | 4 | 5 | + 08 --- 09 --- 10 --- 11 + | 6 | 7 | 8 | + 12 --- 13 --- 14 --- 15 + Z + + The quads exist before the column and row ends: row < rowCount - 1 and column < columnCount - 1 + quadIndex = row * (columnCount - 1) + column + + Quad origin index from quad index (needs row): + index = quadIndex + row * columnCount + + Triangle index is related to the quad index + triangleIndex = 2 * quadIndex + (0/1) + quadIndex = triangleIndex / 2 + + Row and column from quad index: + row = quadIndex / (columnCount - 1) + column = quadIndex - row * (columnCount - 1) + + The triangle diagonal is fixed. + + triangle0 = {00, 04, 01} -> {11, 21, 12} + triangle1 = {04, 05, 01} -> {22, 12, 21} + + 11 12 + 00 ---- 01 + | / | + | 0 / 1 | 1 + | / | + 04 ---- 05 + 21 22 + + For adjacency we have + + NA + 00 ---- 01 ---- 02 + | / | / | + NA | 0 / 1 | 2 / 3 | + | / | / | + 04 ---- 05 ---- 06 + | / | / | + NA | 6 / 7 | 8 / 9 | + | / | / | + 08 ---- 09 ---- 10 + + 0: NA, NA, 1 + 1: 0, 6, 2 + + Triangle layouts + + 11 3 12 + 1 ---- 3 + | / + 1 | 0 / 2 + | / + 2 + 21 + + 12 + 2 + / | + 2 / 1 | 1 + / | + 3 ---- 1 + 21 3 22 + */ + +b3HeightFieldData* b3CreateHeightField( const b3HeightFieldDef* data ) +{ + int columnCount = data->countX; + int rowCount = data->countZ; + + int heightCount = columnCount * rowCount; + B3_ASSERT( heightCount >= 4 ); + + int cellCount = ( columnCount - 1 ) * ( rowCount - 1 ); + int triangleCount = 2 * cellCount; + + // Single blob: struct followed by the height, material, and flag arrays. Layout + // mirrors b3HullData/b3MeshData so the recording path can copy it with one memcpy. + size_t byteCount = b3AlignUp8( sizeof( b3HeightFieldData ) ); + int heightsOffset = (int)byteCount; + byteCount += b3AlignUp8( heightCount * sizeof( uint16_t ) ); + int materialOffset = (int)byteCount; + byteCount += b3AlignUp8( cellCount * sizeof( uint8_t ) ); + int flagsOffset = (int)byteCount; + byteCount += b3AlignUp8( triangleCount * sizeof( uint8_t ) ); + + // Zero the whole blob so alignment padding is defined. The construction-time hash + // sweeps raw bytes and would otherwise pick up uninitialized padding. + b3HeightFieldData* hf = (b3HeightFieldData*)b3Alloc( byteCount ); + memset( hf, 0, byteCount ); + + hf->version = B3_HEIGHT_FIELD_VERSION; + hf->byteCount = (int)byteCount; + hf->scale = data->scale; + hf->columnCount = columnCount; + hf->rowCount = rowCount; + hf->heightsOffset = heightsOffset; + hf->materialOffset = materialOffset; + hf->flagsOffset = flagsOffset; + hf->clockwise = data->clockwiseWinding; + + uint16_t* compressedHeights = (uint16_t*)( (intptr_t)hf + heightsOffset ); + uint8_t* materialIndices = (uint8_t*)( (intptr_t)hf + materialOffset ); + uint8_t* flags = (uint8_t*)( (intptr_t)hf + flagsOffset ); + + const float* heights = data->heights; + + B3_ASSERT( data->globalMinimumHeight <= data->globalMaximumHeight ); + hf->minHeight = data->globalMinimumHeight; + hf->maxHeight = data->globalMaximumHeight; + + float height = b3MaxFloat( hf->maxHeight - hf->minHeight, B3_LINEAR_SLOP ); + hf->heightScale = height / UINT16_MAX; + + float lowerHeightBound = hf->maxHeight; + float upperHeightBound = hf->minHeight; + + float invHeightScale = 1.0f / hf->heightScale; + for ( int i = 0; i < heightCount; ++i ) + { + float clampedHeight = b3ClampFloat( heights[i], hf->minHeight, hf->maxHeight ); + float scaledHeight = ( clampedHeight - hf->minHeight ) * invHeightScale; + compressedHeights[i] = (uint16_t)( b3MinFloat( scaledHeight, (float)UINT16_MAX ) ); + + lowerHeightBound = b3MinFloat( lowerHeightBound, clampedHeight ); + upperHeightBound = b3MaxFloat( upperHeightBound, clampedHeight ); + } + + // Use decompressed heights for accurate convexity metrics. + float* decompressedHeights = (float*)b3Alloc( heightCount * sizeof( float ) ); + for ( int i = 0; i < heightCount; ++i ) + { + decompressedHeights[i] = hf->minHeight + hf->heightScale * compressedHeights[i]; + } + heights = decompressedHeights; + + if ( data->materialIndices != NULL ) + { + for ( int i = 0; i < cellCount; ++i ) + { + materialIndices[i] = data->materialIndices[i]; + } + } + else + { + for ( int i = 0; i < cellCount; ++i ) + { + materialIndices[i] = 0; + } + } + + hf->aabb.lowerBound = (b3Vec3){ 0.0f, hf->scale.y * lowerHeightBound, 0.0f }; + hf->aabb.upperBound = + (b3Vec3){ hf->scale.x * ( hf->columnCount - 1 ), hf->scale.y * upperHeightBound, hf->scale.z * ( hf->rowCount - 1 ) }; + + float cos5Deg = 0.9962f; + b3Vec3 scale = hf->scale; + + int triangleIndex = 0; + for ( int row = 0; row < rowCount - 1; ++row ) + { + for ( int column = 0; column < columnCount - 1; ++column ) + { + // todo compute convexity flags + // This requires a couple things + // - determine all 3 adjacent triangles for each triangle + // - consider clockwise winding + // - consider borders where there is no adjacent triangle + + int triangleIndex1 = triangleIndex; + int triangleIndex2 = triangleIndex + 1; + triangleIndex += 2; + + int cellIndex = row * ( columnCount - 1 ) + column; + + if ( materialIndices[cellIndex] == B3_HEIGHT_FIELD_HOLE ) + { + continue; + } + + int flags1 = 0; + int flags2 = 0; + + b3Plane plane1, plane2; + b3Vec3 center1, center2; + + int index11 = row * columnCount + column; + int index12 = index11 + 1; + int index21 = ( row + 1 ) * columnCount + column; + int index22 = index21 + 1; + + { + float height11 = heights[index11]; + float height12 = heights[index12]; + float height21 = heights[index21]; + float height22 = heights[index22]; + + float x1 = (float)( column ); + float x2 = (float)( column + 1 ); + float z1 = (float)( row ); + float z2 = (float)( row + 1 ); + + // triangle 0 : 11, 21, 12 + b3Vec3 vs0[3]; + vs0[0] = b3Mul( scale, (b3Vec3){ x1, height11, z1 } ); + vs0[1] = b3Mul( scale, (b3Vec3){ x1, height21, z2 } ); + vs0[2] = b3Mul( scale, (b3Vec3){ x2, height12, z1 } ); + plane1 = b3MakePlaneFromPoints( vs0[0], vs0[1], vs0[2] ); + + center1 = b3MulSV( 1.0f / 3.0f, b3Add( b3Add( vs0[0], vs0[1] ), vs0[2] ) ); + + // triangle 1 : 22, 12, 21 + b3Vec3 vs1[3]; + vs1[0] = b3Mul( scale, (b3Vec3){ x2, height22, z2 } ); + vs1[1] = b3Mul( scale, (b3Vec3){ x2, height12, z1 } ); + vs1[2] = b3Mul( scale, (b3Vec3){ x1, height21, z2 } ); + plane2 = b3MakePlaneFromPoints( vs1[0], vs1[1], vs1[2] ); + + center2 = b3MulSV( 1.0f / 3.0f, b3Add( b3Add( vs1[0], vs1[1] ), vs1[2] ) ); + + float separation = b3PlaneSeparation( plane1, vs1[0] ); + float cosAngle = b3Dot( plane1.normal, plane2.normal ); + if ( separation > 0.0f || cosAngle > cos5Deg ) + { + flags1 |= b3_concaveEdge2; + flags2 |= b3_concaveEdge2; + } + if ( separation < 0.0f || cosAngle > cos5Deg ) + { + flags1 |= b3_inverseConcaveEdge2; + flags2 |= b3_inverseConcaveEdge2; + } + } + + B3_UNUSED( center1 ); + B3_UNUSED( center2 ); + + // top + int topCellIndex = ( row - 1 ) * ( columnCount - 1 ) + column; + if ( row > 0 && materialIndices[topCellIndex] != B3_HEIGHT_FIELD_HOLE ) + { + B3_ASSERT( 0 <= topCellIndex && topCellIndex < cellCount ); + + int r = row - 1; + int c = column; + + int i11 = r * columnCount + c; + int i12 = i11 + 1; + int i21 = ( r + 1 ) * columnCount + c; + int i22 = i21 + 1; + + B3_ASSERT( i21 == index11 ); + B3_ASSERT( i22 == index12 ); + + // float h11 = heights[i11]; + float h12 = heights[i12]; + float h21 = heights[i21]; + float h22 = heights[i22]; + + float x1 = (float)( c ); + float x2 = (float)( c + 1 ); + float z1 = (float)( r ); + float z2 = (float)( r + 1 ); + + // triangle 1 + b3Vec3 vs[3]; + vs[0] = b3Mul( scale, (b3Vec3){ x2, h22, z2 } ); + vs[1] = b3Mul( scale, (b3Vec3){ x2, h12, z1 } ); + vs[2] = b3Mul( scale, (b3Vec3){ x1, h21, z2 } ); + + b3Vec3 n = b3Normalize( b3Cross( b3Sub( vs[1], vs[0] ), b3Sub( vs[2], vs[0] ) ) ); + + float separation = b3PlaneSeparation( plane1, vs[1] ); + float cosAngle = b3Dot( plane1.normal, n ); + if ( separation > 0.0f || cosAngle > cos5Deg ) + { + flags1 |= b3_concaveEdge3; + } + if ( separation < 0.0f || cosAngle > cos5Deg ) + { + flags1 |= b3_inverseConcaveEdge3; + } + } + + int bottomCellIndex = ( row + 1 ) * ( columnCount - 1 ) + column; + if ( row + 1 < rowCount - 1 && materialIndices[bottomCellIndex] != B3_HEIGHT_FIELD_HOLE ) + { + B3_ASSERT( 0 <= bottomCellIndex && bottomCellIndex < cellCount ); + + int r = row + 1; + int c = column; + + int i11 = r * columnCount + c; + int i12 = i11 + 1; + int i21 = ( r + 1 ) * columnCount + c; + // int i22 = i21 + 1; + + B3_ASSERT( i11 == index21 ); + B3_ASSERT( i12 == index22 ); + + float h11 = heights[i11]; + float h12 = heights[i12]; + float h21 = heights[i21]; + // float h22 = heights[i22]; + + float x1 = (float)( c ); + float x2 = (float)( c + 1 ); + float z1 = (float)( r ); + float z2 = (float)( r + 1 ); + + // triangle 0 + b3Vec3 vs[3]; + vs[0] = b3Mul( scale, (b3Vec3){ x1, h11, z1 } ); + vs[1] = b3Mul( scale, (b3Vec3){ x1, h21, z2 } ); + vs[2] = b3Mul( scale, (b3Vec3){ x2, h12, z1 } ); + + b3Vec3 n = b3Normalize( b3Cross( b3Sub( vs[1], vs[0] ), b3Sub( vs[2], vs[0] ) ) ); + + float separation = b3PlaneSeparation( plane2, vs[1] ); + float cosAngle = b3Dot( plane2.normal, n ); + if ( separation > 0.0f || cosAngle > cos5Deg ) + { + flags2 |= b3_concaveEdge3; + } + if ( separation < 0.0f || cosAngle > cos5Deg ) + { + flags2 |= b3_inverseConcaveEdge3; + } + } + + int leftCellIndex = row * ( columnCount - 1 ) + column - 1; + if ( column - 1 >= 0 && materialIndices[leftCellIndex] != B3_HEIGHT_FIELD_HOLE ) + { + B3_ASSERT( 0 <= leftCellIndex && leftCellIndex < cellCount ); + + int r = row; + int c = column - 1; + + int i11 = r * columnCount + c; + int i12 = i11 + 1; + int i21 = ( r + 1 ) * columnCount + c; + int i22 = i21 + 1; + + B3_ASSERT( i12 == index11 ); + B3_ASSERT( i22 == index21 ); + + // float h11 = heights[i11]; + float h12 = heights[i12]; + float h21 = heights[i21]; + float h22 = heights[i22]; + + float x1 = (float)( c ); + float x2 = (float)( c + 1 ); + float z1 = (float)( r ); + float z2 = (float)( r + 1 ); + + // triangle 1 + b3Vec3 vs[3]; + vs[0] = b3Mul( scale, (b3Vec3){ x2, h22, z2 } ); + vs[1] = b3Mul( scale, (b3Vec3){ x2, h12, z1 } ); + vs[2] = b3Mul( scale, (b3Vec3){ x1, h21, z2 } ); + + b3Vec3 n = b3Normalize( b3Cross( b3Sub( vs[1], vs[0] ), b3Sub( vs[2], vs[0] ) ) ); + + float separation = b3PlaneSeparation( plane1, vs[2] ); + float cosAngle = b3Dot( plane1.normal, n ); + if ( separation > 0.0f || cosAngle > cos5Deg ) + { + flags1 |= b3_concaveEdge1; + } + if ( separation < 0.0f || cosAngle > cos5Deg ) + { + flags1 |= b3_inverseConcaveEdge1; + } + } + + int rightCellIndex = row * ( columnCount - 1 ) + column + 1; + if ( column + 1 < columnCount - 1 && materialIndices[rightCellIndex] != B3_HEIGHT_FIELD_HOLE ) + { + B3_ASSERT( 0 <= rightCellIndex && rightCellIndex < cellCount ); + + int r = row; + int c = column + 1; + + int i11 = r * columnCount + c; + int i12 = i11 + 1; + int i21 = ( r + 1 ) * columnCount + c; + // int i22 = i21 + 1; + + B3_ASSERT( i11 == index12 ); + B3_ASSERT( i21 == index22 ); + + float h11 = heights[i11]; + float h12 = heights[i12]; + float h21 = heights[i21]; + // float h22 = heights[i22]; + + float x1 = (float)( c ); + float x2 = (float)( c + 1 ); + float z1 = (float)( r ); + float z2 = (float)( r + 1 ); + + // triangle 0 + b3Vec3 vs[3]; + vs[0] = b3Mul( scale, (b3Vec3){ x1, h11, z1 } ); + vs[1] = b3Mul( scale, (b3Vec3){ x1, h21, z2 } ); + vs[2] = b3Mul( scale, (b3Vec3){ x2, h12, z1 } ); + + b3Vec3 n = b3Normalize( b3Cross( b3Sub( vs[1], vs[0] ), b3Sub( vs[2], vs[0] ) ) ); + + float separation = b3PlaneSeparation( plane2, vs[2] ); + float cosAngle = b3Dot( plane2.normal, n ); + if ( separation > 0.0f || cosAngle > cos5Deg ) + { + flags2 |= b3_concaveEdge1; + } + if ( separation < 0.0f || cosAngle > cos5Deg ) + { + flags2 |= b3_inverseConcaveEdge1; + } + } + + B3_ASSERT( 0 <= flags1 && flags1 <= UINT8_MAX ); + B3_ASSERT( 0 <= flags2 && flags2 <= UINT8_MAX ); + + flags[triangleIndex1] = (uint8_t)flags1; + flags[triangleIndex2] = (uint8_t)flags2; + } + } + + B3_ASSERT( triangleIndex == triangleCount ); + + b3Free( decompressedHeights, heightCount * sizeof( float ) ); + + // Content hash over the whole blob with the hash field zeroed, like b3HullData/b3MeshData. + hf->hash = 0; + hf->hash = b3NonZeroHash( b3Hash( B3_HASH_INIT, (const uint8_t*)hf, hf->byteCount ) ); + + return hf; +} + +_Static_assert( b3_concaveEdge3 == 4 * b3_concaveEdge1, "bit math" ); +_Static_assert( b3_inverseConcaveEdge3 == 4 * b3_inverseConcaveEdge1, "bit math" ); + +// Decode the four corner vertices of a height field cell into local space. +// Output order matches the index naming used throughout this file: +// corners[0] = (column, row), corners[1] = (column + 1, row), +// corners[2] = (column, row + 1), corners[3] = (column + 1, row + 1). +static inline void b3GetHeightFieldCellCorners( const b3HeightFieldData* hf, int row, int column, b3Vec3 corners[4] ) +{ + B3_ASSERT( 0 <= row && row < hf->rowCount - 1 && 0 <= column && column < hf->columnCount - 1 ); + + int columnCount = hf->columnCount; + int index11 = row * columnCount + column; + int index12 = index11 + 1; + int index21 = ( row + 1 ) * columnCount + column; + int index22 = index21 + 1; + + float minHeight = hf->minHeight; + float heightScale = hf->heightScale; + const uint16_t* heights = b3GetHeightFieldCompressedHeights( hf ); + + float height11 = minHeight + heightScale * heights[index11]; + float height12 = minHeight + heightScale * heights[index12]; + float height21 = minHeight + heightScale * heights[index21]; + float height22 = minHeight + heightScale * heights[index22]; + + float x1 = (float)( column ); + float x2 = (float)( column + 1 ); + float z1 = (float)( row ); + float z2 = (float)( row + 1 ); + + b3Vec3 scale = hf->scale; + corners[0] = b3Mul( scale, (b3Vec3){ x1, height11, z1 } ); + corners[1] = b3Mul( scale, (b3Vec3){ x2, height12, z1 } ); + corners[2] = b3Mul( scale, (b3Vec3){ x1, height21, z2 } ); + corners[3] = b3Mul( scale, (b3Vec3){ x2, height22, z2 } ); +} + +b3Triangle b3GetHeightFieldTriangle( const b3HeightFieldData* heightField, int triangleIndex ) +{ + B3_ASSERT( 0 <= triangleIndex ); + B3_ASSERT( triangleIndex < 2 * ( heightField->columnCount - 1 ) * ( heightField->rowCount - 1 ) ); + + b3Triangle triangle; + triangle.flags = b3GetHeightFieldFlags( heightField )[triangleIndex]; + + int columnCount = heightField->columnCount; + int quadIndex = triangleIndex >> 1; + int row = quadIndex / ( columnCount - 1 ); + int column = quadIndex - row * ( columnCount - 1 ); + + int index11 = row * columnCount + column; + int index12 = index11 + 1; + int index21 = ( row + 1 ) * columnCount + column; + int index22 = index21 + 1; + + int cellIndex = row * ( columnCount - 1 ) + column; + + B3_ASSERT( quadIndex == cellIndex ); + B3_ASSERT( b3GetHeightFieldMaterialIndices( heightField )[cellIndex] != B3_HEIGHT_FIELD_HOLE ); + B3_UNUSED( cellIndex ); + + b3Vec3 corners[4]; + b3GetHeightFieldCellCorners( heightField, row, column, corners ); + + if ( ( triangleIndex & 1 ) == 0 ) + { + triangle.vertices[0] = corners[0]; + triangle.vertices[1] = corners[2]; + triangle.vertices[2] = corners[1]; + triangle.i1 = index11; + triangle.i2 = index21; + triangle.i3 = index12; + } + else + { + triangle.vertices[0] = corners[3]; + triangle.vertices[1] = corners[1]; + triangle.vertices[2] = corners[2]; + triangle.i1 = index22; + triangle.i2 = index12; + triangle.i3 = index21; + } + + if ( heightField->clockwise ) + { + B3_SWAP( triangle.vertices[1], triangle.vertices[2] ); + B3_SWAP( triangle.i2, triangle.i3 ); + + // Reversing winding swaps edge1 and edge3; edge2 (the diagonal) is preserved. + int flags = triangle.flags; + int edge1Bits = flags & ( b3_concaveEdge1 | b3_inverseConcaveEdge1 ); + int edge3Bits = flags & ( b3_concaveEdge3 | b3_inverseConcaveEdge3 ); + flags &= ~( b3_concaveEdge1 | b3_concaveEdge3 | b3_inverseConcaveEdge1 | b3_inverseConcaveEdge3 ); + flags |= edge1Bits << 2; + flags |= edge3Bits >> 2; + triangle.flags = flags; + } + + return triangle; +} + +int b3GetHeightFieldMaterial( const b3HeightFieldData* heightField, int triangleIndex ) +{ + B3_ASSERT( 0 <= triangleIndex ); + B3_ASSERT( triangleIndex < 2 * ( heightField->columnCount - 1 ) * ( heightField->rowCount - 1 ) ); + + int cellIndex = triangleIndex >> 1; + return b3GetHeightFieldMaterialIndices( heightField )[cellIndex]; +} + +b3AABB b3ComputeHeightFieldAABB( const b3HeightFieldData* shape, b3Transform transform ) +{ + return b3AABB_Transform( transform, shape->aabb ); +} + +b3CastOutput b3RayCastHeightField( const b3HeightFieldData* heightField, const b3RayCastInput* input ) +{ + b3ShapeCastInput shapeCastInput = { 0 }; + shapeCastInput.proxy = (b3ShapeProxy){ &input->origin, 1, 0.0f }; + shapeCastInput.translation = input->translation; + shapeCastInput.maxFraction = input->maxFraction; + + return b3ShapeCastHeightField( heightField, &shapeCastInput ); +} + +// todo advance cast to the grid border immediately if it starts outside the row/column range +// todo terminate the cast immediately if it leaves the row/column range +b3CastOutput b3ShapeCastHeightField( const b3HeightFieldData* heightField, const b3ShapeCastInput* input ) +{ + b3AABB shapeBounds = b3MakeAABB( input->proxy.points, input->proxy.count, input->proxy.radius ); + b3Vec3 shapeTranslation = input->translation; + b3Vec3 scale = heightField->scale; + + b3Vec3 shapeStart = b3AABB_Center( shapeBounds ); + b3Vec3 shapeDelta = b3MulSV( input->maxFraction, shapeTranslation ); + b3Vec3 shapeEnd = b3Add( shapeStart, shapeDelta ); + + b3CastOutput result = { 0 }; + + b3Vec3 shapeExtents = b3AABB_Extents( shapeBounds ); + b3Vec3 margin = { B3_MAX_AABB_MARGIN, B3_MAX_AABB_MARGIN, B3_MAX_AABB_MARGIN }; + b3AABB combinedBounds = { b3Sub( b3Sub( heightField->aabb.lowerBound, shapeExtents ), margin ), + b3Add( b3Add( heightField->aabb.upperBound, shapeExtents ), margin ) }; + + float minFraction, maxFraction; + bool intersects = b3RayCastAABB( combinedBounds, shapeStart, shapeEnd, &minFraction, &maxFraction ); + if ( intersects == false ) + { + return result; + } + + // These are for walking the grid, not the triangle cast. + // The triangle cast uses the unclamped ray and fraction. + b3Vec3 clampedStart = b3MulAdd( shapeStart, minFraction, shapeDelta ); + b3Vec3 clampedDelta = b3MulSV( maxFraction - minFraction, shapeDelta ); + b3Vec3 clampedEnd = b3Add( clampedStart, clampedDelta ); + + // Preserve the un-shifted center sweep. clampedStart/clampedEnd get pushed out to the + // leading box corner below to drive the grid DDA, but the swept-volume AABB used to + // cull cells must stay centered on the actual shape path. + b3Vec3 centerStart = clampedStart; + b3Vec3 centerEnd = clampedEnd; + + // The grid traversal starts from the leading shape bounds corner + float signX, signZ; + if ( shapeTranslation.x >= 0.0f ) + { + clampedStart.x += shapeExtents.x; + signX = 1.0f; + } + else + { + clampedStart.x -= shapeExtents.x; + signX = -1.0f; + } + + if ( shapeTranslation.z >= 0.0f ) + { + clampedStart.z += shapeExtents.z; + signZ = 1.0f; + } + else + { + clampedStart.z -= shapeExtents.z; + signZ = -1.0f; + } + + // Shift the end as well + clampedEnd = b3Add( clampedStart, clampedDelta ); + + // Row and column range for the shape cast + int columnStart = (int)floorf( clampedStart.x / scale.x ); + int columnEnd = (int)floorf( clampedEnd.x / scale.x ); + int rowStart = (int)floorf( clampedStart.z / scale.z ); + int rowEnd = (int)floorf( clampedEnd.z / scale.z ); + + b3Vec3 absClampedDelta = b3Abs( clampedDelta ); + + // Precompute increments for row and column traversal. + // The ray can be slightly tilted yet remain within a single row or column + // once rasterized. + float deltaAlphaX; + float nextFractionX; + int deltaColumn; + + if ( columnStart < columnEnd ) + { + B3_ASSERT( absClampedDelta.x > 0.0f ); + + // Going forward on x columns + deltaAlphaX = scale.x / absClampedDelta.x; + nextFractionX = ( scale.x * ( columnStart + 1 ) - clampedStart.x ) / absClampedDelta.x; + deltaColumn = 1; + } + else if ( columnEnd < columnStart ) + { + B3_ASSERT( absClampedDelta.x > 0.0f ); + + // Going backwards on x columns + deltaAlphaX = scale.x / absClampedDelta.x; + nextFractionX = ( clampedStart.x - scale.x * columnStart ) / absClampedDelta.x; + deltaColumn = -1; + } + else + { + // Cast stays in a single column + deltaAlphaX = 0.0f; + nextFractionX = FLT_MAX; + deltaColumn = 0; + } + + float deltaAlphaZ; + float nextFractionZ; + int deltaRow; + + if ( rowStart < rowEnd ) + { + B3_ASSERT( absClampedDelta.z > 0.0f ); + + // Going forward on z rows + deltaAlphaZ = scale.z / absClampedDelta.z; + nextFractionZ = ( scale.z * ( rowStart + 1 ) - clampedStart.z ) / absClampedDelta.z; + deltaRow = 1; + } + else if ( rowEnd < rowStart ) + { + B3_ASSERT( absClampedDelta.z > 0.0f ); + + // Going backwards on z rows + deltaAlphaZ = scale.z / absClampedDelta.z; + nextFractionZ = ( clampedStart.z - scale.z * rowStart ) / absClampedDelta.z; + deltaRow = -1; + } + else + { + // Cast stays in a single row + deltaAlphaZ = 0.0f; + nextFractionZ = FLT_MAX; + deltaRow = 0; + } + + // Column and row range for 2D projected initial shape bounds + int boxColumnHead = columnStart; + int boxRowHead = rowStart; + + int boxColumnTail = (int)floorf( ( clampedStart.x - 2.0f * signX * shapeExtents.x ) / scale.x ); + int boxRowTail = (int)floorf( ( clampedStart.z - 2.0f * signZ * shapeExtents.z ) / scale.z ); + + float bestFraction = input->maxFraction; + + // nextFractionX / nextFractionZ advance in units of the clamped sweep + // [minFraction, maxFraction], but bestFraction is a fraction of the full input + // translation. Precompute the affine map from clamped space to input space so + // the loop termination test compares like with like — otherwise it can exit + // early and miss a closer hit in a later cell. + float gridFractionScale = input->maxFraction * ( maxFraction - minFraction ); + float gridFractionOffset = input->maxFraction * minFraction; + + int rowCount = heightField->rowCount; + int columnCount = heightField->columnCount; + int cellCount = ( heightField->rowCount - 1 ) * ( heightField->columnCount - 1 ); + B3_UNUSED( cellCount ); + + b3ShapeCastPairInput pairInput = { 0 }; + pairInput.proxyB = input->proxy; + pairInput.transform = b3Transform_identity; + pairInput.translationB = input->translation; + pairInput.canEncroach = input->canEncroach; + + b3AABB castBounds; + castBounds.lowerBound = b3Sub( b3Min( centerStart, centerEnd ), shapeExtents ); + castBounds.upperBound = b3Add( b3Max( centerStart, centerEnd ), shapeExtents ); + + b3V32 rayOrigin = b3LoadV( &shapeStart.x ); + b3V32 rayTranslation = b3LoadV( &shapeTranslation.x ); + + while ( true ) + { + int column1, column2; + if ( boxColumnTail < boxColumnHead ) + { + column1 = boxColumnTail; + column2 = boxColumnHead; + } + else + { + column1 = boxColumnHead; + column2 = boxColumnTail; + } + + int row1, row2; + if ( boxRowTail < boxRowHead ) + { + row1 = boxRowTail; + row2 = boxRowHead; + } + else + { + row1 = boxRowHead; + row2 = boxRowTail; + } + + for ( int row = row1; row <= row2; ++row ) + { + if ( row < 0 || rowCount - 1 <= row ) + { + continue; + } + + for ( int column = column1; column <= column2; ++column ) + { + if ( column < 0 || columnCount - 1 <= column ) + { + continue; + } + + int cellIndex = row * ( columnCount - 1 ) + column; + B3_ASSERT( cellIndex < cellCount ); + + uint8_t materialIndex = b3GetHeightFieldMaterialIndices( heightField )[cellIndex]; + if ( materialIndex == B3_HEIGHT_FIELD_HOLE ) + { + continue; + } + + b3Vec3 corners[4]; + b3GetHeightFieldCellCorners( heightField, row, column, corners ); + b3Vec3 point11 = corners[0]; + b3Vec3 point12 = corners[1]; + b3Vec3 point21 = corners[2]; + b3Vec3 point22 = corners[3]; + + // I know the min/max x and z values, but not the min/max heights. + b3AABB bounds; + bounds.lowerBound = b3Min( b3Min( point11, point12 ), b3Min( point21, point22 ) ); + bounds.upperBound = b3Max( b3Max( point11, point12 ), b3Max( point21, point22 ) ); + + if ( b3AABB_Overlaps( castBounds, bounds ) == false ) + { + continue; + } + + int quadIndex = row * ( columnCount - 1 ) + column; + int triangleIndex1 = 2 * quadIndex; + int triangleIndex2 = triangleIndex1 + 1; + + if ( input->proxy.count == 1 && input->proxy.radius == 0.0f ) + { + // Ray cast + { + b3V32 vertex1 = b3LoadV( &point11.x ); + b3V32 vertex2, vertex3; + + if ( heightField->clockwise ) + { + vertex2 = b3LoadV( &point12.x ); + vertex3 = b3LoadV( &point21.x ); + } + else + { + vertex2 = b3LoadV( &point21.x ); + vertex3 = b3LoadV( &point12.x ); + } + + float alpha = b3IntersectRayTriangle( rayOrigin, rayTranslation, vertex1, vertex2, vertex3 ); + B3_ASSERT( 0 <= alpha && alpha <= 1.0f ); + + if ( alpha < bestFraction ) + { + b3Vec3 edge1 = b3Sub( point21, point11 ); + b3Vec3 edge2 = b3Sub( point12, point11 ); + b3Vec3 normal = heightField->clockwise ? b3Cross( edge2, edge1 ) : b3Cross( edge1, edge2 ); + + result.point = b3MulAdd( shapeStart, alpha, shapeTranslation ); + result.normal = b3Normalize( normal ); + result.fraction = alpha; + result.triangleIndex = triangleIndex1; + result.materialIndex = materialIndex; + result.hit = true; + bestFraction = alpha; + } + } + + { + b3V32 vertex1 = b3LoadV( &point22.x ); + b3V32 vertex2, vertex3; + + if ( heightField->clockwise ) + { + vertex2 = b3LoadV( &point21.x ); + vertex3 = b3LoadV( &point12.x ); + } + else + { + vertex2 = b3LoadV( &point12.x ); + vertex3 = b3LoadV( &point21.x ); + } + + float alpha = b3IntersectRayTriangle( rayOrigin, rayTranslation, vertex1, vertex2, vertex3 ); + B3_ASSERT( 0 <= alpha && alpha <= 1.0f ); + + if ( alpha < bestFraction ) + { + b3Vec3 edge1 = b3Sub( point22, point21 ); + b3Vec3 edge2 = b3Sub( point12, point21 ); + b3Vec3 normal = heightField->clockwise ? b3Cross( edge2, edge1 ) : b3Cross( edge1, edge2 ); + + result.point = b3MulAdd( shapeStart, alpha, shapeTranslation ); + result.normal = b3Normalize( normal ); + result.fraction = alpha; + result.triangleIndex = triangleIndex2; + result.materialIndex = materialIndex; + result.hit = true; + bestFraction = alpha; + } + } + } + else + { + // Shape cast + // todo back-side culling + { + // Shift origin to first vertex + b3Vec3 origin = point11; + b3Vec3 triangleVertices[] = { b3Vec3_zero, b3Sub( point21, origin ), b3Sub( point12, origin ) }; + pairInput.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + pairInput.maxFraction = bestFraction; + pairInput.transform.p = b3Neg( origin ); + + b3CastOutput pairOutput = b3ShapeCast( &pairInput ); + + if ( pairOutput.hit ) + { + bestFraction = pairOutput.fraction; + result = pairOutput; + result.point = b3Add( result.point, origin ); + result.triangleIndex = triangleIndex1; + result.materialIndex = materialIndex; + } + } + + { + // Shift origin to first vertex + b3Vec3 origin = point21; + b3Vec3 triangleVertices[] = { b3Vec3_zero, b3Sub( point22, origin ), b3Sub( point12, origin ) }; + pairInput.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + pairInput.maxFraction = bestFraction; + pairInput.transform.p = b3Neg( origin ); + + b3CastOutput pairOutput = b3ShapeCast( &pairInput ); + + if ( pairOutput.hit ) + { + bestFraction = pairOutput.fraction; + result = pairOutput; + result.point = b3Add( result.point, origin ); + result.triangleIndex = triangleIndex2; + result.materialIndex = materialIndex; + } + } + } + } + } + + // These fractions always increase to guarantee the loop eventually exits. + // Map them from clamped-sweep space into input-translation space before + // comparing against bestFraction. + float inputFractionX = nextFractionX == FLT_MAX ? FLT_MAX : gridFractionOffset + nextFractionX * gridFractionScale; + float inputFractionZ = nextFractionZ == FLT_MAX ? FLT_MAX : gridFractionOffset + nextFractionZ * gridFractionScale; + if ( inputFractionX > bestFraction && inputFractionZ > bestFraction ) + { + break; + } + + // Advance the cast to the next column or row + if ( nextFractionX <= nextFractionZ ) + { + if ( boxColumnHead == columnEnd ) + { + // Hit the end already + break; + } + + // Advance to next column + boxColumnHead += deltaColumn; + + // Build a single column to cast + boxColumnTail = boxColumnHead; + + if ( shapeExtents.z == 0.0f ) + { + // Single row + boxRowTail = boxRowHead; + } + else + { + // Rasterize shape row + float rowIntercept = clampedStart.z + nextFractionX * clampedDelta.z; + boxRowTail = (int)floorf( ( rowIntercept - 2.0f * signZ * shapeExtents.z ) / scale.z ); + } + + nextFractionX += deltaAlphaX; + } + else + { + if ( boxRowHead == rowEnd ) + { + // Hit the end already + break; + } + + // Advance to next row + boxRowHead += deltaRow; + + // Build a single row to cast + boxRowTail = boxRowHead; + + if ( shapeExtents.x == 0.0f ) + { + // Single column + boxColumnTail = boxColumnHead; + } + else + { + // Rasterize shape column + float columnIntercept = clampedStart.x + nextFractionZ * clampedDelta.x; + boxColumnTail = (int)floorf( ( columnIntercept - 2.0f * signX * shapeExtents.x ) / scale.x ); + } + + nextFractionZ += deltaAlphaZ; + } + } + + return result; +} + +bool b3OverlapHeightField( const b3HeightFieldData* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy ) +{ + b3Vec3 buffer[B3_MAX_SHAPE_CAST_POINTS]; + b3ShapeProxy localProxy = b3MakeLocalProxy( proxy, shapeTransform, buffer ); + b3AABB aabb = b3ComputeProxyAABB( &localProxy ); + + b3Vec3 scale = shape->scale; + int minRow = (int)floorf( aabb.lowerBound.z / scale.z ); + int maxRow = (int)floorf( aabb.upperBound.z / scale.z ); + int minCol = (int)floorf( aabb.lowerBound.x / scale.x ); + int maxCol = (int)floorf( aabb.upperBound.x / scale.x ); + + b3V32 boundsMin = b3LoadV( &aabb.lowerBound.x ); + b3V32 boundsMax = b3LoadV( &aabb.upperBound.x ); + b3V32 boundsCenter = b3MulV( b3_halfV, b3AddV( boundsMin, boundsMax ) ); + b3V32 boundsExtent = b3SubV( boundsMax, boundsCenter ); + + b3DistanceInput input; + input.proxyB = localProxy; + input.transform = b3Transform_identity; + input.useRadii = true; + + b3SimplexCache cache = { 0 }; + + // Outer loop on rows and inner loop on columns so that triangle indices + // increase monotonically. + for ( int row = minRow; row <= maxRow; ++row ) + { + if ( row < 0 || shape->rowCount - 1 <= row ) + { + continue; + } + + for ( int column = minCol; column <= maxCol; ++column ) + { + if ( column < 0 || shape->columnCount - 1 <= column ) + { + continue; + } + + int cellIndex = row * ( shape->columnCount - 1 ) + column; + B3_ASSERT( cellIndex < ( shape->rowCount - 1 ) * ( shape->columnCount - 1 ) ); + uint8_t material = b3GetHeightFieldMaterialIndices( shape )[cellIndex]; + if ( material == B3_HEIGHT_FIELD_HOLE ) + { + continue; + } + + b3Vec3 corners[4]; + b3GetHeightFieldCellCorners( shape, row, column, corners ); + b3Vec3 point11 = corners[0]; + b3Vec3 point12 = corners[1]; + b3Vec3 point21 = corners[2]; + b3Vec3 point22 = corners[3]; + + b3V32 v11 = b3LoadV( &point11.x ); + b3V32 v12 = b3LoadV( &point12.x ); + b3V32 v21 = b3LoadV( &point21.x ); + b3V32 v22 = b3LoadV( &point22.x ); + + if ( b3TestBoundsTriangleOverlap( boundsCenter, boundsExtent, v11, v21, v12 ) ) + { + b3Vec3 triangleVertices[] = { point11, point21, point12 }; + input.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + + // reset the cache + cache.count = 0; + + // get distance between triangle and query shape + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + + float tolerance = 0.1f * B3_LINEAR_SLOP; + if ( output.distance < tolerance ) + { + // overlap detected + return true; + } + } + + if ( b3TestBoundsTriangleOverlap( boundsCenter, boundsExtent, v21, v22, v12 ) ) + { + b3Vec3 triangleVertices[] = { point22, point12, point21 }; + input.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + + // reset the cache + cache.count = 0; + + // get distance between triangle and query shape + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + + float tolerance = 0.1f * B3_LINEAR_SLOP; + if ( output.distance < tolerance ) + { + // overlap detected + return true; + } + } + } + } + + return false; +} + +void b3QueryHeightField( const b3HeightFieldData* heightField, b3AABB bounds, b3MeshQueryFcn* fcn, void* context ) +{ + b3Vec3 scale = heightField->scale; + + int minRow = (int)floorf( bounds.lowerBound.z / scale.z ); + int maxRow = (int)floorf( bounds.upperBound.z / scale.z ); + int minCol = (int)floorf( bounds.lowerBound.x / scale.x ); + int maxCol = (int)floorf( bounds.upperBound.x / scale.x ); + + // Outer loop on rows and inner loop on columns so that triangle indices + // increase monotonically. + for ( int row = minRow; row <= maxRow; ++row ) + { + if ( row < 0 || heightField->rowCount - 1 <= row ) + { + continue; + } + + for ( int column = minCol; column <= maxCol; ++column ) + { + if ( column < 0 || heightField->columnCount - 1 <= column ) + { + continue; + } + + int cellIndex = row * ( heightField->columnCount - 1 ) + column; + B3_ASSERT( cellIndex < ( heightField->rowCount - 1 ) * ( heightField->columnCount - 1 ) ); + uint8_t material = b3GetHeightFieldMaterialIndices( heightField )[cellIndex]; + if ( material == B3_HEIGHT_FIELD_HOLE ) + { + continue; + } + + b3Vec3 corners[4]; + b3GetHeightFieldCellCorners( heightField, row, column, corners ); + b3Vec3 point11 = corners[0]; + b3Vec3 point12 = corners[1]; + b3Vec3 point21 = corners[2]; + b3Vec3 point22 = corners[3]; + + // I know the min/max x and z values, but not the min/max heights. + // This could be done with no branching in SIMD. + b3AABB cellBound; + cellBound.lowerBound = b3Min( b3Min( point11, point12 ), b3Min( point21, point22 ) ); + cellBound.upperBound = b3Max( b3Max( point11, point12 ), b3Max( point21, point22 ) ); + + if ( b3AABB_Overlaps( bounds, cellBound ) ) + { + int quadIndex = row * ( heightField->columnCount - 1 ) + column; + int triangleIndex = 2 * quadIndex; + + if ( heightField->clockwise ) + { + fcn( point11, point12, point21, triangleIndex, context ); + fcn( point22, point21, point12, triangleIndex + 1, context ); + } + else + { + fcn( point11, point21, point12, triangleIndex, context ); + fcn( point22, point12, point21, triangleIndex + 1, context ); + } + } + } + } +} + +int b3CollideMoverAndHeightField( b3PlaneResult* planes, int capacity, const b3HeightFieldData* shape, const b3Capsule* mover ) +{ + b3DistanceInput distanceInput = { 0 }; + distanceInput.proxyB = (b3ShapeProxy){ &mover->center1, 2, 0.0f }; + distanceInput.transform = b3Transform_identity; + distanceInput.useRadii = false; + + b3SimplexCache cache = { 0 }; + + float radius = mover->radius; + b3V32 center1 = b3LoadV( &mover->center1.x ); + b3V32 center2 = b3LoadV( &mover->center2.x ); + b3V32 r = b3SplatV( radius ); + b3V32 boundsMin = b3SubV( b3MinV( center1, center2 ), r ); + b3V32 boundsMax = b3AddV( b3MaxV( center1, center2 ), r ); + b3V32 boundsCenter = b3MulV( b3_halfV, b3AddV( boundsMin, boundsMax ) ); + b3V32 boundsExtent = b3SubV( boundsMax, boundsCenter ); + + float localMinX = b3GetXV( boundsMin ); + float localMinZ = b3GetZV( boundsMin ); + float localMaxX = b3GetXV( boundsMax ); + float localMaxZ = b3GetZV( boundsMax ); + + b3Vec3 scale = shape->scale; + int minRow = (int)floorf( localMinZ / scale.z ); + int maxRow = (int)floorf( localMaxZ / scale.z ); + int minCol = (int)floorf( localMinX / scale.x ); + int maxCol = (int)floorf( localMaxX / scale.x ); + + int planeCount = 0; + + // Outer loop on rows and inner loop on columns so that triangle indices + // increase monotonically. + for ( int row = minRow; row <= maxRow; ++row ) + { + if ( row < 0 || shape->rowCount - 1 <= row ) + { + continue; + } + + for ( int column = minCol; column <= maxCol; ++column ) + { + if ( column < 0 || shape->columnCount - 1 <= column ) + { + continue; + } + + int cellIndex = row * ( shape->columnCount - 1 ) + column; + B3_ASSERT( cellIndex < ( shape->rowCount - 1 ) * ( shape->columnCount - 1 ) ); + uint8_t material = b3GetHeightFieldMaterialIndices( shape )[cellIndex]; + if ( material == B3_HEIGHT_FIELD_HOLE ) + { + continue; + } + + b3Vec3 corners[4]; + b3GetHeightFieldCellCorners( shape, row, column, corners ); + b3Vec3 point11 = corners[0]; + b3Vec3 point12 = corners[1]; + b3Vec3 point21 = corners[2]; + b3Vec3 point22 = corners[3]; + + b3V32 v11 = b3LoadV( &point11.x ); + b3V32 v12 = b3LoadV( &point12.x ); + b3V32 v21 = b3LoadV( &point21.x ); + b3V32 v22 = b3LoadV( &point22.x ); + + if ( b3TestBoundsTriangleOverlap( boundsCenter, boundsExtent, v11, v21, v12 ) ) + { + b3Vec3 triangleVertices[] = { point11, point21, point12 }; + distanceInput.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + + // reset the cache + cache.count = 0; + + // get distance between triangle and mover + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, &cache, NULL, 0 ); + + if ( distanceOutput.distance == 0.0f ) + { + // todo SAT + } + else if ( distanceOutput.distance <= mover->radius ) + { + b3Plane plane = { distanceOutput.normal, mover->radius - distanceOutput.distance }; + planes[planeCount] = (b3PlaneResult){ plane, distanceOutput.pointA }; + planeCount += 1; + + if ( planeCount == capacity ) + { + return planeCount; + } + } + } + + if ( b3TestBoundsTriangleOverlap( boundsCenter, boundsExtent, v21, v22, v12 ) ) + { + b3Vec3 triangleVertices[] = { point22, point12, point21 }; + distanceInput.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + + // reset the cache + cache.count = 0; + + // get distance between triangle and mover + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, &cache, NULL, 0 ); + + if ( distanceOutput.distance == 0.0f ) + { + // todo SAT + } + else if ( distanceOutput.distance <= mover->radius ) + { + b3Plane plane = { distanceOutput.normal, mover->radius - distanceOutput.distance }; + planes[planeCount] = (b3PlaneResult){ plane, distanceOutput.pointA }; + planeCount += 1; + + if ( planeCount == capacity ) + { + return planeCount; + } + } + } + } + } + + return planeCount; +} + +b3HeightFieldData* b3CreateGrid( int rowCount, int columnCount, b3Vec3 scale, bool makeHoles ) +{ + int heightCount = rowCount * columnCount; + float* heights = (float*)b3Alloc( heightCount * sizeof( float ) ); + + for ( int i = 0; i < rowCount; ++i ) + { + for ( int j = 0; j < columnCount; ++j ) + { + int k = i * columnCount + j; + heights[k] = 0.0f; + } + } + + int cellCount = ( rowCount - 1 ) * ( columnCount - 1 ); + uint8_t* materialIndices = (uint8_t*)b3Alloc( cellCount * sizeof( uint8_t ) ); + + for ( int i = 0; i < rowCount - 1; ++i ) + { + for ( int j = 0; j < columnCount - 1; ++j ) + { + int k = i * ( columnCount - 1 ) + j; + + if ( makeHoles && k > 0 && k % 16 == 0 ) + { + materialIndices[k] = B3_HEIGHT_FIELD_HOLE; + } + else + { + materialIndices[k] = 0; + } + } + } + + b3HeightFieldDef data = { 0 }; + data.heights = heights; + data.materialIndices = materialIndices; + data.scale = scale; + data.countX = columnCount; + data.countZ = rowCount; + data.globalMinimumHeight = -256.0f; + data.globalMaximumHeight = 256.0f; + data.clockwiseWinding = false; + + b3HeightFieldData* heightField = b3CreateHeightField( &data ); + + b3Free( heights, heightCount * sizeof( float ) ); + b3Free( materialIndices, cellCount * sizeof( uint8_t ) ); + + return heightField; +} + +b3HeightFieldData* b3CreateWave( int rowCount, int columnCount, b3Vec3 scale, float rowFrequency, float columnFrequency, + bool makeHoles ) +{ + int heightCount = rowCount * columnCount; + float* heights = (float*)b3Alloc( heightCount * sizeof( float ) ); + + float omegaZ = 2.0f * B3_PI * rowFrequency; + float omegaX = 2.0f * B3_PI * columnFrequency; + + for ( int i = 0; i < rowCount; ++i ) + { + float rowHeight = sinf( omegaZ * i ); + + for ( int j = 0; j < columnCount; ++j ) + { + int k = i * columnCount + j; + float columnHeight = sinf( omegaX * j ); + heights[k] = rowHeight * columnHeight; + } + } + + int cellCount = ( rowCount - 1 ) * ( columnCount - 1 ); + uint8_t* materialIndices = (uint8_t*)b3Alloc( cellCount * sizeof( uint8_t ) ); + + for ( int i = 0; i < rowCount - 1; ++i ) + { + for ( int j = 0; j < columnCount - 1; ++j ) + { + int k = i * ( columnCount - 1 ) + j; + + if ( makeHoles && k > 0 && k % 16 == 0 ) + { + materialIndices[k] = B3_HEIGHT_FIELD_HOLE; + } + else + { + materialIndices[k] = 0; + } + } + } + + b3HeightFieldDef data = { 0 }; + data.heights = heights; + data.materialIndices = materialIndices; + data.scale = scale; + data.countX = columnCount; + data.countZ = rowCount; + data.globalMinimumHeight = -256.0f; + data.globalMaximumHeight = 256.0f; + data.clockwiseWinding = false; + + b3HeightFieldData* heightField = b3CreateHeightField( &data ); + + b3Free( heights, heightCount * sizeof( float ) ); + b3Free( materialIndices, cellCount * sizeof( uint8_t ) ); + + return heightField; +} + +void b3DestroyHeightField( b3HeightFieldData* heightField ) +{ + b3Free( heightField, heightField->byteCount ); +} + +void b3DumpHeightData( const b3HeightFieldDef* data, const char* fileName ) +{ + FILE* file = NULL; + +#if defined( _MSC_VER ) + errno_t e = fopen_s( &file, fileName, "w" ); + if ( e != 0 ) + { + return; + } +#else + file = fopen( fileName, "w" ); + if ( file == NULL ) + { + return; + } +#endif + + fprintf( file, "%d %d\n", data->countX, data->countZ ); + fprintf( file, "%.9f %.9f %.9f\n", data->scale.x, data->scale.y, data->scale.z ); + fprintf( file, "%.9f %.9f\n", data->globalMinimumHeight, data->globalMaximumHeight ); + fprintf( file, "%d\n", data->clockwiseWinding ); + + int heightCount = data->countX * data->countZ; + for ( int i = 0; i < heightCount; ++i ) + { + fprintf( file, "%.9f\n", data->heights[i] ); + } + + int materialCount = ( data->countX - 1 ) * ( data->countZ - 1 ); + for ( int i = 0; i < materialCount; ++i ) + { + fprintf( file, "%d\n", data->materialIndices[i] ); + } + + fclose( file ); +} + +#if defined( _MSC_VER ) +#define B3_FILE_SCAN fscanf_s +#else +#define B3_FILE_SCAN fscanf +#endif + +b3HeightFieldData* b3LoadHeightField( const char* fileName ) +{ + FILE* file = NULL; + +#if defined( _MSC_VER ) + errno_t e = fopen_s( &file, fileName, "r" ); + if ( e != 0 ) + { + return NULL; + } +#else + file = fopen( fileName, "r" ); + if ( file == NULL ) + { + return NULL; + } +#endif + + b3HeightFieldDef data = { 0 }; + + // Read dimensions + if ( B3_FILE_SCAN( file, "%d %d", &data.countX, &data.countZ ) != 2 ) + { + fclose( file ); + return NULL; + } + + // Read scale + if ( B3_FILE_SCAN( file, "%f %f %f", &data.scale.x, &data.scale.y, &data.scale.z ) != 3 ) + { + fclose( file ); + return NULL; + } + + // Read global height bounds + if ( B3_FILE_SCAN( file, "%f %f", &data.globalMinimumHeight, &data.globalMaximumHeight ) != 2 ) + { + fclose( file ); + return NULL; + } + + // Read clockwise winding + int clockwise; + if ( B3_FILE_SCAN( file, "%d", &clockwise ) != 1 ) + { + fclose( file ); + return NULL; + } + data.clockwiseWinding = clockwise != 0; + + // Allocate and read height data + int heightCount = data.countX * data.countZ; + data.heights = (float*)b3Alloc( heightCount * sizeof( float ) ); + + for ( int i = 0; i < heightCount; ++i ) + { + if ( B3_FILE_SCAN( file, "%f", &data.heights[i] ) != 1 ) + { + b3Free( data.heights, heightCount * sizeof( float ) ); + fclose( file ); + return NULL; + } + } + + // Allocate and read material indices + int materialCount = ( data.countX - 1 ) * ( data.countZ - 1 ); + data.materialIndices = (uint8_t*)b3Alloc( materialCount * sizeof( uint8_t ) ); + + for ( int i = 0; i < materialCount; ++i ) + { + int materialIndex; + if ( B3_FILE_SCAN( file, "%d", &materialIndex ) != 1 ) + { + b3Free( data.heights, heightCount * sizeof( float ) ); + b3Free( data.materialIndices, materialCount * sizeof( uint8_t ) ); + fclose( file ); + return NULL; + } + data.materialIndices[i] = (uint8_t)materialIndex; + } + + fclose( file ); + + // Create height field from loaded data + b3HeightFieldData* heightField = b3CreateHeightField( &data ); + + // Clean up temporary allocations + b3Free( data.heights, heightCount * sizeof( float ) ); + b3Free( data.materialIndices, materialCount * sizeof( uint8_t ) ); + + return heightField; +} diff --git a/vendor/box3d/src/src/hull.c b/vendor/box3d/src/src/hull.c new file mode 100644 index 000000000..75bace9f9 --- /dev/null +++ b/vendor/box3d/src/src/hull.c @@ -0,0 +1,2791 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// Dirk Gregorius contributed portions of this code + +#include "algorithm.h" +#include "hull_map.h" +#include "math_internal.h" +#include "shape.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" +#include "box3d/math_functions.h" + +#include +#include +#include +#include +#include +#include + +#define B3_AXIS_X 0 +#define B3_AXIS_Y 1 +#define B3_AXIS_Z 2 + +#define B3_MARK_VISIBLE 0 +#define B3_MARK_DELETE 1 + +// Final hull is index-encoded with uint8_t, so vertex/edge/face counts are capped at UINT8_MAX. +#define B3_HULL_LIMIT UINT8_MAX + +typedef struct b3QHListNode +{ + struct b3QHListNode* prev; + struct b3QHListNode* next; +} b3QHListNode; + +typedef struct b3QHFace b3QHFace; + +typedef struct b3QHVertex +{ + // Intrusive list link. Must be first so (b3QHVertex*)nodePtr is valid. + b3QHListNode link; + + b3QHFace* conflictFace; + b3Vec3 position; + + // Index in the finalized hull, stamped during emit. B3_NULL_INDEX until then. + int finalIndex; + bool reachable; +} b3QHVertex; + +typedef struct b3QHHalfEdge +{ + // Edge ring (CCW) around the owning face. Not an external list. + struct b3QHHalfEdge* prev; + struct b3QHHalfEdge* next; + + b3QHVertex* origin; + b3QHFace* face; + struct b3QHHalfEdge* twin; + + // Index in the finalized hull, stamped during emit. B3_NULL_INDEX until then. + int finalIndex; +} b3QHHalfEdge; + +struct b3QHFace +{ + // Intrusive list link. Must be first so (b3QHFace*)nodePtr is valid. + b3QHListNode link; + + b3QHHalfEdge* edge; + + int mark; + float area; + b3Plane plane; + b3Vec3 centroid; + float maxConflictDistance; + + // Sentinel head for this face's conflict list of b3QHVertex. + b3QHVertex conflictListHead; + + // Cached farthest conflict vertex (above b3HullBuilder::minOutside). + // NULL when no conflict above threshold; maxConflictDistance is then minOutside. + b3QHVertex* maxConflict; + + // Index in the finalized hull, stamped during emit. B3_NULL_INDEX until then. + int finalIndex; + bool flipped; +}; + +// One frame of the iterative horizon DFS. Replaces a recursive call to b3HullBuilder_BuildHorizon. +typedef struct b3HorizonFrame +{ + b3QHFace* face; + b3QHHalfEdge* startEdge; // ring termination sentinel + b3QHHalfEdge* edge; // next edge to process + bool started; // false until the first edge of this ring has been processed +} b3HorizonFrame; + +// All working memory for one hull build, carved from a single b3Alloc block. +typedef struct b3HullBuilder +{ + float tolerance; + float minRadius; + float minOutside; + + b3Vec3 interiorPoint; + + // List sentinels. Only the link is meaningful; other fields are unused. + b3QHVertex orphanedList; + b3QHVertex vertexList; + b3QHFace faceList; + + // Bump-allocated pools with pointer-based free lists for faces and edges. + b3QHVertex* vertexBase; + int vertexCapacity; + int vertexCount; + + b3QHHalfEdge* edgeBase; + int edgeCapacity; + int edgeCount; + b3QHHalfEdge* edgeFreeHead; // LIFO free list; overlays edge->next + + b3QHFace* faceBase; + int faceCapacity; + int faceCount; + b3QHFace* faceFreeHead; // LIFO free list; overlays face->link.next + + // Reusable scratch buffers. + b3QHHalfEdge** horizon; + int horizonCapacity; + int horizonCount; + + b3QHFace** cone; + int coneCapacity; + int coneCount; + + b3QHFace** mergedFaces; + int mergedFacesCapacity; + int mergedFacesCount; + + // DFS stack used by the iterative b3HullBuilder_BuildHorizon. Depth bounded by live faces. + b3HorizonFrame* horizonStack; + int horizonStackCapacity; + + // Final counts of the constructed hull (vertexList / faceList / half-edges around faces). + // Populated by CleanHull; zero until then. + int finalVertexCount; + int finalHalfEdgeCount; + int finalFaceCount; +} b3HullBuilder; + +static inline void b3QHList_Init( b3QHListNode* head ) +{ + head->prev = head; + head->next = head; +} + +#define B3_LIST_EMPTY( A ) ( ( A )->next == ( A ) ) + +static inline bool b3QHList_Contains( const b3QHListNode* node ) +{ + return node->prev != NULL && node->next != NULL; +} + +// Insert node before `where`. +static inline void b3QHList_Insert( b3QHListNode* node, b3QHListNode* where ) +{ + B3_ASSERT( !b3QHList_Contains( node ) && b3QHList_Contains( where ) ); + + node->prev = where->prev; + node->next = where; + + node->prev->next = node; + node->next->prev = node; +} + +static inline void b3QHList_Remove( b3QHListNode* node ) +{ + B3_ASSERT( b3QHList_Contains( node ) ); + + node->prev->next = node->next; + node->next->prev = node->prev; + + node->prev = NULL; + node->next = NULL; +} + +static inline void b3QHList_PushBack( b3QHListNode* head, b3QHListNode* node ) +{ + b3QHList_Insert( node, head->prev ); +} + +static b3QHVertex* b3HullBuilder_NewVertex( b3HullBuilder* b, b3Vec3 position ) +{ + B3_ASSERT( b->vertexCount < b->vertexCapacity ); + b3QHVertex* vertex = b->vertexBase + b->vertexCount++; + + vertex->link.prev = NULL; + vertex->link.next = NULL; + vertex->conflictFace = NULL; + vertex->position = position; + vertex->finalIndex = B3_NULL_INDEX; + vertex->reachable = false; + + return vertex; +} + +static b3QHHalfEdge* b3HullBuilder_NewEdge( b3HullBuilder* b ) +{ + b3QHHalfEdge* edge; + if ( b->edgeFreeHead != NULL ) + { + edge = b->edgeFreeHead; + b->edgeFreeHead = edge->next; + } + else + { + B3_ASSERT( b->edgeCount < b->edgeCapacity ); + edge = b->edgeBase + b->edgeCount++; + } + // All other fields (prev/next/origin/face/twin) are written by NewFace immediately after. + edge->finalIndex = B3_NULL_INDEX; + return edge; +} + +static void b3HullBuilder_RetireEdge( b3HullBuilder* b, b3QHHalfEdge* edge ) +{ + edge->next = b->edgeFreeHead; + b->edgeFreeHead = edge; +} + +static b3QHFace* b3HullBuilder_NewFace( b3HullBuilder* b, b3QHVertex* v1, b3QHVertex* v2, b3QHVertex* v3 ) +{ + b3QHFace* face; + if ( b->faceFreeHead != NULL ) + { + face = b->faceFreeHead; + // link.next was used as free-list pointer; recover next head before we clobber. + b->faceFreeHead = (b3QHFace*)face->link.next; + } + else + { + B3_ASSERT( b->faceCount < b->faceCapacity ); + face = b->faceBase + b->faceCount++; + } + + // link.prev: NULL on retired faces (cleared by Remove); NULL here so PushBack's + // !b3QHList_Contains assert holds for fresh bump slots too. + // link.next: was the free-list pointer on reused slots, now stale; PushBack overwrites. + face->link.prev = NULL; + face->link.next = NULL; + face->maxConflict = NULL; + face->maxConflictDistance = 0.0f; + face->finalIndex = B3_NULL_INDEX; + + b3QHHalfEdge* edge1 = b3HullBuilder_NewEdge( b ); + b3QHHalfEdge* edge2 = b3HullBuilder_NewEdge( b ); + b3QHHalfEdge* edge3 = b3HullBuilder_NewEdge( b ); + + b3Vec3 p1 = v1->position; + b3Vec3 p2 = v2->position; + b3Vec3 p3 = v3->position; + + b3Plane plane; + plane.normal = b3Cross( b3Sub( p2, p1 ), b3Sub( p3, p1 ) ); + float length; + plane.normal = b3GetLengthAndNormalize( &length, plane.normal ); + plane.offset = b3Dot( plane.normal, p1 ); + + float area = 0.5f * length; + + face->edge = edge1; + face->mark = B3_MARK_VISIBLE; + face->area = area; + face->centroid = b3MulSV( 1.0f / 3.0f, b3Add( v1->position, b3Add( v2->position, v3->position ) ) ); + face->plane = plane; + face->flipped = b3PlaneSeparation( plane, b->interiorPoint ) > 0.0f; + b3QHList_Init( &face->conflictListHead.link ); + + edge1->prev = edge3; + edge1->next = edge2; + edge1->origin = v1; + edge1->face = face; + edge1->twin = NULL; + + edge2->prev = edge1; + edge2->next = edge3; + edge2->origin = v2; + edge2->face = face; + edge2->twin = NULL; + + edge3->prev = edge2; + edge3->next = edge1; + edge3->origin = v3; + edge3->face = face; + edge3->twin = NULL; + + return face; +} + +// Remove face from faceList if still linked, clear its edge pointer, then push onto faceFreeHead. +// Uses face->link.next as the free-list next pointer (link.prev stays NULL, so b3QHList_Contains +// returns false on a free slot, as required by the retire-guard in ResolveFaces). +static void b3HullBuilder_RetireFace( b3HullBuilder* b, b3QHFace* face ) +{ + if ( b3QHList_Contains( &face->link ) ) + { + b3QHList_Remove( &face->link ); + } + face->edge = NULL; + // link.prev is already NULL after Remove (or was never set). link.next holds free-list ptr. + face->link.next = (b3QHListNode*)b->faceFreeHead; + b->faceFreeHead = face; +} + +static b3AABB b3BuildBounds( int vertexCount, const b3Vec3* vertices ) +{ + b3AABB bounds = B3_BOUNDS3_EMPTY; + for ( int i = 0; i < vertexCount; ++i ) + { + bounds.lowerBound = b3Min( bounds.lowerBound, vertices[i] ); + bounds.upperBound = b3Max( bounds.upperBound, vertices[i] ); + } + return bounds; +} + +static void b3FindFarthestPointsAlongCardinalAxes( int* index1Out, int* index2Out, float tolerance, int vertexCount, + const b3Vec3* vertexBase ) +{ + *index1Out = B3_NULL_INDEX; + *index2Out = B3_NULL_INDEX; + + b3Vec3 v0 = vertexBase[0]; + b3Vec3 minPt[3] = { v0, v0, v0 }; + b3Vec3 maxPt[3] = { v0, v0, v0 }; + + int minIndex[3] = { 0, 0, 0 }; + int maxIndex[3] = { 0, 0, 0 }; + + for ( int i = 1; i < vertexCount; ++i ) + { + b3Vec3 v = vertexBase[i]; + + if ( v.x < minPt[B3_AXIS_X].x ) + { + minPt[B3_AXIS_X] = v; + minIndex[B3_AXIS_X] = i; + } + else if ( v.x > maxPt[B3_AXIS_X].x ) + { + maxPt[B3_AXIS_X] = v; + maxIndex[B3_AXIS_X] = i; + } + + if ( v.y < minPt[B3_AXIS_Y].y ) + { + minPt[B3_AXIS_Y] = v; + minIndex[B3_AXIS_Y] = i; + } + else if ( v.y > maxPt[B3_AXIS_Y].y ) + { + maxPt[B3_AXIS_Y] = v; + maxIndex[B3_AXIS_Y] = i; + } + + if ( v.z < minPt[B3_AXIS_Z].z ) + { + minPt[B3_AXIS_Z] = v; + minIndex[B3_AXIS_Z] = i; + } + else if ( v.z > maxPt[B3_AXIS_Z].z ) + { + maxPt[B3_AXIS_Z] = v; + maxIndex[B3_AXIS_Z] = i; + } + } + + b3Vec3 distance; + distance.x = maxPt[B3_AXIS_X].x - minPt[B3_AXIS_X].x; + distance.y = maxPt[B3_AXIS_Y].y - minPt[B3_AXIS_Y].y; + distance.z = maxPt[B3_AXIS_Z].z - minPt[B3_AXIS_Z].z; + + float distanceArray[3] = { distance.x, distance.y, distance.z }; + int maxElement = b3MaxElementIndex( distance ); + + if ( distanceArray[maxElement] > 2.0f * tolerance ) + { + *index1Out = minIndex[maxElement]; + *index2Out = maxIndex[maxElement]; + } +} + +static int b3FindFarthestPointFromLine( int index1, int index2, float tolerance, int vertexCount, const b3Vec3* vertexBase ) +{ + b3Vec3 a = vertexBase[index1]; + b3Vec3 b = vertexBase[index2]; + + // |ap x ab|^2 / |ab|^2 is the squared perpendicular distance from p to the line. + // Compares against (2 * tolerance)^2 + b3Vec3 ab = b3Sub( b, a ); + float abLengthSqr = b3Dot( ab, ab ); + B3_ASSERT( abLengthSqr > 0.0f ); + + float invAbLengthSqr = 1.0f / abLengthSqr; + float maxDistanceSqr = 4.0f * tolerance * tolerance; + int maxIndex = B3_NULL_INDEX; + + for ( int i = 0; i < vertexCount; ++i ) + { + if ( i == index1 || i == index2 ) + { + continue; + } + + b3Vec3 ap = b3Sub( vertexBase[i], a ); + b3Vec3 cross = b3Cross( ap, ab ); + float distanceSqr = b3Dot( cross, cross ) * invAbLengthSqr; + if ( distanceSqr > maxDistanceSqr ) + { + maxDistanceSqr = distanceSqr; + maxIndex = i; + } + } + + return maxIndex; +} + +static int b3FindFarthestPointFromPlane( int index1, int index2, int index3, float tolerance, int vertexCount, + const b3Vec3* vertexBase ) +{ + b3Vec3 a = vertexBase[index1]; + b3Vec3 b = vertexBase[index2]; + b3Vec3 c = vertexBase[index3]; + + b3Plane plane = b3MakePlaneFromPoints( a, b, c ); + + float maxDistance = 2.0f * tolerance; + int maxIndex = B3_NULL_INDEX; + + for ( int i = 0; i < vertexCount; ++i ) + { + if ( i == index1 || i == index2 || i == index3 ) + { + continue; + } + + float distance = b3AbsFloat( b3PlaneSeparation( plane, vertexBase[i] ) ); + if ( distance > maxDistance ) + { + maxDistance = distance; + maxIndex = i; + } + } + + return maxIndex; +} + +static bool b3IsEdgeConvex( const b3QHHalfEdge* edge, float tolerance ) +{ + float distance = b3PlaneSeparation( edge->face->plane, edge->twin->face->centroid ); + return distance < -tolerance; +} + +static bool b3IsEdgeConcave( const b3QHHalfEdge* edge, float tolerance ) +{ + float distance = b3PlaneSeparation( edge->face->plane, edge->twin->face->centroid ); + return distance > tolerance; +} + +static int b3VertexCountOfFace( const b3QHFace* face ) +{ + int count = 0; + const b3QHHalfEdge* edge = face->edge; + do + { + count++; + edge = edge->next; + } + while ( edge != face->edge ); + + return count; +} + +static void b3LinkFace( b3QHFace* face, int index, b3QHHalfEdge* twin ) +{ + B3_ASSERT( face != twin->face ); + + b3QHHalfEdge* edge = face->edge; + while ( index-- > 0 ) + { + B3_ASSERT( edge->face == face ); + edge = edge->next; + } + + B3_ASSERT( edge != twin ); + edge->twin = twin; + twin->twin = edge; +} + +static void b3LinkFaces( b3QHFace* face1, int index1, b3QHFace* face2, int index2 ) +{ + B3_ASSERT( face1 != face2 ); + + b3QHHalfEdge* edge1 = face1->edge; + while ( index1-- > 0 ) + { + edge1 = edge1->next; + } + + b3QHHalfEdge* edge2 = face2->edge; + while ( index2-- > 0 ) + { + edge2 = edge2->next; + } + + B3_ASSERT( edge1 != edge2 ); + edge1->twin = edge2; + edge2->twin = edge1; +} + +static void b3NewellPlane( b3QHFace* face ) +{ + int count = 0; + b3Vec3 centroid = b3Vec3_zero; + b3Vec3 normal = b3Vec3_zero; + + b3QHHalfEdge* edge = face->edge; + B3_ASSERT( edge->face == face ); + + // Use the first vertex as the origin to reduce round-off + b3Vec3 origin = edge->origin->position; + + do + { + b3QHHalfEdge* twin = edge->twin; + B3_ASSERT( twin->twin == edge ); + + b3Vec3 v1 = b3Sub( edge->origin->position, origin ); + b3Vec3 v2 = b3Sub( twin->origin->position, origin ); + + count++; + centroid = b3Add( centroid, v1 ); + normal.x += ( v1.y - v2.y ) * ( v1.z + v2.z ); + normal.y += ( v1.z - v2.z ) * ( v1.x + v2.x ); + normal.z += ( v1.x - v2.x ) * ( v1.y + v2.y ); + + edge = edge->next; + } + while ( edge != face->edge ); + + B3_ASSERT( count > 0 ); + centroid = b3MulSV( 1.0f / (float)count, centroid ); + centroid = b3Add( centroid, origin ); + + float length = b3Length( normal ); + B3_VALIDATE( length > 0.0f ); + normal = b3MulSV( 1.0f / length, normal ); + + face->centroid = centroid; + face->plane = b3MakePlaneFromNormalAndPoint( normal, centroid ); + face->area = 0.5f * length; +} + +#if B3_DEBUG +static bool b3CheckConsistency( const b3QHFace* face ) +{ + if ( face->mark == B3_MARK_DELETE ) + { + return false; + } + + if ( b3VertexCountOfFace( face ) < 3 ) + { + return false; + } + + const b3QHHalfEdge* edge = face->edge; + + do + { + const b3QHHalfEdge* twin = edge->twin; + + if ( twin == NULL ) + { + return false; + } + if ( twin->face == NULL ) + { + return false; + } + if ( twin->face == face ) + { + return false; + } + if ( twin->face->mark == B3_MARK_DELETE ) + { + return false; + } + if ( twin->twin != edge ) + { + return false; + } + if ( edge->next->origin != twin->origin ) + { + return false; + } + if ( edge->origin != twin->next->origin ) + { + return false; + } + if ( edge->face != face ) + { + return false; + } + + edge = edge->next; + } + while ( edge != face->edge ); + + return true; +} +#endif + +static void b3HullBuilder_ComputeTolerance( b3HullBuilder* b, int pointCount, const b3Vec3* points ) +{ + b3AABB bounds = b3BuildBounds( pointCount, points ); + b3Vec3 maxAbs = b3Max( b3Abs( bounds.lowerBound ), b3Abs( bounds.upperBound ) ); + + float maxSum = maxAbs.x + maxAbs.y + maxAbs.z; + float maxCoord = b3MaxFloat( maxAbs.x, b3MaxFloat( maxAbs.y, maxAbs.z ) ); + float maxDistance = b3MinFloat( B3_SQRT3 * maxCoord, maxSum ); + + float tolerance = ( 3.0f * maxDistance * 1.01f + maxCoord ) * FLT_EPSILON; + + b->tolerance = tolerance; + b->minRadius = 4.0f * b->tolerance; + b->minOutside = 2.0f * b->minRadius; + B3_ASSERT( b->minRadius < b->minOutside + 3.0f * FLT_EPSILON ); +} + +static bool b3HullBuilder_BuildInitialHull( b3HullBuilder* b, int pointCount, const b3Vec3* points ) +{ + int index1, index2; + b3FindFarthestPointsAlongCardinalAxes( &index1, &index2, b->tolerance, pointCount, points ); + if ( index1 < 0 || index2 < 0 ) + { + return false; + } + + int index3 = b3FindFarthestPointFromLine( index1, index2, b->tolerance, pointCount, points ); + if ( index3 < 0 ) + { + return false; + } + + int index4 = b3FindFarthestPointFromPlane( index1, index2, index3, b->tolerance, pointCount, points ); + if ( index4 < 0 ) + { + return false; + } + + b3Vec3 v1 = b3Sub( points[index1], points[index4] ); + b3Vec3 v2 = b3Sub( points[index2], points[index4] ); + b3Vec3 v3 = b3Sub( points[index3], points[index4] ); + + if ( b3ScalarTripleProduct( v1, v2, v3 ) < 0.0f ) + { + int temp = index2; + index2 = index3; + index3 = temp; + } + + b->interiorPoint = b3Vec3_zero; + b->interiorPoint = b3Add( b->interiorPoint, points[index1] ); + b->interiorPoint = b3Add( b->interiorPoint, points[index2] ); + b->interiorPoint = b3Add( b->interiorPoint, points[index3] ); + b->interiorPoint = b3Add( b->interiorPoint, points[index4] ); + b->interiorPoint = b3MulSV( 0.25f, b->interiorPoint ); + + b3QHVertex* vertex1 = b3HullBuilder_NewVertex( b, points[index1] ); + b3QHList_PushBack( &b->vertexList.link, &vertex1->link ); + b3QHVertex* vertex2 = b3HullBuilder_NewVertex( b, points[index2] ); + b3QHList_PushBack( &b->vertexList.link, &vertex2->link ); + b3QHVertex* vertex3 = b3HullBuilder_NewVertex( b, points[index3] ); + b3QHList_PushBack( &b->vertexList.link, &vertex3->link ); + b3QHVertex* vertex4 = b3HullBuilder_NewVertex( b, points[index4] ); + b3QHList_PushBack( &b->vertexList.link, &vertex4->link ); + + b3QHFace* face1 = b3HullBuilder_NewFace( b, vertex1, vertex2, vertex3 ); + b3QHList_PushBack( &b->faceList.link, &face1->link ); + b3QHFace* face2 = b3HullBuilder_NewFace( b, vertex4, vertex2, vertex1 ); + b3QHList_PushBack( &b->faceList.link, &face2->link ); + b3QHFace* face3 = b3HullBuilder_NewFace( b, vertex4, vertex3, vertex2 ); + b3QHList_PushBack( &b->faceList.link, &face3->link ); + b3QHFace* face4 = b3HullBuilder_NewFace( b, vertex4, vertex1, vertex3 ); + b3QHList_PushBack( &b->faceList.link, &face4->link ); + + b3LinkFaces( face1, 0, face2, 1 ); + b3LinkFaces( face1, 1, face3, 1 ); + b3LinkFaces( face1, 2, face4, 1 ); + + b3LinkFaces( face2, 0, face3, 2 ); + b3LinkFaces( face3, 0, face4, 2 ); + b3LinkFaces( face4, 0, face2, 2 ); + +#if B3_DEBUG + B3_ASSERT( b3CheckConsistency( face1 ) ); + B3_ASSERT( b3CheckConsistency( face2 ) ); + B3_ASSERT( b3CheckConsistency( face3 ) ); + B3_ASSERT( b3CheckConsistency( face4 ) ); +#endif + + for ( int index = 0; index < pointCount; ++index ) + { + if ( index == index1 || index == index2 || index == index3 || index == index4 ) + { + continue; + } + + b3Vec3 point = points[index]; + + float maxDistance = b->minOutside; + b3QHFace* maxFace = NULL; + + for ( b3QHListNode* node = b->faceList.link.next; node != &b->faceList.link; node = node->next ) + { + b3QHFace* face = (b3QHFace*)node; + float distance = b3PlaneSeparation( face->plane, point ); + if ( distance > maxDistance ) + { + maxDistance = distance; + maxFace = face; + } + } + + if ( maxFace != NULL ) + { + b3QHVertex* vertex = b3HullBuilder_NewVertex( b, point ); + vertex->conflictFace = maxFace; + b3QHList_PushBack( &maxFace->conflictListHead.link, &vertex->link ); + if ( maxDistance > maxFace->maxConflictDistance ) + { + maxFace->maxConflictDistance = maxDistance; + maxFace->maxConflict = vertex; + } + } + } + + return true; +} + +// Recompute the farthest-conflict cache after a face's plane changes. +// Walks the existing conflict list once; cost is bounded by that list, not the global pool. +static void b3HullBuilder_RecacheConflicts( b3QHFace* face, float minOutside ) +{ + b3QHVertex* maxVertex = NULL; + float maxDistance = minOutside; + + for ( b3QHListNode* node = face->conflictListHead.link.next; node != &face->conflictListHead.link; node = node->next ) + { + b3QHVertex* vertex = (b3QHVertex*)node; + float distance = b3PlaneSeparation( face->plane, vertex->position ); + if ( distance > maxDistance ) + { + maxDistance = distance; + maxVertex = vertex; + } + } + + face->maxConflict = maxVertex; + face->maxConflictDistance = maxDistance; +} + +static b3QHVertex* b3HullBuilder_NextConflictVertex( const b3HullBuilder* b ) +{ + b3QHVertex* maxVertex = NULL; + float maxDistance = b->minOutside; + + for ( const b3QHListNode* faceNode = b->faceList.link.next; faceNode != &b->faceList.link; faceNode = faceNode->next ) + { + const b3QHFace* face = (const b3QHFace*)faceNode; + if ( face->maxConflict != NULL && face->maxConflictDistance > maxDistance ) + { + maxDistance = face->maxConflictDistance; + maxVertex = face->maxConflict; + } + } + + return maxVertex; +} + +// Move every conflict vertex of `face` onto the orphaned list and clear their conflictFace. +static void b3HullBuilder_DrainConflictList( b3HullBuilder* b, b3QHFace* face ) +{ + b3QHListNode* node = face->conflictListHead.link.next; + while ( node != &face->conflictListHead.link ) + { + b3QHVertex* orphan = (b3QHVertex*)node; + node = node->next; + + orphan->conflictFace = NULL; + b3QHList_Remove( &orphan->link ); + b3QHList_PushBack( &b->orphanedList.link, &orphan->link ); + } + B3_ASSERT( B3_LIST_EMPTY( &face->conflictListHead.link ) ); +} + +// Mark a face for deletion, drain its conflict list, and populate a fresh DFS frame for it. +// `entryEdge` is the half-edge in `face` whose twin lies in the just-deleted parent face, or +// NULL for the seed. The frame skips `entryEdge` on recursive entries (it would be ignored +// anyway since the parent's mark is now DELETE, but skipping saves one iteration). +static void b3HullBuilder_EnterHorizonFace( b3HullBuilder* b, b3QHFace* face, b3QHHalfEdge* entryEdge, b3HorizonFrame* frameOut ) +{ + face->mark = B3_MARK_DELETE; + b3HullBuilder_DrainConflictList( b, face ); + + frameOut->face = face; + frameOut->started = false; + if ( entryEdge != NULL ) + { + frameOut->startEdge = entryEdge; + frameOut->edge = entryEdge->next; + } + else + { + frameOut->startEdge = face->edge; + frameOut->edge = face->edge; + } +} + +static void b3HullBuilder_BuildHorizon( b3HullBuilder* b, b3QHVertex* apex, b3QHFace* seed ) +{ + b3HorizonFrame* stack = b->horizonStack; + int top = 0; + + B3_ASSERT( top < b->horizonStackCapacity ); + b3HullBuilder_EnterHorizonFace( b, seed, NULL, &stack[top++] ); + + while ( top > 0 ) + { + b3HorizonFrame* f = &stack[top - 1]; + + if ( f->started && f->edge == f->startEdge ) + { + top--; + continue; + } + f->started = true; + + b3QHHalfEdge* edge = f->edge; + b3QHHalfEdge* twin = edge->twin; + f->edge = edge->next; + + if ( twin->face->mark != B3_MARK_VISIBLE ) + { + continue; + } + + float distance = b3PlaneSeparation( twin->face->plane, apex->position ); + if ( distance > b->minRadius ) + { + B3_ASSERT( top < b->horizonStackCapacity ); + b3HullBuilder_EnterHorizonFace( b, twin->face, twin, &stack[top++] ); + } + else + { + B3_ASSERT( b->horizonCount < b->horizonCapacity ); + b->horizon[b->horizonCount++] = edge; + } + } +} + +static void b3HullBuilder_BuildCone( b3HullBuilder* b, b3QHVertex* apex ) +{ + for ( int i = 0; i < b->horizonCount; ++i ) + { + b3QHHalfEdge* edge = b->horizon[i]; + B3_ASSERT( edge->twin->twin == edge ); + + b3QHFace* face = b3HullBuilder_NewFace( b, apex, edge->origin, edge->twin->origin ); + B3_ASSERT( b->coneCount < b->coneCapacity ); + b->cone[b->coneCount++] = face; + + b3LinkFace( face, 1, edge->twin ); + } + + b3QHFace* face1 = b->cone[b->coneCount - 1]; + for ( int i = 0; i < b->coneCount; ++i ) + { + b3QHFace* face2 = b->cone[i]; + b3LinkFaces( face1, 2, face2, 0 ); + face1 = face2; + } +} + +// Retire half-edges in the half-open ring range [begin, end) by pushing each onto edgeFreeHead. +// The caller has already detached these edges from the live face ring (rewire happens before +// destroy), so they are unreachable from the live hull. +static void b3HullBuilder_DestroyEdges( b3HullBuilder* b, b3QHHalfEdge* begin, b3QHHalfEdge* end ) +{ + b3QHHalfEdge* edge = begin; + while ( edge != end ) + { + b3QHHalfEdge* next = edge->next; + b3HullBuilder_RetireEdge( b, edge ); + edge = next; + } +} + +static void b3HullBuilder_ConnectEdges( b3HullBuilder* b, b3QHHalfEdge* prev, b3QHHalfEdge* next ) +{ + B3_ASSERT( prev != next ); + B3_ASSERT( prev->face == next->face ); + + // If both shared neighbors are the same face, prev and next together would orphan that face. + if ( prev->twin->face == next->twin->face ) + { + // next is redundant. + if ( next->face->edge == next ) + { + next->face->edge = prev; + } + + b3QHHalfEdge* twin; + if ( b3VertexCountOfFace( prev->twin->face ) == 3 ) + { + // Capture all 3 half-edges of the dead triangle before the rewire overwrites prev->twin. + b3QHHalfEdge* deadEdge0 = prev->twin; // prev->twin (will be rewired below) + b3QHHalfEdge* deadEdge1 = next->twin; // next->twin + b3QHHalfEdge* deadEdge2 = next->twin->prev; // third edge of the dead triangle + + twin = deadEdge2->twin; + B3_ASSERT( twin->face->mark != B3_MARK_DELETE ); + + b3QHFace* opposingFace = prev->twin->face; + opposingFace->mark = B3_MARK_DELETE; + B3_ASSERT( b->mergedFacesCount < b->mergedFacesCapacity ); + b->mergedFaces[b->mergedFacesCount++] = opposingFace; + + prev->next = next->next; + prev->next->prev = prev; + + prev->twin = twin; + twin->twin = prev; + + // Drop the redundant vertex (slot abandoned in the bump allocator). + b3QHList_Remove( &next->origin->link ); + + // Retire the 3 half-edges of the dead triangle now that the rewire is complete. + b3HullBuilder_RetireEdge( b, deadEdge0 ); + b3HullBuilder_RetireEdge( b, deadEdge1 ); + b3HullBuilder_RetireEdge( b, deadEdge2 ); + } + else + { + twin = next->twin; + + if ( twin->face->edge == prev->twin ) + { + twin->face->edge = twin; + } + + twin->next = prev->twin->next; + twin->next->prev = twin; + // prev->twin slot is retired to the edge free list. + b3HullBuilder_RetireEdge( b, prev->twin ); + + prev->next = next->next; + prev->next->prev = prev; + + prev->twin = twin; + twin->twin = prev; + + // Drop the redundant vertex (slot abandoned in the bump allocator). + b3QHList_Remove( &next->origin->link ); + } + + // Twin->face changed shape; recompute its plane and refresh its cached max conflict. + b3NewellPlane( twin->face ); + b3HullBuilder_RecacheConflicts( twin->face, b->minOutside ); + } + else + { + prev->next = next; + next->prev = prev; + } +} + +static void b3HullBuilder_AbsorbFaces( b3HullBuilder* b, b3QHFace* face ) +{ + for ( int i = 0; i < b->mergedFacesCount; ++i ) + { + B3_ASSERT( b->mergedFaces[i]->mark == B3_MARK_DELETE ); + b3QHListNode* head = &b->mergedFaces[i]->conflictListHead.link; + + b3QHListNode* node = head->next; + while ( node != head ) + { + b3QHVertex* vertex = (b3QHVertex*)node; + node = node->next; + + b3QHList_Remove( &vertex->link ); + + float distance = b3PlaneSeparation( face->plane, vertex->position ); + if ( distance > b->minOutside ) + { + b3QHList_PushBack( &face->conflictListHead.link, &vertex->link ); + vertex->conflictFace = face; + if ( distance > face->maxConflictDistance ) + { + face->maxConflictDistance = distance; + face->maxConflict = vertex; + } + } + else + { + b3QHList_PushBack( &b->orphanedList.link, &vertex->link ); + vertex->conflictFace = NULL; + } + } + + B3_ASSERT( B3_LIST_EMPTY( head ) ); + + // Conflict list is now drained. Retire this face to the free list. + b3HullBuilder_RetireFace( b, b->mergedFaces[i] ); + } +} + +static void b3HullBuilder_ConnectFaces( b3HullBuilder* b, b3QHHalfEdge* edge ) +{ + b3QHFace* face = edge->face; + + b3QHHalfEdge* twin = edge->twin; + + b3QHHalfEdge* edgePrev = edge->prev; + b3QHHalfEdge* edgeNext = edge->next; + b3QHHalfEdge* twinPrev = twin->prev; + b3QHHalfEdge* twinNext = twin->next; + + while ( edgePrev->twin->face == twin->face ) + { + B3_ASSERT( edgePrev->twin == twinNext ); + B3_ASSERT( twinNext->twin == edgePrev ); + + edgePrev = edgePrev->prev; + twinNext = twinNext->next; + } + B3_ASSERT( edgePrev->face != twinNext->face ); + + while ( edgeNext->twin->face == twin->face ) + { + B3_ASSERT( edgeNext->twin == twinPrev ); + B3_ASSERT( twinPrev->twin == edgeNext ); + + edgeNext = edgeNext->next; + twinPrev = twinPrev->prev; + } + B3_ASSERT( edgeNext->face != twinPrev->face ); + + face->edge = edgePrev; + + // Discard opposing face. mergedFaces is single-buffered: ConnectFaces does not nest. + b->mergedFacesCount = 0; + B3_ASSERT( b->mergedFacesCount < b->mergedFacesCapacity ); + b->mergedFaces[b->mergedFacesCount++] = twin->face; + twin->face->mark = B3_MARK_DELETE; + twin->face->edge = NULL; + + for ( b3QHHalfEdge* absorbed = twinNext; absorbed != twinPrev->next; absorbed = absorbed->next ) + { + absorbed->face = face; + } + + b3HullBuilder_DestroyEdges( b, edgePrev->next, edgeNext ); + b3HullBuilder_DestroyEdges( b, twinPrev->next, twinNext ); + + b3HullBuilder_ConnectEdges( b, edgePrev, twinNext ); + b3HullBuilder_ConnectEdges( b, twinPrev, edgeNext ); + + b3NewellPlane( face ); + // Existing conflicts now have stale distances under the new plane; AbsorbFaces will then + // add more incrementally and update the cache as it goes. + b3HullBuilder_RecacheConflicts( face, b->minOutside ); +#if B3_DEBUG + B3_ASSERT( b3CheckConsistency( face ) ); +#endif + + b3HullBuilder_AbsorbFaces( b, face ); +} + +static bool b3HullBuilder_MergeConcave( b3HullBuilder* b, b3QHFace* face ) +{ + b3QHHalfEdge* edge = face->edge; + + do + { + b3QHHalfEdge* twin = edge->twin; + + if ( b3IsEdgeConcave( edge, b->minRadius ) || b3IsEdgeConcave( twin, b->minRadius ) ) + { + b3HullBuilder_ConnectFaces( b, edge ); + return true; + } + + edge = edge->next; + } + while ( edge != face->edge ); + + return false; +} + +static bool b3HullBuilder_MergeCoplanar( b3HullBuilder* b, b3QHFace* face ) +{ + b3QHHalfEdge* edge = face->edge; + + do + { + b3QHHalfEdge* twin = edge->twin; + + if ( !b3IsEdgeConvex( edge, b->minRadius ) || !b3IsEdgeConvex( twin, b->minRadius ) ) + { + b3HullBuilder_ConnectFaces( b, edge ); + return true; + } + + edge = edge->next; + } + while ( edge != face->edge ); + + return false; +} + +static void b3HullBuilder_MergeFaces( b3HullBuilder* b ) +{ + for ( int i = 0; i < b->coneCount; ++i ) + { + b3QHFace* face = b->cone[i]; + if ( face->mark == B3_MARK_VISIBLE && face->flipped ) + { + face->flipped = false; + + float bestArea = 0; + b3QHHalfEdge* bestEdge = NULL; + + b3QHHalfEdge* edge = face->edge; + do + { + b3QHHalfEdge* twin = edge->twin; + float area = twin->face->area; + if ( area > bestArea ) + { + bestArea = area; + bestEdge = edge; + } + + edge = edge->next; + } + while ( edge != face->edge ); + + B3_ASSERT( bestEdge != NULL ); + b3HullBuilder_ConnectFaces( b, bestEdge ); + } + } + + for ( int i = 0; i < b->coneCount; ++i ) + { + b3QHFace* face = b->cone[i]; + if ( face->mark == B3_MARK_VISIBLE ) + { + while ( b3HullBuilder_MergeConcave( b, face ) ) + { + } + } + } + + for ( int i = 0; i < b->coneCount; ++i ) + { + b3QHFace* face = b->cone[i]; + if ( face->mark == B3_MARK_VISIBLE ) + { + while ( b3HullBuilder_MergeCoplanar( b, face ) ) + { + } + } + } +} + +static void b3HullBuilder_ResolveVertices( b3HullBuilder* b ) +{ + b3QHListNode* node = b->orphanedList.link.next; + while ( node != &b->orphanedList.link ) + { + b3QHVertex* vertex = (b3QHVertex*)node; + node = node->next; + b3QHList_Remove( &vertex->link ); + + float maxDistance = b->minOutside; + b3QHFace* maxFace = NULL; + + for ( int i = 0; i < b->coneCount; ++i ) + { + if ( b->cone[i]->mark == B3_MARK_VISIBLE ) + { + float distance = b3PlaneSeparation( b->cone[i]->plane, vertex->position ); + if ( distance > maxDistance ) + { + maxDistance = distance; + maxFace = b->cone[i]; + } + } + } + + if ( maxFace != NULL ) + { + B3_ASSERT( maxFace->mark == B3_MARK_VISIBLE ); + b3QHList_PushBack( &maxFace->conflictListHead.link, &vertex->link ); + vertex->conflictFace = maxFace; + if ( maxDistance > maxFace->maxConflictDistance ) + { + maxFace->maxConflictDistance = maxDistance; + maxFace->maxConflict = vertex; + } + } + // Otherwise: vertex is interior to the hull. Its slot in the bump pool is abandoned. + } + + B3_ASSERT( B3_LIST_EMPTY( &b->orphanedList.link ) ); +} + +static void b3HullBuilder_ResolveFaces( b3HullBuilder* b ) +{ + // Splice deleted faces out of the face list. Faces already retired by AbsorbFaces are no + // longer on faceList, so we guard with b3QHList_Contains before removing. + b3QHListNode* node = b->faceList.link.next; + while ( node != &b->faceList.link ) + { + b3QHFace* face = (b3QHFace*)node; + node = node->next; + + if ( face->mark == B3_MARK_DELETE && b3QHList_Contains( &face->link ) ) + { + B3_ASSERT( B3_LIST_EMPTY( &face->conflictListHead.link ) ); + b3QHList_Remove( &face->link ); + } + } + + for ( int i = 0; i < b->coneCount; ++i ) + { + b3QHFace* face = b->cone[i]; + if ( face->mark == B3_MARK_DELETE ) + { + continue; + } + b3QHList_PushBack( &b->faceList.link, &face->link ); + } +} + +static void b3HullBuilder_AddVertexToHull( b3HullBuilder* b, b3QHVertex* vertex ) +{ + b3QHFace* face = vertex->conflictFace; + vertex->conflictFace = NULL; + b3QHList_Remove( &vertex->link ); + b3QHList_PushBack( &b->vertexList.link, &vertex->link ); + + b->horizonCount = 0; + b3HullBuilder_BuildHorizon( b, vertex, face ); + B3_ASSERT( b->horizonCount >= 3 ); + + b->coneCount = 0; + b3HullBuilder_BuildCone( b, vertex ); + B3_ASSERT( b->coneCount >= 3 ); + + b3HullBuilder_MergeFaces( b ); + b3HullBuilder_ResolveVertices( b ); + b3HullBuilder_ResolveFaces( b ); +} + +static void b3HullBuilder_CleanHull( b3HullBuilder* b, b3Vec3 origin ) +{ + int faceCount = 0; + int halfEdgeCount = 0; + + for ( b3QHListNode* faceNode = b->faceList.link.next; faceNode != &b->faceList.link; faceNode = faceNode->next ) + { + b3QHFace* face = (b3QHFace*)faceNode; + b3QHHalfEdge* edge = face->edge; + + do + { + edge->origin->reachable = true; + edge = edge->next; + halfEdgeCount++; + } + while ( edge != face->edge ); + + face->plane.offset += b3Dot( face->plane.normal, origin ); + face->centroid = b3Add( face->centroid, origin ); + faceCount++; + } + + int vertexCount = 0; + b3QHListNode* node = b->vertexList.link.next; + while ( node != &b->vertexList.link ) + { + b3QHVertex* vertex = (b3QHVertex*)node; + node = node->next; + + if ( !vertex->reachable ) + { + b3QHList_Remove( &vertex->link ); + } + else + { + vertex->position = b3Add( vertex->position, origin ); + vertexCount++; + } + } + + b->interiorPoint = b3Add( b->interiorPoint, origin ); + + b->finalVertexCount = vertexCount; + b->finalHalfEdgeCount = halfEdgeCount; + b->finalFaceCount = faceCount; +} + +#if B3_DEBUG +static bool b3HullBuilder_IsConsistent( const b3HullBuilder* b ) +{ + int v = b->finalVertexCount; + int e = b->finalHalfEdgeCount / 2; + int f = b->finalFaceCount; + + if ( v - e + f != 2 ) + { + return false; + } + + for ( const b3QHListNode* faceNode = b->faceList.link.next; faceNode != &b->faceList.link; faceNode = faceNode->next ) + { + const b3QHFace* face = (const b3QHFace*)faceNode; + if ( face->edge->face != face ) + { + return false; + } + + if ( !b3CheckConsistency( face ) ) + { + return false; + } + + if ( b3PlaneSeparation( face->plane, b->interiorPoint ) > 0 ) + { + return false; + } + + if ( face->mark != B3_MARK_VISIBLE ) + { + return false; + } + + const b3QHHalfEdge* edge = face->edge; + + do + { + if ( edge->next->origin != edge->twin->origin ) + { + return false; + } + if ( edge->prev->next != edge ) + { + return false; + } + if ( edge->next->prev != edge ) + { + return false; + } + if ( edge->twin->twin != edge ) + { + return false; + } + if ( edge->face != face ) + { + return false; + } + if ( b3DistanceSquared( edge->origin->position, edge->twin->origin->position ) < 1000.0f * FLT_MIN ) + { + return false; + } + + edge = edge->next; + } + while ( edge != face->edge ); + } + + return true; +} +#endif + +static bool b3HullBuilder_HasHull( const b3HullBuilder* b ) +{ + int v = b->finalVertexCount; + int e = b->finalHalfEdgeCount / 2; + int f = b->finalFaceCount; + return v - e + f == 2 && f >= 4; +} + +// Build the entire hull. Returns true iff the result satisfies Euler's identity. +static bool b3HullBuilder_Construct( b3HullBuilder* b, const b3Vec3* points, int pointCount, int maxVertexCount, b3Vec3 origin, + b3Vec3* shiftedPoints ) +{ + if ( pointCount < 4 ) + { + return false; + } + + for ( int i = 0; i < pointCount; ++i ) + { + shiftedPoints[i] = b3Sub( points[i], origin ); + } + + b3HullBuilder_ComputeTolerance( b, pointCount, shiftedPoints ); + if ( !b3HullBuilder_BuildInitialHull( b, pointCount, shiftedPoints ) ) + { + return false; + } + + int budget = b3ClampInt( maxVertexCount - 4, 0, B3_HULL_LIMIT - 4 ); + + b3QHVertex* vertex = b3HullBuilder_NextConflictVertex( b ); + while ( vertex && budget > 0 ) + { + b3HullBuilder_AddVertexToHull( b, vertex ); + vertex = b3HullBuilder_NextConflictVertex( b ); + budget -= 1; + } + + b3HullBuilder_CleanHull( b, origin ); + +#if B3_DEBUG + B3_ASSERT( b3HullBuilder_IsConsistent( b ) ); +#endif + + return b3HullBuilder_HasHull( b ); +} + +typedef struct b3HullWorkSizes +{ + int N; // pointCount + int M; // clamped maxVertexCount, in [4, B3_HULL_LIMIT] + int vertexCapacity; + int edgeCapacity; + int faceCapacity; + int horizonCapacity; + int coneCapacity; + int mergedFacesCapacity; + int horizonStackCapacity; + size_t totalBytes; + + size_t offsetVertex; + size_t offsetEdge; + size_t offsetFace; + size_t offsetHorizon; + size_t offsetCone; + size_t offsetMergedFaces; + size_t offsetHorizonStack; + size_t offsetShiftedPoints; +} b3HullWorkSizes; + +static b3HullWorkSizes b3ComputeHullWorkSizes( int pointCount, int clampedMaxCount ) +{ + b3HullWorkSizes s; + s.N = pointCount; + s.M = clampedMaxCount; + + // Vertices: 4 initial hull vertices + at most one per remaining input point. No free list. + s.vertexCapacity = pointCount + 4; + + // Edges and faces use free-list recycling; capacity is proportional to live hull size. + // edgeCapacity: peak is ~twice live edges plus cone edges; floor 48. + s.edgeCapacity = 24 * s.M - 48; + if ( s.edgeCapacity < 48 ) + { + s.edgeCapacity = 48; + } + + // faceCapacity: peak intermediate state live faces (<=2*M-4) plus full cone (<=3*M-6); floor 16. + s.faceCapacity = 5 * s.M - 10; + if ( s.faceCapacity < 16 ) + { + s.faceCapacity = 16; + } + + // Horizon/cone bounded by current half-edge count; mergedFaces by face count. + s.horizonCapacity = 3 * s.M - 6; + if ( s.horizonCapacity < 6 ) + { + s.horizonCapacity = 6; + } + s.coneCapacity = s.horizonCapacity; + s.mergedFacesCapacity = 2 * s.M - 4; + if ( s.mergedFacesCapacity < 4 ) + { + s.mergedFacesCapacity = 4; + } + + // Horizon DFS depth is bounded by the number of live faces (Euler: <=2*M-4). + s.horizonStackCapacity = 2 * s.M - 4; + if ( s.horizonStackCapacity < 4 ) + { + s.horizonStackCapacity = 4; + } + + size_t offset = 0; + + s.offsetVertex = offset; + offset = b3AlignUp8( offset + (size_t)s.vertexCapacity * sizeof( b3QHVertex ) ); + + s.offsetEdge = offset; + offset = b3AlignUp8( offset + (size_t)s.edgeCapacity * sizeof( b3QHHalfEdge ) ); + + s.offsetFace = offset; + offset = b3AlignUp8( offset + (size_t)s.faceCapacity * sizeof( b3QHFace ) ); + + s.offsetHorizon = offset; + offset = b3AlignUp8( offset + (size_t)s.horizonCapacity * sizeof( b3QHHalfEdge* ) ); + + s.offsetCone = offset; + offset = b3AlignUp8( offset + (size_t)s.coneCapacity * sizeof( b3QHFace* ) ); + + s.offsetMergedFaces = offset; + offset = b3AlignUp8( offset + (size_t)s.mergedFacesCapacity * sizeof( b3QHFace* ) ); + + s.offsetHorizonStack = offset; + offset = b3AlignUp8( offset + (size_t)s.horizonStackCapacity * sizeof( b3HorizonFrame ) ); + + s.offsetShiftedPoints = offset; + offset += (size_t)pointCount * sizeof( b3Vec3 ); + + s.totalBytes = offset; + return s; +} + +static void b3HullBuilder_Init( b3HullBuilder* b, char* mem, const b3HullWorkSizes* s ) +{ + memset( b, 0, sizeof( *b ) ); + b3QHList_Init( &b->orphanedList.link ); + b3QHList_Init( &b->vertexList.link ); + b3QHList_Init( &b->faceList.link ); + + b->vertexBase = (b3QHVertex*)( mem + s->offsetVertex ); + b->vertexCapacity = s->vertexCapacity; + + b->edgeBase = (b3QHHalfEdge*)( mem + s->offsetEdge ); + b->edgeCapacity = s->edgeCapacity; + + b->faceBase = (b3QHFace*)( mem + s->offsetFace ); + b->faceCapacity = s->faceCapacity; + + b->horizon = (b3QHHalfEdge**)( mem + s->offsetHorizon ); + b->horizonCapacity = s->horizonCapacity; + + b->cone = (b3QHFace**)( mem + s->offsetCone ); + b->coneCapacity = s->coneCapacity; + + b->mergedFaces = (b3QHFace**)( mem + s->offsetMergedFaces ); + b->mergedFacesCapacity = s->mergedFacesCapacity; + + b->horizonStack = (b3HorizonFrame*)( mem + s->offsetHorizonStack ); + b->horizonStackCapacity = s->horizonStackCapacity; +} + +static b3Vec3* b3GetHullPointsWrite( b3HullData* hull ) +{ + if ( hull->pointOffset == 0 ) + { + return NULL; + } + return (b3Vec3*)( (intptr_t)hull + hull->pointOffset ); +} + +static b3Plane* b3GetHullPlanesWrite( b3HullData* hull ) +{ + if ( hull->planeOffset == 0 ) + { + return NULL; + } + return (b3Plane*)( (intptr_t)hull + hull->planeOffset ); +} + +static b3HullVertex* b3GetHullVerticesWrite( b3HullData* hull ) +{ + if ( hull->vertexOffset == 0 ) + { + return NULL; + } + return (b3HullVertex*)( (intptr_t)hull + hull->vertexOffset ); +} + +static b3HullHalfEdge* b3GetHullEdgesWrite( b3HullData* hull ) +{ + if ( hull->edgeOffset == 0 ) + { + return NULL; + } + return (b3HullHalfEdge*)( (intptr_t)hull + hull->edgeOffset ); +} + +int b3FindHullSupportVertex( const b3HullData* hull, b3Vec3 direction ) +{ + int bestIndex = B3_NULL_INDEX; + float bestDot = -FLT_MAX; + + int vertexCount = hull->vertexCount; + const b3Vec3* points = b3GetHullPoints( hull ); + + for ( int index = 0; index < vertexCount; ++index ) + { + float dot = b3Dot( direction, points[index] ); + if ( dot > bestDot ) + { + bestIndex = index; + bestDot = dot; + } + } + B3_ASSERT( bestIndex >= 0 ); + + return bestIndex; +} + +int b3FindHullSupportFace( const b3HullData* hull, b3Vec3 direction ) +{ + int bestIndex = B3_NULL_INDEX; + float bestDot = -FLT_MAX; + + int faceCount = hull->faceCount; + const b3Plane* planes = b3GetHullPlanes( hull ); + + for ( int index = 0; index < faceCount; ++index ) + { + float dot = b3Dot( planes[index].normal, direction ); + if ( dot > bestDot ) + { + bestDot = dot; + bestIndex = index; + } + } + B3_ASSERT( bestIndex >= 0 ); + + return bestIndex; +} + +#if B3_ENABLE_VALIDATION + +bool b3IsValidHull( const b3HullData* hull ) +{ + if ( hull->version != B3_HULL_VERSION ) + { + return false; + } + + int v = hull->vertexCount; + int e = hull->edgeCount / 2; + int f = hull->faceCount; + + if ( v - e + f != 2 ) + { + return false; + } + + const b3HullVertex* vertices = b3GetHullVertices( hull ); + const b3HullHalfEdge* edges = b3GetHullEdges( hull ); + for ( int index = 0; index < hull->vertexCount; ++index ) + { + const b3HullVertex* vertex = vertices + index; + const b3HullHalfEdge* edge = edges + vertex->edge; + + if ( edge->origin != index ) + { + return false; + } + } + + for ( int index = 0; index < hull->edgeCount; index += 2 ) + { + const b3HullHalfEdge* edge = edges + index + 0; + const b3HullHalfEdge* twin = edges + index + 1; + + if ( edge->twin != index + 1 ) + { + return false; + } + + if ( twin->twin != index + 0 ) + { + return false; + } + } + + const b3HullFace* faces = b3GetHullFaces( hull ); + const b3Plane* planes = b3GetHullPlanes( hull ); + for ( int faceIndex = 0; faceIndex < hull->faceCount; ++faceIndex ) + { + const b3HullFace* face = faces + faceIndex; + + int baseEdgeIndex = face->edge; + const b3HullHalfEdge* edge = edges + baseEdgeIndex; + + b3Plane plane = planes[faceIndex]; + if ( b3PlaneSeparation( plane, hull->center ) >= 0.0f ) + { + return false; + } + + int edgeIndex = baseEdgeIndex; + do + { + edge = edges + edgeIndex; + const b3HullHalfEdge* next = edges + edge->next; + const b3HullHalfEdge* twin = edges + edge->twin; + + if ( edge->face != faceIndex ) + { + return false; + } + + if ( twin->twin != edgeIndex ) + { + return false; + } + + if ( next->origin != twin->origin ) + { + return false; + } + + edgeIndex = edge->next; + } + while ( edgeIndex != baseEdgeIndex ); + } + + if ( hull->volume <= 0.0f ) + { + return false; + } + + if ( hull->surfaceArea <= 0.0f ) + { + return false; + } + + if ( hull->innerRadius <= 0.0f ) + { + return false; + } + + return true; +} + +#else + +bool b3IsValidHull( const b3HullData* hull ) +{ + B3_UNUSED( hull ); + return true; +} + +#endif + +b3HullData* b3CreateCylinder( float height, float radius, float yOffset, int sides ) +{ + B3_ASSERT( height > 0.0f ); + B3_ASSERT( radius > 0.0f ); + B3_ASSERT( 3 <= sides && sides <= 32 ); + + int pointCount = 2 * sides; + b3Vec3* points = (b3Vec3*)b3Alloc( pointCount * sizeof( b3Vec3 ) ); + B3_ASSERT( points != NULL ); + + float alpha = 0.0f; + float deltaAlpha = 2.0f * B3_PI / sides; + + for ( int index = 0; index < sides; ++index ) + { + float sinAlpha = b3Sin( alpha ); + float cosAlpha = b3Cos( alpha ); + + points[2 * index + 0] = (b3Vec3){ radius * cosAlpha, yOffset, radius * sinAlpha }; + points[2 * index + 1] = (b3Vec3){ radius * cosAlpha, yOffset + height, radius * sinAlpha }; + + alpha += deltaAlpha; + } + + b3HullData* hull = b3CreateHull( points, pointCount, pointCount ); + B3_ASSERT( hull->vertexCount == pointCount ); + B3_ASSERT( hull->edgeCount == 6 * sides ); + B3_ASSERT( hull->faceCount == sides + 2 ); + + b3Free( points, pointCount * sizeof( b3Vec3 ) ); + + return hull; +} + +b3HullData* b3CreateCone( float height, float radius1, float radius2, int slices ) +{ + B3_ASSERT( height > 0.0f ); + B3_ASSERT( radius1 > 0.0f ); + B3_ASSERT( radius2 > 0.0f ); + B3_ASSERT( 4 <= slices && slices <= 32 ); + + int pointCount = 2 * slices; + b3Vec3* points = (b3Vec3*)b3Alloc( pointCount * sizeof( b3Vec3 ) ); + B3_ASSERT( points != NULL ); + + float alpha = 0.0f; + float deltaAlpha = 2.0f * B3_PI / slices; + + for ( int index = 0; index < slices; ++index ) + { + float sinAlpha = b3Sin( alpha ); + float cosAlpha = b3Cos( alpha ); + + points[2 * index + 0] = (b3Vec3){ radius1 * cosAlpha, 0.0f, radius1 * sinAlpha }; + points[2 * index + 1] = (b3Vec3){ radius2 * cosAlpha, height, radius2 * sinAlpha }; + + alpha += deltaAlpha; + } + + b3HullData* hull = b3CreateHull( points, pointCount, pointCount ); + B3_ASSERT( hull->vertexCount == pointCount ); + B3_ASSERT( hull->edgeCount == 6 * slices ); + B3_ASSERT( hull->faceCount == slices + 2 ); + + b3Free( points, pointCount * sizeof( b3Vec3 ) ); + + return hull; +} + +b3HullData* b3CreateRock( float radius ) +{ + int pointCount = 10; + + // Golden ratio + const float phi = ( 1.0f + sqrtf( 5.0f ) ) / 2.0f; + + // Fibonacci lattice + b3Vec3 points[10]; + + // Azimuthal angle + float theta = 2.0f * B3_PI / phi; + + b3CosSin cs = { 1.0f, 0.0 }; + b3CosSin deltaCS = b3ComputeCosSin( theta ); + + for ( int i = 0; i < pointCount; ++i ) + { + // Z coordinate + float z = 1.0f - ( 2.0f * i + 1.0f ) / pointCount; + // Radius in xy-plane + float radius_XY = sqrtf( 1.0f - z * z ); + + points[i].x = radius * radius_XY * cs.cosine; + points[i].y = radius * radius_XY * cs.sine; + points[i].z = radius * z; + + b3CosSin cs0 = cs; + cs.cosine = deltaCS.cosine * cs0.cosine - deltaCS.sine * cs0.sine; + cs.sine = deltaCS.sine * cs0.cosine + deltaCS.cosine * cs0.sine; + } + + return b3CreateHull( points, pointCount, pointCount ); +} + +static void b3UpdateHullBounds( b3HullData* hull ) +{ + const b3Vec3* points = b3GetHullPoints( hull ); + int vertexCount = hull->vertexCount; + + B3_ASSERT( vertexCount > 0 ); + b3AABB bounds; + bounds.lowerBound = points[0]; + bounds.upperBound = points[0]; + + for ( int i = 1; i < vertexCount; ++i ) + { + b3Vec3 p = points[i]; + bounds.lowerBound = b3Min( bounds.lowerBound, p ); + bounds.upperBound = b3Max( bounds.upperBound, p ); + } + + hull->aabb = bounds; +} + +// M. Kallay - "Computing the Moment of Inertia of a Solid Defined by a Triangle Mesh" +static bool b3UpdateHullBulkProperties( b3HullData* hull ) +{ + const b3Vec3* points = b3GetHullPoints( hull ); + const b3HullFace* faces = b3GetHullFaces( hull ); + const b3HullHalfEdge* edges = b3GetHullEdges( hull ); + const b3Plane* planes = b3GetHullPlanes( hull ); + + float area = 0.0f; + float volume = 0.0f; + b3Vec3 center = b3Vec3_zero; + + // Use the first vertex to reduce round-off errors. + b3Vec3 origin = points[0]; + + float xx = 0.0f; + float xy = 0.0f; + float yy = 0.0f; + float xz = 0.0f; + float zz = 0.0f; + float yz = 0.0f; + + int faceCount = hull->faceCount; + + for ( int faceIndex = 0; faceIndex < faceCount; ++faceIndex ) + { + const b3HullFace* face = faces + faceIndex; + const b3HullHalfEdge* edge1 = edges + face->edge; + const b3HullHalfEdge* edge2 = edges + edge1->next; + const b3HullHalfEdge* edge3 = edges + edge2->next; + + B3_ASSERT( edge1 != edge3 ); + B3_ASSERT( edge1->origin < hull->vertexCount ); + + b3Vec3 v1 = b3Sub( points[edge1->origin], origin ); + + do + { + B3_ASSERT( edge2->origin < hull->vertexCount ); + B3_ASSERT( edge3->origin < hull->vertexCount ); + + b3Vec3 v2 = b3Sub( points[edge2->origin], origin ); + b3Vec3 v3 = b3Sub( points[edge3->origin], origin ); + + area += b3Length( b3Cross( b3Sub( v2, v1 ), b3Sub( v3, v1 ) ) ); + + float det = b3ScalarTripleProduct( v1, v2, v3 ); + + volume += det; + + b3Vec3 v4 = b3Add( v1, b3Add( v2, v3 ) ); + center = b3Add( center, b3MulSV( det, v4 ) ); + + xx += det * ( v1.x * v1.x + v2.x * v2.x + v3.x * v3.x + v4.x * v4.x ); + yy += det * ( v1.y * v1.y + v2.y * v2.y + v3.y * v3.y + v4.y * v4.y ); + zz += det * ( v1.z * v1.z + v2.z * v2.z + v3.z * v3.z + v4.z * v4.z ); + xy += det * ( v1.x * v1.y + v2.x * v2.y + v3.x * v3.y + v4.x * v4.y ); + xz += det * ( v1.x * v1.z + v2.x * v2.z + v3.x * v3.z + v4.x * v4.z ); + yz += det * ( v1.y * v1.z + v2.y * v2.z + v3.y * v3.z + v4.y * v4.z ); + + edge2 = edge3; + edge3 = edges + edge3->next; + } + while ( edge1 != edge3 ); + } + + B3_VALIDATE( volume > 0.0f ); + + b3Vec3 localCenter = volume > 0.0f ? b3MulSV( 0.25f / volume, center ) : b3Vec3_zero; + center = b3Add( localCenter, origin ); + + float radius = FLT_MAX; + for ( int faceIndex = 0; faceIndex < faceCount; ++faceIndex ) + { + b3Plane plane = planes[faceIndex]; + float distance = b3PlaneSeparation( plane, center ); + B3_VALIDATE( distance < 0.0f ); + + radius = b3MinFloat( radius, -distance ); + } + + B3_VALIDATE( 0.0f < radius && radius < FLT_MAX ); + + b3Matrix3 inertia; + inertia.cx.x = yy + zz; + inertia.cy.x = -xy; + inertia.cz.x = -xz; + inertia.cx.y = -xy; + inertia.cy.y = xx + zz; + inertia.cz.y = -yz; + inertia.cx.z = -xz; + inertia.cy.z = -yz; + inertia.cz.z = xx + yy; + + float mass = volume / 6.0f; + + b3Matrix3 centralInertia = b3MulSM( 1.0f / 120.0f, inertia ); + centralInertia = b3SubMM( centralInertia, b3Steiner( mass, localCenter ) ); + + hull->center = center; + hull->centralInertia = centralInertia; + hull->volume = mass; + hull->surfaceArea = 0.5f * area; + hull->innerRadius = radius; + + if ( mass <= 0.0f ) + { + return false; + } + + if ( volume <= 0.0f ) + { + return false; + } + + if ( area <= 0.0f ) + { + return false; + } + + if ( radius <= 0.0f ) + { + return false; + } + + return true; +} + +b3HullData* b3CreateHull( const b3Vec3* points, int pointCount, int maxVertexCount ) +{ + if ( pointCount < 4 ) + { + return NULL; + } + + b3Vec3 origin = points[0]; + int clampedMaxCount = b3ClampInt( maxVertexCount, 4, B3_HULL_LIMIT ); + + // Single allocation for all working memory. + b3HullWorkSizes sizes = b3ComputeHullWorkSizes( pointCount, clampedMaxCount ); + char* work = b3Alloc( sizes.totalBytes ); + + b3HullBuilder builder; + b3HullBuilder_Init( &builder, work, &sizes ); + + b3Vec3* shiftedPoints = (b3Vec3*)( work + sizes.offsetShiftedPoints ); + + bool ok = b3HullBuilder_Construct( &builder, points, pointCount, clampedMaxCount, origin, shiftedPoints ); + if ( !ok ) + { + b3Free( work, sizes.totalBytes ); + return NULL; + } + + if ( builder.finalVertexCount >= B3_HULL_LIMIT ) + { + b3Log( "hull final vertex count of %d exceeds limit of %d", builder.finalVertexCount, B3_HULL_LIMIT ); + b3Free( work, sizes.totalBytes ); + return NULL; + } + + if ( builder.finalFaceCount >= B3_HULL_LIMIT ) + { + b3Log( "hull final face count of %d exceeds limit of %d", builder.finalFaceCount, B3_HULL_LIMIT ); + b3Free( work, sizes.totalBytes ); + return NULL; + } + + if ( builder.finalHalfEdgeCount >= B3_HULL_LIMIT ) + { + b3Log( "hull final half edge count of %d exceeds limit of %d", builder.finalHalfEdgeCount, B3_HULL_LIMIT ); + b3Free( work, sizes.totalBytes ); + return NULL; + } + + // Walk lists into temp arrays bounded by B3_HULL_LIMIT, stamping finalIndex on each node so + // the resolution pass below is O(E + F) instead of O(E^2 + F^2). + const b3QHVertex* tempVertices[B3_HULL_LIMIT]; + int vertexCount = 0; + for ( b3QHListNode* node = builder.vertexList.link.next; node != &builder.vertexList.link; node = node->next ) + { + B3_ASSERT( vertexCount <= B3_HULL_LIMIT - 1 ); + + b3QHVertex* vertex = (b3QHVertex*)node; + vertex->finalIndex = vertexCount; + tempVertices[vertexCount++] = vertex; + } + + // Collect edges in twin-paired order (i, i+1) by stamping each pair as we discover it. + // Replaces b3SortEdges' O(E^2) twin pairing. + const b3QHFace* tempFaces[B3_HULL_LIMIT]; + const b3QHHalfEdge* tempEdges[B3_HULL_LIMIT]; + int faceCount = 0; + int edgeCount = 0; + + for ( b3QHListNode* faceNode = builder.faceList.link.next; faceNode != &builder.faceList.link; faceNode = faceNode->next ) + { + B3_ASSERT( faceCount <= B3_HULL_LIMIT - 1 ); + + b3QHFace* face = (b3QHFace*)faceNode; + face->finalIndex = faceCount; + tempFaces[faceCount++] = face; + + b3QHHalfEdge* edge = face->edge; + do + { + if ( edge->finalIndex < 0 ) + { + B3_ASSERT( edgeCount + 1 <= B3_HULL_LIMIT - 1 ); + + edge->finalIndex = edgeCount; + tempEdges[edgeCount++] = edge; + edge->twin->finalIndex = edgeCount; + tempEdges[edgeCount++] = edge->twin; + } + edge = edge->next; + } + while ( edge != face->edge ); + } + + // Allocate the hull. Arrays hang off the end. + size_t byteCount = b3AlignUp8( sizeof( b3HullData ) ); + int vertexOffset = (int)byteCount; + byteCount += b3AlignUp8( vertexCount * (int)sizeof( b3HullVertex ) ); + int pointOffset = (int)byteCount; + byteCount += b3AlignUp8( vertexCount * (int)sizeof( b3Vec3 ) ); + int edgeOffset = (int)byteCount; + byteCount += b3AlignUp8( edgeCount * (int)sizeof( b3HullHalfEdge ) ); + int faceOffset = (int)byteCount; + byteCount += b3AlignUp8( faceCount * (int)sizeof( b3HullFace ) ); + int planeOffset = (int)byteCount; + byteCount += b3AlignUp8( faceCount * (int)sizeof( b3Plane ) ); + + b3HullData* hull = b3Alloc( byteCount ); + memset( hull, 0, byteCount ); + + hull->version = B3_HULL_VERSION; + hull->vertexOffset = vertexOffset; + hull->pointOffset = pointOffset; + hull->edgeOffset = edgeOffset; + hull->faceOffset = faceOffset; + hull->planeOffset = planeOffset; + + hull->vertexCount = vertexCount; + hull->edgeCount = edgeCount; + hull->faceCount = faceCount; + + hull->byteCount = (int)byteCount; + + b3HullVertex* vertices = b3GetHullVerticesWrite( hull ); + b3HullHalfEdge* edges = b3GetHullEdgesWrite( hull ); + b3HullFace* faces = (b3HullFace*)( (intptr_t)hull + hull->faceOffset ); + b3Vec3* finalPoints = b3GetHullPointsWrite( hull ); + b3Plane* planes = b3GetHullPlanesWrite( hull ); + + for ( int index = 0; index < vertexCount; ++index ) + { + vertices[index].edge = 0; + finalPoints[index] = tempVertices[index]->position; + } + + for ( int index = 0; index < edgeCount; ++index ) + { + const b3QHHalfEdge* edge = tempEdges[index]; + B3_ASSERT( 0 <= edge->next->finalIndex && edge->next->finalIndex <= UINT8_MAX ); + B3_ASSERT( 0 <= edge->twin->finalIndex && edge->twin->finalIndex <= UINT8_MAX ); + B3_ASSERT( 0 <= edge->face->finalIndex && edge->face->finalIndex <= UINT8_MAX ); + B3_ASSERT( 0 <= edge->origin->finalIndex && edge->origin->finalIndex <= UINT8_MAX ); + + edges[index].next = (uint8_t)edge->next->finalIndex; + edges[index].twin = (uint8_t)edge->twin->finalIndex; + edges[index].face = (uint8_t)edge->face->finalIndex; + edges[index].origin = (uint8_t)edge->origin->finalIndex; + + vertices[edge->origin->finalIndex].edge = (uint8_t)index; + } + + for ( int index = 0; index < faceCount; ++index ) + { + const b3QHFace* face = tempFaces[index]; + B3_ASSERT( 0 <= face->edge->finalIndex && face->edge->finalIndex <= UINT8_MAX ); + + faces[index].edge = (uint8_t)face->edge->finalIndex; + planes[index] = face->plane; + } + + // All builder pointers are dead from here on. + b3Free( work, sizes.totalBytes ); + + b3UpdateHullBounds( hull ); + bool success = b3UpdateHullBulkProperties( hull ); + if ( success == false ) + { + b3DestroyHull( hull ); + return NULL; + } + + if ( b3IsValidHull( hull ) == false ) + { + b3DestroyHull( hull ); + return NULL; + } + + hull->hash = 0; + hull->hash = b3NonZeroHash( b3Hash( B3_HASH_INIT, (uint8_t*)hull, hull->byteCount ) ); + + return hull; +} + +b3HullData* b3CloneHull( const b3HullData* hull ) +{ + if ( hull == NULL || b3IsValidHull( hull ) == false ) + { + return NULL; + } + + b3HullData* clone = (b3HullData*)b3Alloc( hull->byteCount ); + memcpy( clone, hull, hull->byteCount ); + + return clone; +} + +uint64_t b3HashHullData( const b3HullData* hull ) +{ + // The baked content hash already covers byteCount. Spread the 32 bits across 64 so the table + // can use the high bits for its fast reject fragment. + return (uint64_t)hull->hash * 0x9E3779B97F4A7C15ull; +} + +bool b3CompareHullData( const b3HullData* hull1, const b3HullData* hull2 ) +{ + if ( hull1 == hull2 ) + { + return true; + } + + if ( hull1->byteCount != hull2->byteCount ) + { + return false; + } + + return memcmp( hull1, hull2, hull1->byteCount ) == 0; +} + +// Hull identity covers every byte, so the structs carry explicit padding. These lock +// the layout, re-audit padding if a size changes. +_Static_assert( sizeof( b3HullData ) == 136, "unexpected hull data size" ); +_Static_assert( sizeof( b3BoxHull ) == 440, "unexpected box hull size" ); + +#define NAME b3HullMap +#define KEY_TY const b3HullData* +#define VAL_TY int +#define HASH_FN b3HashHullData +#define CMPR_FN b3CompareHullData +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#define IMPLEMENTATION_MODE +#include "verstable.h" + +size_t b3HullMapByteCount( b3HullMap* map ) +{ + // The map owns a combined bucket and metadata allocation, valid only when buckets exist + size_t byteCount = sizeof( b3HullMap ); + if ( b3HullMap_bucket_count( map ) > 0 ) + { + byteCount += b3HullMap_total_alloc_size( map ); + } + return byteCount; +} + +b3HullData* b3CloneAndTransformHull( const b3HullData* original, b3Transform transform, b3Vec3 scale ) +{ + if ( original == NULL || b3IsValidHull( original ) == false ) + { + return NULL; + } + + b3HullData* hull = (b3HullData*)b3Alloc( original->byteCount ); + memcpy( hull, original, original->byteCount ); + + b3Vec3 safeScale = b3SafeScale( scale ); + + b3HullHalfEdge* edges = b3GetHullEdgesWrite( hull ); + const b3HullFace* faces = b3GetHullFaces( hull ); + int faceCount = hull->faceCount; + int vertexCount = hull->vertexCount; + + if ( safeScale.x * safeScale.y * safeScale.z < 0.0f ) + { + // Reflected: reverse edge winding for each face. + for ( int i = 0; i < faceCount; ++i ) + { + const b3HullFace* face = faces + i; + + uint8_t startEdgeIndex = face->edge; + uint8_t currentEdgeIndex = startEdgeIndex; + uint8_t prevEdgeIndex = UINT8_MAX; + + do + { + b3HullHalfEdge* edge = edges + currentEdgeIndex; + + if ( edge->next == startEdgeIndex ) + { + prevEdgeIndex = currentEdgeIndex; + break; + } + + currentEdgeIndex = edge->next; + } + while ( currentEdgeIndex != startEdgeIndex ); + + B3_ASSERT( prevEdgeIndex != UINT8_MAX ); + + currentEdgeIndex = startEdgeIndex; + + do + { + b3HullHalfEdge* edge = edges + currentEdgeIndex; + uint8_t nextIndex = edge->next; + edge->next = prevEdgeIndex; + + if ( currentEdgeIndex < edge->twin ) + { + b3HullHalfEdge* twin = edges + edge->twin; + B3_SWAP( edge->origin, twin->origin ); + } + + prevEdgeIndex = currentEdgeIndex; + currentEdgeIndex = nextIndex; + } + while ( currentEdgeIndex != startEdgeIndex ); + } + + b3HullVertex* vertices = b3GetHullVerticesWrite( hull ); + + for ( int i = 0; i < vertexCount; ++i ) + { + b3HullVertex* vertex = vertices + i; + const b3HullHalfEdge* edge = edges + vertex->edge; + vertex->edge = edge->twin; + } + } + + b3Matrix3 matrix = b3MakeMatrixFromQuat( transform.q ); + b3Vec3* points = b3GetHullPointsWrite( hull ); + + for ( int i = 0; i < vertexCount; ++i ) + { + points[i] = b3Add( b3MulMV( matrix, b3Mul( safeScale, points[i] ) ), transform.p ); + } + + b3Plane* planes = b3GetHullPlanesWrite( hull ); + + for ( int i = 0; i < faceCount; ++i ) + { + int count = 0; + b3Vec3 centroid = b3Vec3_zero; + b3Vec3 normal = b3Vec3_zero; + + const b3HullFace* face = faces + i; + uint8_t startEdgeIndex = face->edge; + uint8_t currentEdgeIndex = startEdgeIndex; + + const b3HullHalfEdge* startEdge = edges + currentEdgeIndex; + B3_ASSERT( startEdge->face == i ); + B3_ASSERT( startEdge->origin < vertexCount ); + + b3Vec3 origin = points[startEdge->origin]; + + do + { + b3HullHalfEdge* edge = edges + currentEdgeIndex; + b3HullHalfEdge* twin = edges + edge->twin; + B3_ASSERT( twin->twin == currentEdgeIndex ); + + b3Vec3 v1 = b3Sub( points[edge->origin], origin ); + b3Vec3 v2 = b3Sub( points[twin->origin], origin ); + + count++; + centroid = b3Add( centroid, v1 ); + normal.x += ( v1.y - v2.y ) * ( v1.z + v2.z ); + normal.y += ( v1.z - v2.z ) * ( v1.x + v2.x ); + normal.z += ( v1.x - v2.x ) * ( v1.y + v2.y ); + + currentEdgeIndex = edge->next; + } + while ( currentEdgeIndex != startEdgeIndex ); + + B3_ASSERT( count > 0 ); + centroid = b3MulSV( 1.0f / (float)count, centroid ); + centroid = b3Add( centroid, origin ); + + float area = b3Length( normal ); + B3_ASSERT( area > 0.0f ); + normal = b3MulSV( 1.0f / area, normal ); + + planes[i] = b3MakePlaneFromNormalAndPoint( normal, centroid ); + } + + b3UpdateHullBounds( hull ); + bool success = b3UpdateHullBulkProperties( hull ); + if ( success == false ) + { + b3Free( hull, original->byteCount ); + return NULL; + } + + hull->hash = 0; + hull->hash = b3NonZeroHash( b3Hash( B3_HASH_INIT, (uint8_t*)hull, hull->byteCount ) ); + + B3_VALIDATE( b3IsValidHull( hull ) ); + + return hull; +} + +void b3DestroyHull( b3HullData* hull ) +{ + b3Free( hull, hull->byteCount ); +} + +b3MassData b3ComputeHullMass( const b3HullData* shape, float density ) +{ + b3MassData out; + out.mass = density * shape->volume; + out.center = shape->center; + + // Inertia about the center of mass + out.inertia = b3MulSM( density, shape->centralInertia ); + return out; +} + +b3AABB b3ComputeHullAABB( const b3HullData* shape, b3Transform transform ) +{ + return b3AABB_Transform( transform, shape->aabb ); +} + +b3AABB b3ComputeSweptHullAABB( const b3HullData* shape, b3Transform xf1, b3Transform xf2 ) +{ + b3AABB aabb1 = b3AABB_Transform( xf1, shape->aabb ); + b3AABB aabb2 = b3AABB_Transform( xf2, shape->aabb ); + return b3AABB_Union( aabb1, aabb2 ); +} + +bool b3OverlapHull( const b3HullData* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy ) +{ + const b3Vec3* points = b3GetHullPoints( shape ); + + b3DistanceInput input; + input.proxyA = (b3ShapeProxy){ points, shape->vertexCount, 0.0f }; + input.proxyB = *proxy; + input.transform = b3InvMulTransforms( shapeTransform, b3Transform_identity ); + input.useRadii = true; + + b3SimplexCache cache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + return output.distance < B3_OVERLAP_SLOP; +} + +b3CastOutput b3RayCastHull( const b3HullData* shape, const b3RayCastInput* input ) +{ + B3_ASSERT( b3IsValidRay( input ) ); + b3CastOutput output = { 0 }; + + float lower = 0.0f; + float upper = input->maxFraction; + int bestFace = B3_NULL_INDEX; + + const b3Plane* planes = b3GetHullPlanes( shape ); + + for ( int faceIndex = 0; faceIndex < shape->faceCount; ++faceIndex ) + { + b3Plane plane = planes[faceIndex]; + + float distance = plane.offset - b3Dot( plane.normal, input->origin ); + float denominator = b3Dot( plane.normal, input->translation ); + + if ( denominator == 0.0f ) + { + if ( distance < 0.0f ) + { + return output; + } + } + else + { + float fraction = distance / denominator; + + if ( denominator < 0.0f ) + { + if ( fraction > lower ) + { + bestFace = faceIndex; + lower = fraction; + } + } + else + { + if ( fraction < upper ) + { + upper = fraction; + } + } + + if ( upper < lower ) + { + return output; + } + } + } + + if ( bestFace >= 0 ) + { + output.point = b3Add( input->origin, b3MulSV( lower, input->translation ) ); + output.normal = planes[bestFace].normal; + output.fraction = lower; + output.hit = true; + } + else + { + output.point = input->origin; + output.hit = true; + } + + return output; +} + +b3CastOutput b3ShapeCastHull( const b3HullData* shape, const b3ShapeCastInput* input ) +{ + const b3Vec3* points = b3GetHullPoints( shape ); + + b3ShapeCastPairInput pairInput; + pairInput.proxyA = (b3ShapeProxy){ points, shape->vertexCount, 0.0f }; + pairInput.proxyB = input->proxy; + pairInput.transform = b3Transform_identity; + pairInput.translationB = input->translation; + pairInput.maxFraction = input->maxFraction; + pairInput.canEncroach = input->canEncroach; + + b3CastOutput output = b3ShapeCast( &pairInput ); + return output; +} + +int b3CollideMoverAndHull( b3PlaneResult* result, const b3HullData* shape, const b3Capsule* mover ) +{ + const b3Vec3* points = b3GetHullPoints( shape ); + b3DistanceInput distanceInput; + distanceInput.proxyA = (b3ShapeProxy){ points, shape->vertexCount, 0.0f }; + distanceInput.proxyB = (b3ShapeProxy){ &mover->center1, 2, mover->radius }; + distanceInput.transform = b3Transform_identity; + distanceInput.useRadii = false; + + float totalRadius = mover->radius; + + b3SimplexCache cache = { 0 }; + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, &cache, NULL, 0 ); + + if ( distanceOutput.distance == 0.0f ) + { + // I could handle deep overlap on hulls, but there is no reasonable solution for + // deep overlap on meshes. So if someone converted a hull to a mesh there would be + // different behavior. So I think there is not a good reason to handle deep overlap + // on hulls. + return 0; + } + + if ( distanceOutput.distance <= totalRadius ) + { + b3Plane plane = { distanceOutput.normal, totalRadius - distanceOutput.distance }; + *result = (b3PlaneResult){ plane, distanceOutput.pointA }; + return 1; + } + + return 0; +} + +b3ShapeExtent b3ComputeHullExtent( const b3HullData* hull, b3Vec3 origin ) +{ + const b3Vec3* points = b3GetHullPoints( hull ); + + b3ShapeExtent extent; + extent.minExtent = hull->innerRadius; + extent.maxExtent = b3Vec3_zero; + for ( int index = 0; index < hull->vertexCount; ++index ) + { + b3Vec3 point = points[index]; + extent.maxExtent = b3Max( extent.maxExtent, b3Abs( b3Sub( point, origin ) ) ); + } + + return extent; +} + +float b3ComputeHullProjectedArea( const b3HullData* hull, b3Vec3 direction ) +{ + float area = 0.0f; + + int faceCount = hull->faceCount; + const b3HullFace* hullFaces = b3GetHullFaces( hull ); + const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull ); + const b3Vec3* hullPoints = b3GetHullPoints( hull ); + + for ( int i = 0; i < faceCount; ++i ) + { + const b3HullFace* face = hullFaces + i; + + int baseEdge = face->edge; + const b3HullHalfEdge* edge = hullEdges + baseEdge; + b3Vec3 p1 = hullPoints[edge->origin]; + + int edgeIndex = edge->next; + edge = hullEdges + edgeIndex; + b3Vec3 p2 = hullPoints[edge->origin]; + + edgeIndex = edge->next; + + do + { + edge = hullEdges + edgeIndex; + b3Vec3 p3 = hullPoints[edge->origin]; + + b3Vec3 e1 = b3Sub( p2, p1 ); + b3Vec3 e2 = b3Sub( p3, p1 ); + b3Vec3 n = b3Cross( e1, e2 ); + float a = b3Dot( n, direction ); + area += b3MaxFloat( a, 0.0f ); + + p2 = p3; + edgeIndex = edge->next; + } + while ( edgeIndex != baseEdge ); + } + + return 0.5f * area; +} + +// Constant template box (vertex/edge/face/topology). b3MakeTransformedBoxHull copies and +// fills in the runtime-dependent fields (boxPoints, boxPlanes, aabb, mass properties, hash). +static const b3BoxHull s_boxHull = { + .base = + { + .version = B3_HULL_VERSION, + .byteCount = sizeof( b3BoxHull ), + .hash = 0, + .vertexCount = 8, + .edgeCount = 24, + .faceCount = 6, + .vertexOffset = offsetof( b3BoxHull, boxVertices ), + .pointOffset = offsetof( b3BoxHull, boxPoints ), + .edgeOffset = offsetof( b3BoxHull, boxEdges ), + .faceOffset = offsetof( b3BoxHull, boxFaces ), + .planeOffset = offsetof( b3BoxHull, boxPlanes ), + }, + .boxVertices = + { + [0] = { .edge = 8 }, + [1] = { .edge = 1 }, + [2] = { .edge = 0 }, + [3] = { .edge = 9 }, + [4] = { .edge = 13 }, + [5] = { .edge = 3 }, + [6] = { .edge = 5 }, + [7] = { .edge = 11 }, + }, + .boxEdges = + { + [0] = { 2, 1, 2, 0 }, [1] = { 17, 0, 1, 5 }, [2] = { 4, 3, 1, 0 }, [3] = { 20, 2, 5, 3 }, + [4] = { 6, 5, 5, 0 }, [5] = { 23, 4, 6, 4 }, [6] = { 0, 7, 6, 0 }, [7] = { 18, 6, 2, 2 }, + [8] = { 10, 9, 0, 1 }, [9] = { 21, 8, 3, 5 }, [10] = { 12, 11, 3, 1 }, [11] = { 16, 10, 7, 2 }, + [12] = { 14, 13, 7, 1 }, [13] = { 19, 12, 4, 4 }, [14] = { 8, 15, 4, 1 }, [15] = { 22, 14, 0, 3 }, + [16] = { 7, 17, 3, 2 }, [17] = { 9, 16, 2, 5 }, [18] = { 11, 19, 6, 2 }, [19] = { 5, 18, 7, 4 }, + [20] = { 15, 21, 1, 3 }, [21] = { 1, 20, 0, 5 }, [22] = { 3, 23, 4, 3 }, [23] = { 13, 22, 5, 4 }, + }, + .boxFaces = + { + [0] = { .edge = 0 }, + [1] = { .edge = 8 }, + [2] = { .edge = 16 }, + [3] = { .edge = 20 }, + [4] = { .edge = 19 }, + [5] = { .edge = 21 }, + }, +}; + +b3BoxHull b3MakeTransformedBoxHull( float hx, float hy, float hz, b3Transform transform ) +{ + b3BoxHull boxHull = s_boxHull; + + float minH = 0.2f * B3_LINEAR_SLOP; + b3Vec3 h = b3Max( (b3Vec3){ minH, minH, minH }, (b3Vec3){ hx, hy, hz } ); + + boxHull.base.aabb = b3AABB_Transform( transform, (b3AABB){ b3Neg( h ), h } ); + boxHull.base.surfaceArea = 8.0f * ( h.x * h.y + h.x * h.z + h.y * h.z ); + boxHull.base.volume = 8.0f * h.x * h.y * h.z; + boxHull.base.innerRadius = b3MinFloat( h.x, b3MinFloat( h.y, h.z ) ); + boxHull.base.center = transform.p; + + b3Matrix3 boxInertia = b3BoxInertia( boxHull.base.volume, b3Neg( h ), h ); + boxHull.base.centralInertia = b3RotateInertia( transform.q, boxInertia ); + + b3Vec3 lower = b3Neg( h ); + b3Vec3 upper = h; + + boxHull.boxPlanes[0] = b3TransformPlane( transform, b3MakePlaneFromNormalAndPoint( b3Neg( b3Vec3_axisX ), lower ) ); + boxHull.boxPlanes[1] = b3TransformPlane( transform, b3MakePlaneFromNormalAndPoint( b3Vec3_axisX, upper ) ); + boxHull.boxPlanes[2] = b3TransformPlane( transform, b3MakePlaneFromNormalAndPoint( b3Neg( b3Vec3_axisY ), lower ) ); + boxHull.boxPlanes[3] = b3TransformPlane( transform, b3MakePlaneFromNormalAndPoint( b3Vec3_axisY, upper ) ); + boxHull.boxPlanes[4] = b3TransformPlane( transform, b3MakePlaneFromNormalAndPoint( b3Neg( b3Vec3_axisZ ), lower ) ); + boxHull.boxPlanes[5] = b3TransformPlane( transform, b3MakePlaneFromNormalAndPoint( b3Vec3_axisZ, upper ) ); + + boxHull.boxPoints[0] = b3TransformPoint( transform, (b3Vec3){ h.x, h.y, h.z } ); + boxHull.boxPoints[1] = b3TransformPoint( transform, (b3Vec3){ -h.x, h.y, h.z } ); + boxHull.boxPoints[2] = b3TransformPoint( transform, (b3Vec3){ -h.x, -h.y, h.z } ); + boxHull.boxPoints[3] = b3TransformPoint( transform, (b3Vec3){ h.x, -h.y, h.z } ); + boxHull.boxPoints[4] = b3TransformPoint( transform, (b3Vec3){ h.x, h.y, -h.z } ); + boxHull.boxPoints[5] = b3TransformPoint( transform, (b3Vec3){ -h.x, h.y, -h.z } ); + boxHull.boxPoints[6] = b3TransformPoint( transform, (b3Vec3){ -h.x, -h.y, -h.z } ); + boxHull.boxPoints[7] = b3TransformPoint( transform, (b3Vec3){ h.x, -h.y, -h.z } ); + + boxHull.base.hash = 0; + boxHull.base.hash = b3NonZeroHash( b3Hash( B3_HASH_INIT, (uint8_t*)&boxHull, sizeof( b3BoxHull ) ) ); + + return boxHull; +} + +b3BoxHull b3MakeCubeHull( float halfWidth ) +{ + return b3MakeBoxHull( halfWidth, halfWidth, halfWidth ); +} + +b3BoxHull b3MakeOffsetBoxHull( float hx, float hy, float hz, b3Vec3 offset ) +{ + b3Transform transform = { .p = offset, .q = b3Quat_identity }; + return b3MakeTransformedBoxHull( hx, hy, hz, transform ); +} + +b3BoxHull b3MakeBoxHull( float hx, float hy, float hz ) +{ + return b3MakeTransformedBoxHull( hx, hy, hz, b3Transform_identity ); +} + +void b3ScaleBox( b3Vec3* halfWidths, b3Transform* transform, b3Vec3 postScale, float minHalfWidth ) +{ + B3_ASSERT( b3IsValidFloat( minHalfWidth ) && minHalfWidth > 0.0f ); + + b3Quat q = transform->q; + + if ( postScale.x < 0.0f || postScale.y < 0.0f || postScale.z < 0.0f ) + { + // todo this might be unnecessary if rotation is identity + // todo compare with polar decomposition (much more expensive) + b3Matrix3 m = b3MakeMatrixFromQuat( q ); + m.cx.x *= postScale.x; + m.cy.x *= postScale.x; + m.cz.x *= postScale.x; + m.cx.y *= postScale.y; + m.cy.y *= postScale.y; + m.cz.y *= postScale.y; + m.cx.z *= postScale.z; + m.cy.z *= postScale.z; + m.cz.z *= postScale.z; + m.cx = b3Normalize( m.cx ); + m.cy = b3Normalize( m.cy ); + m.cz = b3Normalize( m.cz ); + m.cx = postScale.x < 0.0f ? b3Neg( m.cx ) : m.cx; + m.cy = postScale.y < 0.0f ? b3Neg( m.cy ) : m.cy; + m.cz = postScale.z < 0.0f ? b3Neg( m.cz ) : m.cz; + q = b3MakeQuatFromMatrix( &m ); + } + + b3Vec3 absScale = b3Abs( postScale ); + + b3Vec3 h = *halfWidths; + b3Vec3 p1 = b3Mul( absScale, b3RotateVector( q, b3Neg( h ) ) ); + b3Vec3 p2 = b3Mul( absScale, b3RotateVector( q, h ) ); + + b3Vec3 localP1 = b3InvRotateVector( q, p1 ); + b3Vec3 localP2 = b3InvRotateVector( q, p2 ); + + b3Vec3 lower = b3Min( localP1, localP2 ); + b3Vec3 upper = b3Max( localP1, localP2 ); + + b3Vec3 scaledHalfWidth = b3MulSV( 0.5f, b3Sub( upper, lower ) ); + + b3Vec3 mLimit = { minHalfWidth, minHalfWidth, minHalfWidth }; + *halfWidths = b3Max( scaledHalfWidth, mLimit ); + transform->p = b3Mul( postScale, transform->p ); + transform->q = q; +} + +// todo use new hull scaling technique +b3BoxHull b3MakeScaledBoxHull( b3Vec3 halfWidths, b3Transform transform, b3Vec3 postScale ) +{ + b3Vec3 h = halfWidths; + b3Transform xf = transform; + b3ScaleBox( &h, &xf, postScale, 4.0f * B3_LINEAR_SLOP ); + return b3MakeTransformedBoxHull( h.x, h.y, h.z, xf ); +} diff --git a/vendor/box3d/src/src/hull_map.h b/vendor/box3d/src/src/hull_map.h new file mode 100644 index 000000000..5b50c56eb --- /dev/null +++ b/vendor/box3d/src/src/hull_map.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/types.h" + +#include + +// Content hash and equality over the whole baked hull. Shared by every hull +// de-duplication map so they agree on identity. +uint64_t b3HashHullData( const b3HullData* hull ); +bool b3CompareHullData( const b3HullData* hull1, const b3HullData* hull2 ); + +// Map keyed by hull content. The world hull database stores a reference count, +// compound baking stores a byte offset. Implementation lives in hull.c. +#define NAME b3HullMap +#define KEY_TY const b3HullData* +#define VAL_TY int +#define HEADER_MODE +#include "verstable.h" + +// Total map allocation in bytes, excluding the stored hull data +size_t b3HullMapByteCount( b3HullMap* map ); diff --git a/vendor/box3d/src/src/id_pool.c b/vendor/box3d/src/src/id_pool.c new file mode 100644 index 000000000..185b6eebc --- /dev/null +++ b/vendor/box3d/src/src/id_pool.c @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "id_pool.h" + +b3IdPool b3CreateIdPool( void ) +{ + b3IdPool pool = { 0 }; + b3Array_Reserve( pool.freeArray, 32 ); + return pool; +} + +void b3DestroyIdPool( b3IdPool* pool ) +{ + b3Array_Destroy( pool->freeArray ); + *pool = (b3IdPool){ 0 }; +} + +int b3AllocId( b3IdPool* pool ) +{ + int count = pool->freeArray.count; + if ( count > 0 ) + { + int id = b3Array_Pop( pool->freeArray ); + return id; + } + + int id = pool->nextIndex; + pool->nextIndex += 1; + return id; +} + +void b3FreeId( b3IdPool* pool, int id ) +{ + B3_ASSERT( pool->nextIndex > 0 ); + B3_ASSERT( 0 <= id && id < pool->nextIndex ); + + // todo does not work with assertion above + // should probably be `id == pool->nextIndex - 1` + if ( id == pool->nextIndex ) + { + pool->nextIndex -= 1; + return; + } + + b3Array_Push( pool->freeArray, id ); +} + +#if B3_ENABLE_VALIDATION + +void b3ValidateFreeId( const b3IdPool* pool, int id ) +{ + int freeCount = pool->freeArray.count; + for ( int i = 0; i < freeCount; ++i ) + { + if ( pool->freeArray.data[i] == id ) + { + return; + } + } + + B3_ASSERT( 0 ); +} + +#else + +void b3ValidateFreeId( const b3IdPool* pool, int id ) +{ + B3_UNUSED( pool ); + B3_UNUSED( id ); +} + +#endif diff --git a/vendor/box3d/src/src/id_pool.h b/vendor/box3d/src/src/id_pool.h new file mode 100644 index 000000000..06106de20 --- /dev/null +++ b/vendor/box3d/src/src/id_pool.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "container.h" + +typedef struct b3IdPool +{ + b3Array( int ) freeArray; + int nextIndex; +} b3IdPool; + +b3IdPool b3CreateIdPool( void ); +void b3DestroyIdPool( b3IdPool* pool ); + +int b3AllocId( b3IdPool* pool ); +void b3FreeId( b3IdPool* pool, int id ); +void b3ValidateFreeId( const b3IdPool* pool, int id ); + +static inline int b3GetIdCount( const b3IdPool* pool ) +{ + return pool->nextIndex - pool->freeArray.count; +} + +static inline int b3GetIdCapacity( const b3IdPool* pool ) +{ + return pool->nextIndex; +} + +static inline int b3GetIdBytes( const b3IdPool* pool ) +{ + return b3Array_ByteCount( pool->freeArray ); +} diff --git a/vendor/box3d/src/src/island.c b/vendor/box3d/src/src/island.c new file mode 100644 index 000000000..625a1b345 --- /dev/null +++ b/vendor/box3d/src/src/island.c @@ -0,0 +1,735 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "island.h" + +#include "body.h" +#include "contact.h" +#include "core.h" +#include "id_pool.h" +#include "joint.h" +#include "physics_world.h" +#include "solver_set.h" + +#include + +b3Island* b3CreateIsland( b3World* world, int setIndex ) +{ + B3_ASSERT( setIndex == b3_awakeSet || setIndex >= b3_firstSleepingSet ); + + int islandId = b3AllocId( &world->islandIdPool ); + + if ( islandId == world->islands.count ) + { + b3Island emptyIsland = { 0 }; + b3Array_Push( world->islands, emptyIsland ); + } + else + { + B3_ASSERT( world->islands.data[islandId].setIndex == B3_NULL_INDEX ); + } + + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + + b3Island* island = b3Array_Get( world->islands, islandId ); + island->setIndex = setIndex; + island->localIndex = set->islandSims.count; + island->islandId = islandId; + b3Array_Create( island->bodies ); + b3Array_Create( island->contacts ); + b3Array_Create( island->joints ); + island->constraintRemoveCount = 0; + + b3IslandSim* islandSim = b3Array_Emplace( set->islandSims ); + islandSim->islandId = islandId; + + return island; +} + +void b3DestroyIsland( b3World* world, int islandId ) +{ + if ( world->splitIslandId == islandId ) + { + world->splitIslandId = B3_NULL_INDEX; + } + + // assume island is empty + b3Island* island = b3Array_Get( world->islands, islandId ); + b3SolverSet* set = b3Array_Get( world->solverSets, island->setIndex ); + { + int localIndex = island->localIndex; + int lastIndex = set->islandSims.count - 1; + B3_ASSERT( 0 <= localIndex && localIndex <= lastIndex ); + int moveIslandId = set->islandSims.data[lastIndex].islandId; + set->islandSims.data[localIndex] = set->islandSims.data[lastIndex]; + world->islands.data[moveIslandId].localIndex = localIndex; + set->islandSims.count -= 1; + } + + // Free island and id (preserve island generation) + b3Array_Destroy( island->bodies ); + b3Array_Destroy( island->contacts ); + b3Array_Destroy( island->joints ); + island->constraintRemoveCount = 0; + island->localIndex = B3_NULL_INDEX; + island->islandId = B3_NULL_INDEX; + island->setIndex = B3_NULL_INDEX; + + b3FreeId( &world->islandIdPool, islandId ); +} + +static int b3MergeIslands( b3World* world, int islandIdA, int islandIdB ) +{ + if ( islandIdA == islandIdB ) + { + return islandIdA; + } + + if ( islandIdA == B3_NULL_INDEX ) + { + B3_ASSERT( islandIdB != B3_NULL_INDEX ); + return islandIdB; + } + + if ( islandIdB == B3_NULL_INDEX ) + { + B3_ASSERT( islandIdA != B3_NULL_INDEX ); + return islandIdA; + } + + b3Island* smallIsland; + b3Island* bigIsland; + { + b3Island* islandA = b3Array_Get( world->islands, islandIdA ); + b3Island* islandB = b3Array_Get( world->islands, islandIdB ); + + // Keep the biggest island to reduce cache misses + if ( islandA->bodies.count >= islandB->bodies.count ) + { + bigIsland = islandA; + smallIsland = islandB; + } + else + { + bigIsland = islandB; + smallIsland = islandA; + } + } + + int bigIslandId = bigIsland->islandId; + b3Array_Reserve( bigIsland->bodies, bigIsland->bodies.count + smallIsland->bodies.count ); + + // Move bodies from smaller island to larger island + for ( int i = 0; i < smallIsland->bodies.count; ++i ) + { + int bodyId = smallIsland->bodies.data[i]; + b3Body* body = b3Array_Get( world->bodies, bodyId ); + B3_VALIDATE( body->islandId == smallIsland->islandId ); + body->islandId = bigIslandId; + body->islandIndex = bigIsland->bodies.count; + b3Array_Push( bigIsland->bodies, bodyId ); + } + + // Migrate contacts from smaller island to larger island + if ( smallIsland->contacts.count > 0 ) + { + b3Array_Reserve( bigIsland->contacts, bigIsland->contacts.count + smallIsland->contacts.count ); + + for ( int i = 0; i < smallIsland->contacts.count; ++i ) + { + b3ContactLink* link = smallIsland->contacts.data + i; + b3Contact* contact = b3Array_Get( world->contacts, link->contactId ); + contact->islandId = bigIslandId; + contact->islandIndex = bigIsland->contacts.count; + b3Array_Push( bigIsland->contacts, *link ); + } + } + + // Migrate joints from smaller island to larger island + if ( smallIsland->joints.count > 0 ) + { + b3Array_Reserve( bigIsland->joints, bigIsland->joints.count + smallIsland->joints.count ); + + for ( int i = 0; i < smallIsland->joints.count; ++i ) + { + b3JointLink* link = smallIsland->joints.data + i; + b3Joint* joint = b3Array_Get( world->joints, link->jointId ); + joint->islandId = bigIslandId; + joint->islandIndex = bigIsland->joints.count; + b3Array_Push( bigIsland->joints, *link ); + } + } + + // Track removed constraints + bigIsland->constraintRemoveCount += smallIsland->constraintRemoveCount; + + b3DestroyIsland( world, smallIsland->islandId ); + + b3ValidateIsland( world, bigIslandId ); + + return bigIslandId; +} + +static void b3AddContactToIsland( b3World* world, int islandId, b3Contact* contact ) +{ + B3_ASSERT( contact->islandId == B3_NULL_INDEX ); + B3_ASSERT( contact->islandIndex == B3_NULL_INDEX ); + + b3Island* island = b3Array_Get( world->islands, islandId ); + + contact->islandId = islandId; + contact->islandIndex = island->contacts.count; + + b3ContactLink link; + link.contactId = contact->contactId; + link.bodyIdA = contact->edges[0].bodyId; + link.bodyIdB = contact->edges[1].bodyId; + b3Array_Push( island->contacts, link ); + + b3ValidateIsland( world, islandId ); +} + +// Link a contact into an island. +void b3LinkContact( b3World* world, b3Contact* contact ) +{ + B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) != 0 ); + + int bodyIdA = contact->edges[0].bodyId; + int bodyIdB = contact->edges[1].bodyId; + + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + + B3_ASSERT( bodyA->setIndex != b3_disabledSet && bodyB->setIndex != b3_disabledSet ); + B3_ASSERT( bodyA->setIndex != b3_staticSet || bodyB->setIndex != b3_staticSet ); + + // Wake bodyB if bodyA is awake and bodyB is sleeping + if ( bodyA->setIndex == b3_awakeSet && bodyB->setIndex >= b3_firstSleepingSet ) + { + b3WakeSolverSet( world, bodyB->setIndex ); + } + + // Wake bodyA if bodyB is awake and bodyA is sleeping + if ( bodyB->setIndex == b3_awakeSet && bodyA->setIndex >= b3_firstSleepingSet ) + { + b3WakeSolverSet( world, bodyA->setIndex ); + } + + int islandIdA = bodyA->islandId; + int islandIdB = bodyB->islandId; + + // Static bodies have null island indices. + B3_ASSERT( bodyA->setIndex != b3_staticSet || islandIdA == B3_NULL_INDEX ); + B3_ASSERT( bodyB->setIndex != b3_staticSet || islandIdB == B3_NULL_INDEX ); + B3_ASSERT( islandIdA != B3_NULL_INDEX || islandIdB != B3_NULL_INDEX ); + + // Merge islands. This will destroy one of the islands. + int finalIslandId = b3MergeIslands( world, islandIdA, islandIdB ); + + // Add contact to the island that survived + b3AddContactToIsland( world, finalIslandId, contact ); +} + +// This is called when a contact no longer has contact points or when a contact is destroyed. +void b3UnlinkContact( b3World* world, b3Contact* contact ) +{ + B3_ASSERT( contact->islandId != B3_NULL_INDEX ); + + // remove from island + int islandId = contact->islandId; + b3Island* island = b3Array_Get( world->islands, islandId ); + + int removeIndex = contact->islandIndex; + B3_ASSERT( 0 <= removeIndex && removeIndex < island->contacts.count ); + B3_ASSERT( island->contacts.data[removeIndex].contactId == contact->contactId ); + + int movedIndex = b3Array_RemoveSwap( island->contacts, removeIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix islandIndex on the contact that was swapped into removeIndex + b3ContactLink* movedLink = island->contacts.data + removeIndex; + b3Contact* movedContact = b3Array_Get( world->contacts, movedLink->contactId ); + B3_ASSERT( movedContact->islandIndex == movedIndex ); + movedContact->islandIndex = removeIndex; + } + + contact->islandId = B3_NULL_INDEX; + contact->islandIndex = B3_NULL_INDEX; + island->constraintRemoveCount += 1; + + b3ValidateIsland( world, islandId ); +} + +static void b3AddJointToIsland( b3World* world, int islandId, b3Joint* joint ) +{ + B3_ASSERT( joint->islandId == B3_NULL_INDEX ); + B3_ASSERT( joint->islandIndex == B3_NULL_INDEX ); + + b3Island* island = b3Array_Get( world->islands, islandId ); + + joint->islandId = islandId; + joint->islandIndex = island->joints.count; + + b3JointLink link; + link.jointId = joint->jointId; + link.bodyIdA = joint->edges[0].bodyId; + link.bodyIdB = joint->edges[1].bodyId; + b3Array_Push( island->joints, link ); + + b3ValidateIsland( world, islandId ); +} + +void b3LinkJoint( b3World* world, b3Joint* joint ) +{ + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + + B3_ASSERT( bodyA->type == b3_dynamicBody || bodyB->type == b3_dynamicBody ); + + if ( bodyA->setIndex == b3_awakeSet && bodyB->setIndex >= b3_firstSleepingSet ) + { + b3WakeSolverSet( world, bodyB->setIndex ); + } + else if ( bodyB->setIndex == b3_awakeSet && bodyA->setIndex >= b3_firstSleepingSet ) + { + b3WakeSolverSet( world, bodyA->setIndex ); + } + + int islandIdA = bodyA->islandId; + int islandIdB = bodyB->islandId; + + B3_ASSERT( islandIdA != B3_NULL_INDEX || islandIdB != B3_NULL_INDEX ); + + // Merge islands. This will destroy one of the islands. + int finalIslandId = b3MergeIslands( world, islandIdA, islandIdB ); + + // Add joint the island that survived + b3AddJointToIsland( world, finalIslandId, joint ); +} + +void b3UnlinkJoint( b3World* world, b3Joint* joint ) +{ + if ( joint->islandId == B3_NULL_INDEX ) + { + return; + } + + // remove from island + int islandId = joint->islandId; + b3Island* island = b3Array_Get( world->islands, islandId ); + + int removeIndex = joint->islandIndex; + B3_ASSERT( 0 <= removeIndex && removeIndex < island->joints.count ); + B3_ASSERT( island->joints.data[removeIndex].jointId == joint->jointId ); + + int movedIndex = b3Array_RemoveSwap( island->joints, removeIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix islandIndex on the joint that was swapped into removeIndex + b3JointLink* movedLink = island->joints.data + removeIndex; + b3Joint* movedJoint = b3Array_Get( world->joints, movedLink->jointId ); + B3_ASSERT( movedJoint->islandIndex == movedIndex ); + movedJoint->islandIndex = removeIndex; + } + + joint->islandId = B3_NULL_INDEX; + joint->islandIndex = B3_NULL_INDEX; + island->constraintRemoveCount += 1; + + b3ValidateIsland( world, islandId ); +} + +// Find parent of a node. Use path halving to speed up further queries. +static inline int b3IslandFindParent( int* parents, int node ) +{ + // Walk the chain of parents to find the node that is its own parent (the root) + while ( parents[node] != node ) + { + int grandParent = parents[parents[node]]; + parents[node] = grandParent; + node = grandParent; + } + + return node; +} + +// Connect the components containing node1 and node2. +// Uses rank to keep tree balanced. Tracks per-component contact and joint counts. +static inline void b3IslandUnion( int* parents, int* ranks, int node1, int node2, int* contactCounts, int* jointCounts ) +{ + int root1 = b3IslandFindParent( parents, node1 ); + int root2 = b3IslandFindParent( parents, node2 ); + if ( root1 != root2 ) + { + if ( ranks[root1] < ranks[root2] ) + { + parents[root1] = root2; + contactCounts[root2] += contactCounts[root1]; + jointCounts[root2] += jointCounts[root1]; + } + else if ( ranks[root1] > ranks[root2] ) + { + parents[root2] = root1; + contactCounts[root1] += contactCounts[root2]; + jointCounts[root1] += jointCounts[root2]; + } + else + { + parents[root2] = root1; + ranks[root1] += 1; + contactCounts[root1] += contactCounts[root2]; + jointCounts[root1] += jointCounts[root2]; + } + } +} + +// This uses union-find. +// https://en.wikipedia.org/wiki/Disjoint-set_data_structure +void b3SplitIsland( b3World* world, int baseId ) +{ + b3Island* baseIsland = b3Array_Get( world->islands, baseId ); + B3_ASSERT( baseIsland->constraintRemoveCount > 0 ); + B3_ASSERT( baseIsland->setIndex == b3_awakeSet ); + + b3ValidateIsland( world, baseId ); + + // Cache base island fields before b3CreateIsland, which may reallocate + // world->islands and invalidate the baseIsland pointer. + int baseBodyCount = baseIsland->bodies.count; + int* baseBodyIds = baseIsland->bodies.data; + int baseBodyCapacity = baseIsland->bodies.capacity; + + int baseContactCount = baseIsland->contacts.count; + b3ContactLink* baseContacts = baseIsland->contacts.data; + int baseContactCapacity = baseIsland->contacts.capacity; + + int baseJointCount = baseIsland->joints.count; + b3JointLink* baseJoints = baseIsland->joints.data; + int baseJointCapacity = baseIsland->joints.capacity; + + b3Stack* alloc = &world->stack; + + // No lock is needed because I ensure the allocator is not used while this task is active. + // Allocate contactCounts and jointCounts before ranks so ranks can be freed first (LIFO arena). + int* parents = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "parents" ); + int* contactCounts = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "contact counts" ); + int* jointCounts = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "joint counts" ); + int* ranks = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "ranks" ); + for ( int i = 0; i < baseBodyCount; ++i ) + { + parents[i] = i; + ranks[i] = 0; + contactCounts[i] = 0; + jointCounts[i] = 0; + } + + b3Body* bodies = world->bodies.data; + + // Union over contacts, tracking per-component contact counts + for ( int i = 0; i < baseContactCount; ++i ) + { + int bodyIdA = baseContacts[i].bodyIdA; + int bodyIdB = baseContacts[i].bodyIdB; + B3_VALIDATE( 0 <= bodyIdA && bodyIdA < world->bodies.count ); + B3_VALIDATE( 0 <= bodyIdB && bodyIdB < world->bodies.count ); + b3Body* bodyA = bodies + bodyIdA; + b3Body* bodyB = bodies + bodyIdB; + int islandIndexA = bodyA->islandIndex; + int islandIndexB = bodyB->islandIndex; + + // Only connect non-static bodies + if ( islandIndexA != B3_NULL_INDEX && islandIndexB != B3_NULL_INDEX ) + { + B3_VALIDATE( 0 <= islandIndexA && islandIndexA < baseBodyCount ); + B3_VALIDATE( 0 <= islandIndexB && islandIndexB < baseBodyCount ); + b3IslandUnion( parents, ranks, islandIndexA, islandIndexB, contactCounts, jointCounts ); + int root = b3IslandFindParent( parents, islandIndexA ); + contactCounts[root] += 1; + } + else + { + int islandIndex = islandIndexA != B3_NULL_INDEX ? islandIndexA : islandIndexB; + int root = b3IslandFindParent( parents, islandIndex ); + contactCounts[root] += 1; + } + } + + // Union over joints, tracking per-component joint counts + for ( int i = 0; i < baseJointCount; ++i ) + { + int bodyIdA = baseJoints[i].bodyIdA; + int bodyIdB = baseJoints[i].bodyIdB; + B3_VALIDATE( 0 <= bodyIdA && bodyIdA < world->bodies.count ); + B3_VALIDATE( 0 <= bodyIdB && bodyIdB < world->bodies.count ); + b3Body* bodyA = bodies + bodyIdA; + b3Body* bodyB = bodies + bodyIdB; + int islandIndexA = bodyA->islandIndex; + int islandIndexB = bodyB->islandIndex; + + // Only connect non-static bodies + if ( islandIndexA != B3_NULL_INDEX && islandIndexB != B3_NULL_INDEX ) + { + B3_VALIDATE( 0 <= islandIndexA && islandIndexA < baseBodyCount ); + B3_VALIDATE( 0 <= islandIndexB && islandIndexB < baseBodyCount ); + b3IslandUnion( parents, ranks, islandIndexA, islandIndexB, contactCounts, jointCounts ); + int root = b3IslandFindParent( parents, islandIndexA ); + jointCounts[root] += 1; + } + else + { + int islandIndex = islandIndexA != B3_NULL_INDEX ? islandIndexA : islandIndexB; + int root = b3IslandFindParent( parents, islandIndex ); + jointCounts[root] += 1; + } + } + + // Done with ranks + b3StackFree( alloc, ranks ); + ranks = NULL; + + // Flatten all parent indices and count connected components. + int componentCount = 0; + for ( int i = 0; i < baseBodyCount; ++i ) + { + parents[i] = b3IslandFindParent( parents, i ); + if ( parents[i] == i ) + { + componentCount += 1; + } + } + + // Early return — island is still fully connected, no split needed. + if ( componentCount == 1 ) + { + baseIsland->constraintRemoveCount = 0; + b3StackFree( alloc, jointCounts ); + b3StackFree( alloc, contactCounts ); + b3StackFree( alloc, parents ); + return; + } + + // Detach body/contact/joint arrays from base island so b3DestroyIsland won't free them + baseIsland->bodies.data = NULL; + baseIsland->bodies.count = 0; + baseIsland->bodies.capacity = 0; + + baseIsland->contacts.data = NULL; + baseIsland->contacts.count = 0; + baseIsland->contacts.capacity = 0; + + baseIsland->joints.data = NULL; + baseIsland->joints.count = 0; + baseIsland->joints.capacity = 0; + + // Null so code below doesn't accidentally use this. + baseIsland = NULL; + + // Map from body index to new island index. Only set for root bodies. + int* rootMap = b3StackAlloc( alloc, baseBodyCount * sizeof( int ), "root map" ); + for ( int i = 0; i < baseBodyCount; ++i ) + { + rootMap[i] = B3_NULL_INDEX; + } + + int* componentBodyCounts = b3StackAlloc( alloc, componentCount * sizeof( int ), "component body counts" ); + int* componentContactCounts = b3StackAlloc( alloc, componentCount * sizeof( int ), "component contact counts" ); + int* componentJointCounts = b3StackAlloc( alloc, componentCount * sizeof( int ), "component joint counts" ); + int islandCount = 0; + + // Find the root body for each body and create islands as needed. + // Extract per-component counts from the root nodes' accumulated counts. + for ( int i = 0; i < baseBodyCount; ++i ) + { + int rootIndex = parents[i]; + if ( rootMap[rootIndex] == B3_NULL_INDEX ) + { + rootMap[rootIndex] = islandCount; + componentBodyCounts[islandCount] = 0; + componentContactCounts[islandCount] = contactCounts[rootIndex]; + componentJointCounts[islandCount] = jointCounts[rootIndex]; + islandCount += 1; + } + + componentBodyCounts[rootMap[rootIndex]] += 1; + } + + B3_ASSERT( islandCount == componentCount ); + + // Map from new island index to island id + int* islandIds = b3StackAlloc( alloc, islandCount * sizeof( int ), "island ids" ); + + // Create new islands and reserve body/contact/joint arrays + for ( int i = 0; i < islandCount; ++i ) + { + // WARNING: this invalidates baseIsland pointer + b3Island* newIsland = b3CreateIsland( world, b3_awakeSet ); + islandIds[i] = newIsland->islandId; + + // Reserve arrays to avoid wasteful growth and memcpy. + b3Array_Reserve( newIsland->bodies, componentBodyCounts[i] ); + b3Array_Reserve( newIsland->contacts, componentContactCounts[i] ); + b3Array_Reserve( newIsland->joints, componentJointCounts[i] ); + } + + // Assign bodies to new islands + for ( int i = 0; i < baseBodyCount; ++i ) + { + int bodyId = baseBodyIds[i]; + int root = b3IslandFindParent( parents, i ); + int newIslandId = islandIds[rootMap[root]]; + + b3Body* body = b3Array_Get( world->bodies, bodyId ); + b3Island* newIsland = b3Array_Get( world->islands, newIslandId ); + + body->islandId = newIslandId; + body->islandIndex = newIsland->bodies.count; + + // Ensure the array has the correct capacity + B3_VALIDATE( newIsland->bodies.count < newIsland->bodies.capacity ); + b3Array_Push( newIsland->bodies, bodyId ); + } + + // Assign contacts to the island of their bodies + for ( int i = 0; i < baseContactCount; ++i ) + { + b3ContactLink* link = baseContacts + i; + b3Contact* contact = b3Array_Get( world->contacts, link->contactId ); + + // Static bodies don't have an island id. + b3Body* bodyA = b3Array_Get( world->bodies, link->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, link->bodyIdB ); + int targetIslandId = bodyA->islandId != B3_NULL_INDEX ? bodyA->islandId : bodyB->islandId; + + b3Island* targetIsland = b3Array_Get( world->islands, targetIslandId ); + contact->islandId = targetIslandId; + contact->islandIndex = targetIsland->contacts.count; + + // Ensure the array has the correct capacity + B3_VALIDATE( targetIsland->contacts.count < targetIsland->contacts.capacity ); + b3Array_Push( targetIsland->contacts, *link ); + } + + // Assign joints to the island of their bodies + for ( int i = 0; i < baseJointCount; ++i ) + { + b3JointLink* link = baseJoints + i; + b3Joint* joint = b3Array_Get( world->joints, link->jointId ); + + // Static bodies don't have an island id. + b3Body* bodyA = b3Array_Get( world->bodies, link->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, link->bodyIdB ); + int targetIslandId = bodyA->islandId != B3_NULL_INDEX ? bodyA->islandId : bodyB->islandId; + + b3Island* targetIsland = b3Array_Get( world->islands, targetIslandId ); + joint->islandId = targetIslandId; + joint->islandIndex = targetIsland->joints.count; + + // Ensure the array has the correct capacity + B3_VALIDATE( targetIsland->joints.count < targetIsland->joints.capacity ); + b3Array_Push( targetIsland->joints, *link ); + } + + // Destroy the base island + b3DestroyIsland( world, baseId ); + + // Free the detached arrays manually + b3Free( baseBodyIds, baseBodyCapacity * sizeof( int ) ); + b3Free( baseContacts, baseContactCapacity * sizeof( b3ContactLink ) ); + b3Free( baseJoints, baseJointCapacity * sizeof( b3JointLink ) ); + + // Free arena items in LIFO order + b3StackFree( alloc, islandIds ); + b3StackFree( alloc, componentJointCounts ); + b3StackFree( alloc, componentContactCounts ); + b3StackFree( alloc, componentBodyCounts ); + b3StackFree( alloc, rootMap ); + b3StackFree( alloc, jointCounts ); + b3StackFree( alloc, contactCounts ); + b3StackFree( alloc, parents ); +} + +// Split an island because some contacts and/or joints have been removed. +// This is called during the constraint solve while islands are not being touched. This uses union find and +// touches a lot of memory, so it can be slow. +// Note: contacts/joints connected to static bodies must belong to an island but don't affect island connectivity +// Note: static bodies are never in an island +// Note: this task interacts with some allocators without locks under the assumption that no other tasks +// are interacting with these data structures. +void b3SplitIslandTask( void* context ) +{ + b3TracyCZoneNC( split, "Split Island", b3_colorOlive, true ); + + uint64_t ticks = b3GetTicks(); + b3World* world = context; + + B3_ASSERT( world->splitIslandId != B3_NULL_INDEX ); + + b3SplitIsland( world, world->splitIslandId ); + + world->splitIslandId = B3_NULL_INDEX; + world->profile.splitIslands += b3GetMilliseconds( ticks ); + b3TracyCZoneEnd( split ); +} + +#if B3_ENABLE_VALIDATION +void b3ValidateIsland( b3World* world, int islandId ) +{ + if ( islandId == B3_NULL_INDEX ) + { + return; + } + + b3Island* island = b3Array_Get( world->islands, islandId ); + B3_ASSERT( island->islandId == islandId ); + B3_ASSERT( island->setIndex != B3_NULL_INDEX ); + + { + B3_ASSERT( island->bodies.count > 0 ); + B3_ASSERT( island->bodies.count <= b3GetIdCount( &world->bodyIdPool ) ); + + for ( int i = 0; i < island->bodies.count; ++i ) + { + b3Body* body = b3Array_Get( world->bodies, island->bodies.data[i] ); + B3_ASSERT( body->islandId == islandId ); + B3_ASSERT( body->islandIndex == i ); + B3_ASSERT( body->setIndex == island->setIndex ); + } + } + + if ( island->contacts.count > 0 ) + { + B3_ASSERT( island->contacts.count <= b3GetIdCount( &world->contactIdPool ) ); + + for ( int i = 0; i < island->contacts.count; ++i ) + { + b3ContactLink* link = island->contacts.data + i; + b3Contact* contact = b3Array_Get( world->contacts, link->contactId ); + B3_ASSERT( contact->setIndex == island->setIndex ); + B3_ASSERT( contact->islandId == islandId ); + B3_ASSERT( contact->islandIndex == i ); + } + } + + if ( island->joints.count > 0 ) + { + B3_ASSERT( island->joints.count <= b3GetIdCount( &world->jointIdPool ) ); + + for ( int i = 0; i < island->joints.count; ++i ) + { + b3JointLink* link = island->joints.data + i; + b3Joint* joint = b3Array_Get( world->joints, link->jointId ); + B3_ASSERT( joint->setIndex == island->setIndex ); + B3_ASSERT( joint->islandId == islandId ); + B3_ASSERT( joint->islandIndex == i ); + } + } +} + +#else + +void b3ValidateIsland( b3World* world, int islandId ) +{ + B3_UNUSED( world ); + B3_UNUSED( islandId ); +} +#endif diff --git a/vendor/box3d/src/src/island.h b/vendor/box3d/src/src/island.h new file mode 100644 index 000000000..297c198dd --- /dev/null +++ b/vendor/box3d/src/src/island.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "container.h" + +#include + +typedef struct b3Contact b3Contact; +typedef struct b3Joint b3Joint; +typedef struct b3World b3World; + +// Cached contact data stored in the island for fast contiguous iteration. +// Avoids touching b3Contact during union-find in b3SplitIsland. +typedef struct b3ContactLink +{ + int contactId; + int bodyIdA; + int bodyIdB; +} b3ContactLink; + +b3DeclareArray( b3ContactLink ); + +// Cached joint data stored in the island for fast contiguous iteration. +typedef struct b3JointLink +{ + int jointId; + int bodyIdA; + int bodyIdB; +} b3JointLink; + +b3DeclareArray( b3JointLink ); + +// Deterministic solver +// +// Collide all awake contacts +// Use bit array to emit start/stop touching events in defined order, per thread. Try using contact index, assuming contacts are +// created in a deterministic order. bit-wise OR together bit arrays and issue changes: +// - start touching: merge islands - temporary linked list - mark root island dirty - wake all - largest island is root +// - stop touching: increment constraintRemoveCount + +// Persistent island for awake bodies, joints, and contacts. +// Contacts are touching. +// Contacts and joints may connect to static bodies, but static bodies are not in the island. +// https://en.wikipedia.org/wiki/Component_(graph_theory) +// https://en.wikipedia.org/wiki/Dynamic_connectivity +typedef struct b3Island +{ + // index of solver set stored in b3World + // may be B3_NULL_INDEX + int setIndex; + + // island index within set + // may be B3_NULL_INDEX + int localIndex; + + int islandId; + + // Keeps track of how many contacts have been removed from this island. + // This is used to determine if an island is a candidate for splitting. + int constraintRemoveCount; + + // I tried using a stack array for this but the data pointer goes out of + // sync when the world island array grows. + b3Array( int ) bodies; + + // Contacts and joints that belong to this island. May connect to static + // bodies not in the island. + // Each link has the two body ids so that b3SplitIsland's union-find pass + // never needs to touch b3Contact/b3Joint. + b3Array( b3ContactLink ) contacts; + b3Array( b3JointLink ) joints; + +} b3Island; + +// This is used to move islands across solver sets +typedef struct b3IslandSim +{ + int islandId; +} b3IslandSim; + +b3Island* b3CreateIsland( b3World* world, int setIndex ); +void b3DestroyIsland( b3World* world, int islandId ); + +// Link contacts into the island graph when it starts having contact points +void b3LinkContact( b3World* world, b3Contact* contact ); + +// Unlink contact from the island graph when it stops having contact points +void b3UnlinkContact( b3World* world, b3Contact* contact ); + +// Link a joint into the island graph when it is created +void b3LinkJoint( b3World* world, b3Joint* joint ); + +// Unlink a joint from the island graph when it is destroyed +void b3UnlinkJoint( b3World* world, b3Joint* joint ); + +void b3SplitIsland( b3World* world, int baseId ); +void b3SplitIslandTask( void* context ); + +void b3ValidateIsland( b3World* world, int islandId ); diff --git a/vendor/box3d/src/src/joint.c b/vendor/box3d/src/src/joint.c new file mode 100644 index 000000000..5875f55f9 --- /dev/null +++ b/vendor/box3d/src/src/joint.c @@ -0,0 +1,1748 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "joint.h" + +#include "body.h" +#include "contact.h" +#include "core.h" +#include "island.h" +#include "physics_world.h" +#include "recording.h" +#include "shape.h" +#include "solver.h" +#include "solver_set.h" + +// needed for dll export +#include "box3d/box3d.h" + +#include +#include +#include + +static b3JointDef b3DefaultJointDef( void ) +{ + b3JointDef def = { 0 }; + def.localFrameA.q = b3Quat_identity; + def.localFrameB.q = b3Quat_identity; + def.forceThreshold = FLT_MAX; + def.torqueThreshold = FLT_MAX; + def.constraintHertz = 60.0f; + def.constraintDampingRatio = 2.0f; + def.drawScale = b3GetLengthUnitsPerMeter(); + def.internalValue = B3_SECRET_COOKIE; + return def; +} + +b3ParallelJointDef b3DefaultParallelJointDef( void ) +{ + b3ParallelJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + def.hertz = 1.0f; + def.dampingRatio = 1.0f; + def.maxTorque = FLT_MAX; + return def; +} + +b3DistanceJointDef b3DefaultDistanceJointDef( void ) +{ + b3DistanceJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + def.lowerSpringForce = -FLT_MAX; + def.upperSpringForce = FLT_MAX; + def.length = 1.0f; + def.maxLength = B3_HUGE; + return def; +} + +b3MotorJointDef b3DefaultMotorJointDef( void ) +{ + b3MotorJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + return def; +} + +b3FilterJointDef b3DefaultFilterJointDef( void ) +{ + b3FilterJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + return def; +} + +b3PrismaticJointDef b3DefaultPrismaticJointDef( void ) +{ + b3PrismaticJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + return def; +} + +b3RevoluteJointDef b3DefaultRevoluteJointDef( void ) +{ + b3RevoluteJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + return def; +} + +b3SphericalJointDef b3DefaultSphericalJointDef( void ) +{ + b3SphericalJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + def.targetRotation = b3Quat_identity; + return def; +} + +b3WeldJointDef b3DefaultWeldJointDef( void ) +{ + b3WeldJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + return def; +} + +b3WheelJointDef b3DefaultWheelJointDef( void ) +{ + b3WheelJointDef def = { 0 }; + def.base = b3DefaultJointDef(); + def.enableSuspensionSpring = true; + def.suspensionHertz = 1.0f; + def.suspensionDampingRatio = 0.7f; + def.steeringHertz = 1.0f; + def.steeringDampingRatio = 0.7f; + return def; +} + +b3ExplosionDef b3DefaultExplosionDef( void ) +{ + b3ExplosionDef def = { 0 }; + def.maskBits = B3_DEFAULT_MASK_BITS; + return def; +} + +b3Joint* b3GetJointFullId( b3World* world, b3JointId jointId ) +{ + int id = jointId.index1 - 1; + b3Joint* joint = b3Array_Get( world->joints, id ); + B3_ASSERT( joint->jointId == id && joint->generation == jointId.generation ); + return joint; +} + +b3JointSim* b3GetJointSim( b3World* world, b3Joint* joint ) +{ + if ( joint->setIndex == b3_awakeSet ) + { + B3_ASSERT( 0 <= joint->colorIndex && joint->colorIndex < B3_GRAPH_COLOR_COUNT ); + b3GraphColor* color = world->constraintGraph.colors + joint->colorIndex; + return b3Array_Get( color->jointSims, joint->localIndex ); + } + + b3SolverSet* set = b3Array_Get( world->solverSets, joint->setIndex ); + return b3Array_Get( set->jointSims, joint->localIndex ); +} + +b3JointSim* b3GetJointSimCheckType( b3JointId jointId, b3JointType type ) +{ + B3_UNUSED( type ); + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + B3_ASSERT( joint->type == type ); + b3JointSim* jointSim = b3GetJointSim( world, joint ); + B3_ASSERT( jointSim->type == type ); + return jointSim; +} + +typedef struct b3JointPair +{ + b3Joint* joint; + b3JointSim* jointSim; +} b3JointPair; + +static b3JointPair b3CreateJoint( b3World* world, const b3JointDef* def, b3JointType type ) +{ + B3_ASSERT( b3IsValidTransform( def->localFrameA ) ); + B3_ASSERT( b3IsValidTransform( def->localFrameB ) ); + + b3Body* bodyA = b3GetBodyFullId( world, def->bodyIdA ); + b3Body* bodyB = b3GetBodyFullId( world, def->bodyIdB ); + + int bodyIdA = bodyA->id; + int bodyIdB = bodyB->id; + int maxSetIndex = b3MaxInt( bodyA->setIndex, bodyB->setIndex ); + + // Create joint id and joint + int jointId = b3AllocId( &world->jointIdPool ); + if ( jointId == world->joints.count ) + { + b3Array_Push( world->joints, (b3Joint){ 0 } ); + } + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + joint->jointId = jointId; + joint->userData = def->userData; + joint->generation += 1; + joint->setIndex = B3_NULL_INDEX; + joint->colorIndex = B3_NULL_INDEX; + joint->localIndex = B3_NULL_INDEX; + joint->islandId = B3_NULL_INDEX; + joint->islandIndex = B3_NULL_INDEX; + joint->drawScale = def->drawScale; + joint->type = type; + joint->collideConnected = def->collideConnected; + + // Doubly linked list on bodyA + joint->edges[0].bodyId = bodyIdA; + joint->edges[0].prevKey = B3_NULL_INDEX; + joint->edges[0].nextKey = bodyA->headJointKey; + + int keyA = ( jointId << 1 ) | 0; + if ( bodyA->headJointKey != B3_NULL_INDEX ) + { + b3Joint* jointA = b3Array_Get( world->joints, bodyA->headJointKey >> 1 ); + b3JointEdge* edgeA = jointA->edges + ( bodyA->headJointKey & 1 ); + edgeA->prevKey = keyA; + } + bodyA->headJointKey = keyA; + bodyA->jointCount += 1; + + // Doubly linked list on bodyB + joint->edges[1].bodyId = bodyIdB; + joint->edges[1].prevKey = B3_NULL_INDEX; + joint->edges[1].nextKey = bodyB->headJointKey; + + int keyB = ( jointId << 1 ) | 1; + if ( bodyB->headJointKey != B3_NULL_INDEX ) + { + b3Joint* jointB = b3Array_Get( world->joints, bodyB->headJointKey >> 1 ); + b3JointEdge* edgeB = jointB->edges + ( bodyB->headJointKey & 1 ); + edgeB->prevKey = keyB; + } + bodyB->headJointKey = keyB; + bodyB->jointCount += 1; + + b3JointSim* jointSim; + + if ( bodyA->setIndex == b3_disabledSet || bodyB->setIndex == b3_disabledSet ) + { + // if either body is disabled, create in disabled set + b3SolverSet* set = b3Array_Get( world->solverSets, b3_disabledSet ); + joint->setIndex = b3_disabledSet; + joint->localIndex = set->jointSims.count; + + jointSim = b3Array_Emplace( set->jointSims ); + memset( jointSim, 0, sizeof( b3JointSim ) ); + + jointSim->jointId = jointId; + jointSim->bodyIdA = bodyIdA; + jointSim->bodyIdB = bodyIdB; + } + else if ( bodyA->type != b3_dynamicBody && bodyB->type != b3_dynamicBody ) + { + // joint is not attached to a dynamic body + b3SolverSet* set = b3Array_Get( world->solverSets, b3_staticSet ); + joint->setIndex = b3_staticSet; + joint->localIndex = set->jointSims.count; + + jointSim = b3Array_Emplace( set->jointSims ); + memset( jointSim, 0, sizeof( b3JointSim ) ); + + jointSim->jointId = jointId; + jointSim->bodyIdA = bodyIdA; + jointSim->bodyIdB = bodyIdB; + } + else if ( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ) + { + // if either body is sleeping, wake it + if ( maxSetIndex >= b3_firstSleepingSet ) + { + b3WakeSolverSet( world, maxSetIndex ); + } + + joint->setIndex = b3_awakeSet; + + jointSim = b3CreateJointInGraph( world, joint ); + jointSim->jointId = jointId; + jointSim->bodyIdA = bodyIdA; + jointSim->bodyIdB = bodyIdB; + } + else + { + // joint connected between sleeping and/or static bodies + B3_ASSERT( bodyA->setIndex >= b3_firstSleepingSet || bodyB->setIndex >= b3_firstSleepingSet ); + B3_ASSERT( bodyA->setIndex != b3_staticSet || bodyB->setIndex != b3_staticSet ); + + // joint should go into the sleeping set (not static set) + int setIndex = maxSetIndex; + + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + joint->setIndex = setIndex; + joint->localIndex = set->jointSims.count; + + jointSim = b3Array_Emplace( set->jointSims ); + memset( jointSim, 0, sizeof( b3JointSim ) ); + + jointSim->jointId = jointId; + jointSim->bodyIdA = bodyIdA; + jointSim->bodyIdB = bodyIdB; + + if ( bodyA->setIndex != bodyB->setIndex && bodyA->setIndex >= b3_firstSleepingSet && + bodyB->setIndex >= b3_firstSleepingSet ) + { + // merge sleeping sets + b3MergeSolverSets( world, bodyA->setIndex, bodyB->setIndex ); + B3_ASSERT( bodyA->setIndex == bodyB->setIndex ); + + // fix potentially invalid set index + setIndex = bodyA->setIndex; + + b3SolverSet* mergedSet = b3Array_Get( world->solverSets, setIndex ); + + // Careful! The joint sim pointer was orphaned by the set merge. + jointSim = b3Array_Get( mergedSet->jointSims, joint->localIndex ); + } + + B3_ASSERT( joint->setIndex == setIndex ); + } + + jointSim->localFrameA = def->localFrameA; + jointSim->localFrameB = def->localFrameB; + jointSim->type = type; + jointSim->constraintHertz = def->constraintHertz; + jointSim->constraintDampingRatio = def->constraintDampingRatio; + jointSim->constraintSoftness = (b3Softness){ + .biasRate = 0.0f, + .massScale = 1.0f, + .impulseScale = 0.0f, + }; + + B3_ASSERT( b3IsValidFloat( def->forceThreshold ) && def->forceThreshold >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->torqueThreshold ) && def->torqueThreshold >= 0.0f ); + + jointSim->forceThreshold = def->forceThreshold; + jointSim->torqueThreshold = def->torqueThreshold; + + B3_ASSERT( jointSim->jointId == jointId ); + B3_ASSERT( jointSim->bodyIdA == bodyIdA ); + B3_ASSERT( jointSim->bodyIdB == bodyIdB ); + + if ( joint->setIndex > b3_disabledSet ) + { + // Add edge to island graph + b3LinkJoint( world, joint ); + } + + b3ValidateSolverSets( world ); + + return (b3JointPair){ joint, jointSim }; +} + +static void b3DestroyContactsBetweenBodies( b3World* world, b3Body* bodyA, b3Body* bodyB ) +{ + int contactKey; + int otherBodyId; + + // use the smaller of the two contact lists + if ( bodyA->contactCount < bodyB->contactCount ) + { + contactKey = bodyA->headContactKey; + otherBodyId = bodyB->id; + } + else + { + contactKey = bodyB->headContactKey; + otherBodyId = bodyA->id; + } + + // no need to wake bodies when a joint removes collision between them + bool wakeBodies = false; + + // destroy the contacts + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + contactKey = contact->edges[edgeIndex].nextKey; + + int otherEdgeIndex = edgeIndex ^ 1; + if ( contact->edges[otherEdgeIndex].bodyId == otherBodyId ) + { + // Careful, this removes the contact from the current doubly linked list + b3DestroyContact( world, contact, wakeBodies ); + } + } + + b3ValidateSolverSets( world ); +} + +void b3Joint_SetConstraintTuning( b3JointId jointId, float hertz, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, JointSetConstraintTuning, jointId, hertz, dampingRatio ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + base->constraintHertz = hertz; + base->constraintDampingRatio = dampingRatio; +} + +void b3Joint_GetConstraintTuning( b3JointId jointId, float* hertz, float* dampingRatio ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + *hertz = base->constraintHertz; + *dampingRatio = base->constraintDampingRatio; +} + +void b3Joint_SetForceThreshold( b3JointId jointId, float threshold ) +{ + B3_ASSERT( b3IsValidFloat( threshold ) && threshold >= 0.0f ); + + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, JointSetForceThreshold, jointId, threshold ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + base->forceThreshold = threshold; +} + +float b3Joint_GetForceThreshold( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + return base->forceThreshold; +} + +void b3Joint_SetTorqueThreshold( b3JointId jointId, float threshold ) +{ + B3_ASSERT( b3IsValidFloat( threshold ) && threshold >= 0.0f ); + + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, JointSetTorqueThreshold, jointId, threshold ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + base->torqueThreshold = threshold; +} + +float b3Joint_GetTorqueThreshold( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + return base->torqueThreshold; +} + +b3JointId b3CreateDistanceJoint( b3WorldId worldId, const b3DistanceJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + B3_ASSERT( b3IsValidFloat( def->length ) && def->length > 0.0f ); + B3_ASSERT( def->lowerSpringForce <= def->upperSpringForce ); + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_distanceJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->distanceJoint = (b3DistanceJoint){ 0 }; + joint->distanceJoint.length = b3MaxFloat( def->length, B3_LINEAR_SLOP ); + joint->distanceJoint.hertz = def->hertz; + joint->distanceJoint.dampingRatio = def->dampingRatio; + joint->distanceJoint.lowerSpringForce = def->lowerSpringForce; + joint->distanceJoint.upperSpringForce = def->upperSpringForce; + joint->distanceJoint.minLength = b3MaxFloat( def->minLength, B3_LINEAR_SLOP ); + joint->distanceJoint.maxLength = b3MaxFloat( def->minLength, def->maxLength ); + joint->distanceJoint.maxMotorForce = def->maxMotorForce; + joint->distanceJoint.motorSpeed = def->motorSpeed; + joint->distanceJoint.enableSpring = def->enableSpring; + joint->distanceJoint.enableLimit = def->enableLimit; + joint->distanceJoint.enableMotor = def->enableMotor; + joint->distanceJoint.impulse = 0.0f; + joint->distanceJoint.lowerImpulse = 0.0f; + joint->distanceJoint.upperImpulse = 0.0f; + joint->distanceJoint.motorImpulse = 0.0f; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateDistanceJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateMotorJoint( b3WorldId worldId, const b3MotorJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_motorJoint ); + b3JointSim* joint = pair.jointSim; + + joint->motorJoint = (b3MotorJoint){ 0 }; + joint->motorJoint.linearVelocity = def->linearVelocity; + joint->motorJoint.maxVelocityForce = def->maxVelocityForce; + joint->motorJoint.angularVelocity = def->angularVelocity; + joint->motorJoint.maxVelocityTorque = def->maxVelocityTorque; + joint->motorJoint.linearHertz = def->linearHertz; + joint->motorJoint.linearDampingRatio = def->linearDampingRatio; + joint->motorJoint.maxSpringForce = def->maxSpringForce; + joint->motorJoint.angularHertz = def->angularHertz; + joint->motorJoint.angularDampingRatio = def->angularDampingRatio; + joint->motorJoint.maxSpringTorque = def->maxSpringTorque; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateMotorJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateFilterJoint( b3WorldId worldId, const b3FilterJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_filterJoint ); + + b3JointSim* joint = pair.jointSim; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateFilterJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateParallelJoint( b3WorldId worldId, const b3ParallelJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + B3_ASSERT( b3IsValidFloat( def->hertz ) && def->hertz >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->dampingRatio ) && def->dampingRatio >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->maxTorque ) && def->maxTorque >= 0.0f ); + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_parallelJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->parallelJoint = (b3ParallelJoint){ 0 }; + joint->parallelJoint.hertz = def->hertz; + joint->parallelJoint.dampingRatio = def->dampingRatio; + joint->parallelJoint.maxTorque = def->maxTorque; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateParallelJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreatePrismaticJoint( b3WorldId worldId, const b3PrismaticJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + B3_ASSERT( def->lowerTranslation <= def->upperTranslation ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_prismaticJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->prismaticJoint = (b3PrismaticJoint){ 0 }; + joint->prismaticJoint.hertz = def->hertz; + joint->prismaticJoint.dampingRatio = def->dampingRatio; + joint->prismaticJoint.targetTranslation = def->targetTranslation; + joint->prismaticJoint.lowerTranslation = def->lowerTranslation; + joint->prismaticJoint.upperTranslation = def->upperTranslation; + joint->prismaticJoint.maxMotorForce = def->maxMotorForce; + joint->prismaticJoint.motorSpeed = def->motorSpeed; + joint->prismaticJoint.enableSpring = def->enableSpring; + joint->prismaticJoint.enableLimit = def->enableLimit; + joint->prismaticJoint.enableMotor = def->enableMotor; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreatePrismaticJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateRevoluteJoint( b3WorldId worldId, const b3RevoluteJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_revoluteJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->revoluteJoint = (b3RevoluteJoint){ 0 }; + joint->revoluteJoint.hertz = def->hertz; + joint->revoluteJoint.dampingRatio = def->dampingRatio; + joint->revoluteJoint.targetAngle = b3ClampFloat( def->targetAngle, -B3_PI, B3_PI ); + + float lowerAngle = b3MinFloat( def->lowerAngle, def->upperAngle ); + float upperAngle = b3MaxFloat( def->lowerAngle, def->upperAngle ); + joint->revoluteJoint.lowerAngle = b3ClampFloat( lowerAngle, -0.99f * B3_PI, 0.99f * B3_PI ); + joint->revoluteJoint.upperAngle = b3ClampFloat( upperAngle, -0.99f * B3_PI, 0.99f * B3_PI ); + + joint->revoluteJoint.maxMotorTorque = def->maxMotorTorque; + joint->revoluteJoint.motorSpeed = def->motorSpeed; + joint->revoluteJoint.enableSpring = def->enableSpring; + joint->revoluteJoint.enableLimit = def->enableLimit; + joint->revoluteJoint.enableMotor = def->enableMotor; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateRevoluteJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateSphericalJoint( b3WorldId worldId, const b3SphericalJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + B3_ASSERT( 0.0f <= def->coneAngle && def->coneAngle <= 0.99f * B3_PI ); + B3_ASSERT( b3IsValidQuat( def->targetRotation ) ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_sphericalJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->sphericalJoint = (b3SphericalJoint){ 0 }; + joint->sphericalJoint.hertz = def->hertz; + joint->sphericalJoint.dampingRatio = def->dampingRatio; + joint->sphericalJoint.targetRotation = def->targetRotation; + joint->sphericalJoint.coneAngle = b3ClampFloat( def->coneAngle, 0.0f, 0.5f * B3_PI ); + + float lowerAngle = b3MinFloat( def->lowerTwistAngle, def->upperTwistAngle ); + float upperAngle = b3MaxFloat( def->lowerTwistAngle, def->upperTwistAngle ); + joint->sphericalJoint.lowerTwistAngle = b3ClampFloat( lowerAngle, -0.99f * B3_PI, 0.99f * B3_PI ); + joint->sphericalJoint.upperTwistAngle = b3ClampFloat( upperAngle, -0.99f * B3_PI, 0.99f * B3_PI ); + + joint->sphericalJoint.maxMotorTorque = def->maxMotorTorque; + joint->sphericalJoint.motorVelocity = def->motorVelocity; + joint->sphericalJoint.enableSpring = def->enableSpring; + joint->sphericalJoint.enableConeLimit = def->enableConeLimit; + joint->sphericalJoint.enableTwistLimit = def->enableTwistLimit; + joint->sphericalJoint.enableMotor = def->enableMotor; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateSphericalJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateWeldJoint( b3WorldId worldId, const b3WeldJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + B3_ASSERT( 0.0f <= def->angularHertz ); + B3_ASSERT( 0.0f <= def->angularDampingRatio ); + B3_ASSERT( 0.0f <= def->linearHertz ); + B3_ASSERT( 0.0f <= def->linearDampingRatio ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_weldJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->weldJoint = (b3WeldJoint){ 0 }; + joint->weldJoint.linearHertz = def->linearHertz; + joint->weldJoint.linearDampingRatio = def->linearDampingRatio; + joint->weldJoint.angularHertz = def->angularHertz; + joint->weldJoint.angularDampingRatio = def->angularDampingRatio; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateWeldJoint, jointId, worldId, *def ); + return jointId; +} + +b3JointId b3CreateWheelJoint( b3WorldId worldId, const b3WheelJointDef* def ) +{ + B3_CHECK_JOINT_DEF( def ); + B3_ASSERT( def->lowerSuspensionLimit <= def->upperSuspensionLimit ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if (world == NULL) + { + return (b3JointId){ 0 }; + } + + b3JointPair pair = b3CreateJoint( world, &def->base, b3_wheelJoint ); + + b3JointSim* joint = pair.jointSim; + + joint->wheelJoint = (b3WheelJoint){ 0 }; + joint->wheelJoint.enableSuspensionSpring = def->enableSuspensionSpring; + joint->wheelJoint.suspensionHertz = def->suspensionHertz; + joint->wheelJoint.suspensionDampingRatio = def->suspensionDampingRatio; + joint->wheelJoint.enableSuspensionLimit = def->enableSuspensionLimit; + joint->wheelJoint.lowerSuspensionLimit = def->lowerSuspensionLimit; + joint->wheelJoint.upperSuspensionLimit = def->upperSuspensionLimit; + joint->wheelJoint.enableSpinMotor = def->enableSpinMotor; + joint->wheelJoint.maxSpinTorque = def->maxSpinTorque; + joint->wheelJoint.spinSpeed = def->spinSpeed; + + joint->wheelJoint.enableSteering = def->enableSteering; + joint->wheelJoint.steeringHertz = def->steeringHertz; + joint->wheelJoint.steeringDampingRatio = def->steeringDampingRatio; + joint->wheelJoint.targetSteeringAngle = def->targetSteeringAngle; + joint->wheelJoint.maxSteeringTorque = def->maxSteeringTorque; + joint->wheelJoint.enableSteeringLimit = def->enableSteeringLimit; + joint->wheelJoint.lowerSteeringLimit = def->lowerSteeringLimit; + joint->wheelJoint.upperSteeringLimit = def->upperSteeringLimit; + + b3JointId jointId = { joint->jointId + 1, world->worldId, pair.joint->generation }; + B3_REC_CREATE( world, CreateWheelJoint, jointId, worldId, *def ); + return jointId; +} + +void b3DestroyJointInternal( b3World* world, b3Joint* joint, bool wakeBodies ) +{ + int jointId = joint->jointId; + + b3JointEdge* edgeA = joint->edges + 0; + b3JointEdge* edgeB = joint->edges + 1; + + int idA = edgeA->bodyId; + int idB = edgeB->bodyId; + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + // Remove from body A + if ( edgeA->prevKey != B3_NULL_INDEX ) + { + b3Joint* prevJoint = b3Array_Get( world->joints, edgeA->prevKey >> 1 ); + b3JointEdge* prevEdge = prevJoint->edges + ( edgeA->prevKey & 1 ); + prevEdge->nextKey = edgeA->nextKey; + } + + if ( edgeA->nextKey != B3_NULL_INDEX ) + { + b3Joint* nextJoint = b3Array_Get( world->joints, edgeA->nextKey >> 1 ); + b3JointEdge* nextEdge = nextJoint->edges + ( edgeA->nextKey & 1 ); + nextEdge->prevKey = edgeA->prevKey; + } + + int edgeKeyA = ( jointId << 1 ) | 0; + if ( bodyA->headJointKey == edgeKeyA ) + { + bodyA->headJointKey = edgeA->nextKey; + } + + bodyA->jointCount -= 1; + + // Remove from body B + if ( edgeB->prevKey != B3_NULL_INDEX ) + { + b3Joint* prevJoint = b3Array_Get( world->joints, edgeB->prevKey >> 1 ); + b3JointEdge* prevEdge = prevJoint->edges + ( edgeB->prevKey & 1 ); + prevEdge->nextKey = edgeB->nextKey; + } + + if ( edgeB->nextKey != B3_NULL_INDEX ) + { + b3Joint* nextJoint = b3Array_Get( world->joints, edgeB->nextKey >> 1 ); + b3JointEdge* nextEdge = nextJoint->edges + ( edgeB->nextKey & 1 ); + nextEdge->prevKey = edgeB->prevKey; + } + + int edgeKeyB = ( jointId << 1 ) | 1; + if ( bodyB->headJointKey == edgeKeyB ) + { + bodyB->headJointKey = edgeB->nextKey; + } + + bodyB->jointCount -= 1; + + if ( joint->islandId != B3_NULL_INDEX ) + { + B3_ASSERT( joint->setIndex > b3_disabledSet ); + b3UnlinkJoint( world, joint ); + } + else + { + B3_ASSERT( joint->setIndex <= b3_disabledSet ); + } + + // Remove joint from solver set that owns it + int setIndex = joint->setIndex; + int localIndex = joint->localIndex; + + if ( setIndex == b3_awakeSet ) + { + b3RemoveJointFromGraph( world, joint->edges[0].bodyId, joint->edges[1].bodyId, joint->colorIndex, localIndex ); + } + else + { + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + int movedIndex = b3Array_RemoveSwap( set->jointSims, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix moved joint + b3JointSim* movedJointSim = set->jointSims.data + localIndex; + int movedId = movedJointSim->jointId; + b3Joint* movedJoint = b3Array_Get( world->joints, movedId ); + B3_ASSERT( movedJoint->localIndex == movedIndex ); + movedJoint->localIndex = localIndex; + } + } + + // Free joint and id (preserve joint revision) + joint->setIndex = B3_NULL_INDEX; + joint->localIndex = B3_NULL_INDEX; + joint->colorIndex = B3_NULL_INDEX; + joint->jointId = B3_NULL_INDEX; + b3FreeId( &world->jointIdPool, jointId ); + + if ( wakeBodies ) + { + b3WakeBody( world, bodyA ); + b3WakeBody( world, bodyB ); + } + + b3ValidateSolverSets( world ); +} + +void b3DestroyJoint( b3JointId jointId, bool wakeAttached ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, DestroyJoint, jointId, wakeAttached ); + + b3Joint* joint = b3GetJointFullId( world, jointId ); + + b3DestroyJointInternal( world, joint, wakeAttached ); +} + +b3JointType b3Joint_GetType( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return joint->type; +} + +b3BodyId b3Joint_GetBodyA( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return b3MakeBodyId( world, joint->edges[0].bodyId ); +} + +b3BodyId b3Joint_GetBodyB( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return b3MakeBodyId( world, joint->edges[1].bodyId ); +} + +b3WorldId b3Joint_GetWorld( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + return (b3WorldId){ (uint16_t)( jointId.world0 + 1 ), world->generation }; +} + +void b3Joint_SetLocalFrameA( b3JointId jointId, b3Transform localFrame ) +{ + B3_ASSERT( b3IsValidTransform( localFrame ) ); + + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, JointSetLocalFrameA, jointId, localFrame ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* jointSim = b3GetJointSim( world, joint ); + jointSim->localFrameA = localFrame; +} + +b3Transform b3Joint_GetLocalFrameA( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* jointSim = b3GetJointSim( world, joint ); + return jointSim->localFrameA; +} + +void b3Joint_SetLocalFrameB( b3JointId jointId, b3Transform localFrame ) +{ + B3_ASSERT( b3IsValidTransform( localFrame ) ); + + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, JointSetLocalFrameB, jointId, localFrame ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* jointSim = b3GetJointSim( world, joint ); + jointSim->localFrameB = localFrame; +} + +b3Transform b3Joint_GetLocalFrameB( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* jointSim = b3GetJointSim( world, joint ); + return jointSim->localFrameB; +} + +void b3Joint_SetCollideConnected( b3JointId jointId, bool shouldCollide ) +{ + b3World* world = b3GetUnlockedWorld( jointId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, JointSetCollideConnected, jointId, shouldCollide ); + + b3Joint* joint = b3GetJointFullId( world, jointId ); + if ( joint->collideConnected == shouldCollide ) + { + return; + } + + joint->collideConnected = shouldCollide; + + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + + if ( shouldCollide ) + { + // need to tell the broad-phase to look for new pairs for one of the + // two bodies. Pick the one with the fewest shapes. + int shapeCountA = bodyA->shapeCount; + int shapeCountB = bodyB->shapeCount; + + int shapeId = shapeCountA < shapeCountB ? bodyA->headShapeId : bodyB->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + if ( shape->proxyKey != B3_NULL_INDEX ) + { + b3BufferMove( &world->broadPhase, shape->proxyKey ); + } + + shapeId = shape->nextShapeId; + } + } + else + { + b3DestroyContactsBetweenBodies( world, bodyA, bodyB ); + } +} + +bool b3Joint_GetCollideConnected( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return joint->collideConnected; +} + +void b3Joint_SetUserData( b3JointId jointId, void* userData ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + joint->userData = userData; +} + +void* b3Joint_GetUserData( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return joint->userData; +} + +void b3Joint_WakeBodies( b3JointId jointId ) +{ + b3World* world = b3GetUnlockedWorld( jointId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, JointWakeBodies, jointId ); + + world->locked = true; + + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + + b3WakeBody( world, bodyA ); + b3WakeBody( world, bodyB ); + + world->locked = false; +} + +void b3GetJointReaction( b3World* world, b3JointSim* sim, float invTimeStep, float* force, float* torque ) +{ + float linearImpulse = 0.0f; + float angularImpulse = 0.0f; + + switch ( sim->type ) + { + case b3_parallelJoint: + { + b3ParallelJoint* joint = &sim->parallelJoint; + b3Vec3 impulse = { + .x = joint->perpImpulse.x, + .y = joint->perpImpulse.y, + .z = 0.0f, + }; + angularImpulse = b3Length( impulse ); + } + break; + + case b3_distanceJoint: + { + b3DistanceJoint* joint = &sim->distanceJoint; + linearImpulse = b3AbsFloat( joint->impulse + joint->lowerImpulse - joint->upperImpulse + joint->motorImpulse ); + } + break; + + case b3_motorJoint: + { + b3MotorJoint* joint = &sim->motorJoint; + linearImpulse = b3Length( b3Add( joint->linearVelocityImpulse, joint->linearSpringImpulse ) ); + angularImpulse = b3Length( b3Add( joint->angularVelocityImpulse, joint->angularSpringImpulse ) ); + } + break; + + case b3_prismaticJoint: + { + b3PrismaticJoint* joint = &sim->prismaticJoint; + b3Vec3 impulse = { + .x = joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse, + .y = joint->perpImpulse.x, + .z = joint->perpImpulse.y, + }; + linearImpulse = b3Length( impulse ); + angularImpulse = b3Length( joint->angularImpulse ); + } + break; + + case b3_revoluteJoint: + { + b3RevoluteJoint* joint = &sim->revoluteJoint; + linearImpulse = b3Length( joint->linearImpulse ); + b3Vec3 impulse = { + .x = joint->perpImpulse.x, + .y = joint->perpImpulse.y, + .z = joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse, + }; + angularImpulse = b3Length( impulse ); + } + break; + + case b3_sphericalJoint: + { + // todo improve performance + b3SphericalJoint* joint = &sim->sphericalJoint; + linearImpulse = b3Length( joint->linearImpulse ); + + b3WorldTransform xfA = b3GetBodyTransform( world, sim->bodyIdA ); + b3WorldTransform xfB = b3GetBodyTransform( world, sim->bodyIdB ); + b3Quat qA = b3MulQuat( xfA.q, sim->localFrameA.q ); + b3Quat qB = b3MulQuat( xfB.q, sim->localFrameB.q ); + + // Cone axis is the z-axis of body A. + b3Vec3 coneAxis = b3RotateVector( qA, b3Vec3_axisZ ); + b3Vec3 twistAxis = b3RotateVector( qB, b3Vec3_axisZ ); + b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) ); + + b3Vec3 impulse = b3Add( joint->springImpulse, joint->motorImpulse ); + impulse = b3MulAdd( impulse, joint->lowerTwistImpulse - joint->upperTwistImpulse, twistAxis ); + impulse = b3MulAdd( impulse, joint->swingImpulse, swingAxis ); + + angularImpulse = b3Length( impulse ); + } + break; + + case b3_weldJoint: + { + b3WeldJoint* joint = &sim->weldJoint; + linearImpulse = b3Length( joint->linearImpulse ); + angularImpulse = b3Length( joint->angularImpulse ); + } + break; + + case b3_wheelJoint: + { + // todo probably wrong + b3WheelJoint* joint = &sim->wheelJoint; + b3Vec2 perpImpulse = joint->linearImpulse; + float axialImpulse = joint->suspensionSpringImpulse + joint->lowerSuspensionImpulse - joint->upperSuspensionImpulse; + linearImpulse = sqrtf( perpImpulse.x * perpImpulse.x + perpImpulse.y * perpImpulse.y + axialImpulse * axialImpulse ); + angularImpulse = b3AbsFloat( joint->spinImpulse ); + } + break; + + default: + break; + } + + *force = linearImpulse * invTimeStep; + *torque = angularImpulse * invTimeStep; +} + +static b3Vec3 b3GetJointConstraintForce( b3World* world, b3Joint* joint ) +{ + b3JointSim* base = b3GetJointSim( world, joint ); + + switch ( joint->type ) + { + case b3_parallelJoint: + return b3Vec3_zero; + + case b3_distanceJoint: + return b3GetDistanceJointForce( world, base ); + + case b3_filterJoint: + return b3Vec3_zero; + + case b3_motorJoint: + return b3GetMotorJointForce( world, base ); + + case b3_prismaticJoint: + return b3GetPrismaticJointForce( world, base ); + + case b3_revoluteJoint: + return b3GetRevoluteJointForce( world, base ); + + case b3_sphericalJoint: + return b3GetSphericalJointForce( world, base ); + + case b3_weldJoint: + return b3GetWeldJointForce( world, base ); + + case b3_wheelJoint: + return b3GetWheelJointForce( world, base ); + + default: + B3_ASSERT( false ); + return b3Vec3_zero; + } +} + +static b3Vec3 b3GetJointConstraintTorque( b3World* world, b3Joint* joint ) +{ + b3JointSim* base = b3GetJointSim( world, joint ); + + switch ( joint->type ) + { + case b3_parallelJoint: + return b3GetParallelJointTorque( world, base ); + + case b3_distanceJoint: + return b3Vec3_zero; + + case b3_filterJoint: + return b3Vec3_zero; + + case b3_motorJoint: + return b3GetMotorJointTorque( world, base ); + + case b3_prismaticJoint: + return b3GetPrismaticJointTorque( world, base ); + + case b3_revoluteJoint: + return b3GetRevoluteJointTorque( world, base ); + + case b3_sphericalJoint: + return b3GetSphericalJointTorque( world, base ); + + case b3_weldJoint: + return b3GetWeldJointTorque( world, base ); + + case b3_wheelJoint: + return b3GetWheelJointTorque( world, base ); + + default: + B3_ASSERT( false ); + return b3Vec3_zero; + } +} + +b3Vec3 b3Joint_GetConstraintForce( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return b3GetJointConstraintForce( world, joint ); +} + +b3Vec3 b3Joint_GetConstraintTorque( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + return b3GetJointConstraintTorque( world, joint ); +} + +float b3Joint_GetLinearSeparation( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + + b3WorldTransform xfA = b3GetBodyTransform( world, joint->edges[0].bodyId ); + b3WorldTransform xfB = b3GetBodyTransform( world, joint->edges[1].bodyId ); + + b3Pos pA = b3TransformWorldPoint( xfA, base->localFrameA.p ); + b3Pos pB = b3TransformWorldPoint( xfB, base->localFrameB.p ); + b3Vec3 dp = b3SubPos( pB, pA ); + + switch ( joint->type ) + { + case b3_parallelJoint: + return 0.0f; + + case b3_distanceJoint: + { + b3DistanceJoint* distanceJoint = &base->distanceJoint; + float length = b3Length( dp ); + if ( distanceJoint->enableSpring ) + { + if ( distanceJoint->enableLimit ) + { + if ( length < distanceJoint->minLength ) + { + return distanceJoint->minLength - length; + } + + if ( length > distanceJoint->maxLength ) + { + return length - distanceJoint->maxLength; + } + + return 0.0f; + } + + return 0.0f; + } + + return b3AbsFloat( length - distanceJoint->length ); + } + + case b3_motorJoint: + return 0.0f; + + case b3_filterJoint: + return 0.0f; + + case b3_prismaticJoint: + { + b3PrismaticJoint* prismaticJoint = &base->prismaticJoint; + b3Vec3 axisA = b3RotateVector( xfA.q, b3Vec3_axisX ); + b3Vec3 perpA = b3Perp( axisA ); + float perpendicularSeparation = b3AbsFloat( b3Dot( perpA, dp ) ); + float limitSeparation = 0.0f; + + if ( prismaticJoint->enableLimit ) + { + float translation = b3Dot( axisA, dp ); + if ( translation < prismaticJoint->lowerTranslation ) + { + limitSeparation = prismaticJoint->lowerTranslation - translation; + } + + if ( prismaticJoint->upperTranslation < translation ) + { + limitSeparation = translation - prismaticJoint->upperTranslation; + } + } + + return sqrtf( perpendicularSeparation * perpendicularSeparation + limitSeparation * limitSeparation ); + } + + case b3_revoluteJoint: + return b3Length( dp ); + + case b3_sphericalJoint: + return b3Length( dp ); + + case b3_weldJoint: + { + b3WeldJoint* weldJoint = &base->weldJoint; + if ( weldJoint->linearHertz == 0.0f ) + { + return b3Length( dp ); + } + + return 0.0f; + } + + case b3_wheelJoint: + { + b3WheelJoint* wheelJoint = &base->wheelJoint; + b3Vec3 axisA = b3RotateVector( xfA.q, b3Vec3_axisX ); + b3Vec3 perpA = b3Perp( axisA ); + float perpendicularSeparation = b3AbsFloat( b3Dot( perpA, dp ) ); + float limitSeparation = 0.0f; + + if ( wheelJoint->enableSuspensionLimit ) + { + float translation = b3Dot( axisA, dp ); + if ( translation < wheelJoint->lowerSuspensionLimit ) + { + limitSeparation = wheelJoint->lowerSuspensionLimit - translation; + } + + if ( wheelJoint->upperSuspensionLimit < translation ) + { + limitSeparation = translation - wheelJoint->upperSuspensionLimit; + } + } + + return sqrtf( perpendicularSeparation * perpendicularSeparation + limitSeparation * limitSeparation ); + } + + default: + B3_ASSERT( false ); + return 0.0f; + } +} + +float b3Joint_GetAngularSeparation( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + + b3WorldTransform xfA = b3GetBodyTransform( world, joint->edges[0].bodyId ); + b3WorldTransform xfB = b3GetBodyTransform( world, joint->edges[1].bodyId ); + + b3Quat relQ = b3InvMulQuat( xfA.q, xfB.q ); + + switch ( joint->type ) + { + case b3_parallelJoint: + { + // Remove hinge angle + relQ.v.z = 0.0f; + return b3GetQuatAngle( relQ ); + } + + case b3_distanceJoint: + return 0.0f; + + case b3_motorJoint: + return 0.0f; + + case b3_filterJoint: + return 0.0f; + + case b3_prismaticJoint: + return b3GetQuatAngle( relQ ); + + case b3_revoluteJoint: + { + b3RevoluteJoint* revoluteJoint = &base->revoluteJoint; + if ( revoluteJoint->enableLimit ) + { + float angle = b3GetTwistAngle( relQ ); + if ( angle < revoluteJoint->lowerAngle ) + { + return b3GetQuatAngle( relQ ); + } + + if ( revoluteJoint->upperAngle < angle ) + { + return b3GetQuatAngle( relQ ); + } + } + + // Remove hinge angle + relQ.v.z = 0.0f; + return b3GetQuatAngle( relQ ); + } + + case b3_sphericalJoint: + { + b3SphericalJoint* sphericalJoint = &base->sphericalJoint; + float sum = 0.0f; + if ( sphericalJoint->enableConeLimit ) + { + float swingAngle = b3GetSwingAngle( relQ ); + sum += b3MaxFloat( 0.0f, swingAngle - sphericalJoint->coneAngle ); + } + + if ( sphericalJoint->enableTwistLimit ) + { + float twistAngle = b3GetTwistAngle( relQ ); + sum += b3MaxFloat( 0.0f, sphericalJoint->lowerTwistAngle - twistAngle ); + sum += b3MaxFloat( 0.0f, twistAngle - sphericalJoint->upperTwistAngle ); + } + + return sum; + } + + case b3_weldJoint: + { + b3WeldJoint* weldJoint = &base->weldJoint; + if ( weldJoint->angularHertz == 0.0f ) + { + return b3GetQuatAngle( relQ ); + } + + return 0.0f; + } + + case b3_wheelJoint: + // todo + B3_ASSERT( false ); + return 0.0f; + + default: + B3_ASSERT( false ); + return 0.0f; + } +} + +#if 0 +void b3Joint_SetSpringRotationTarget( b3JointId jointId, b3Quat relativeBodyRotation, float hertz ) +{ + B3_ASSERT( b3IsValidQuat( relativeBodyRotation ) ); + B3_ASSERT( b3IsValidFloat( hertz ) && hertz > 0.0f ); + + b3World* world = b3GetWorld( jointId.world0 ); + b3Joint* joint = b3GetJointFullId( world, jointId ); + b3JointSim* base = b3GetJointSim( world, joint ); + + b3Quat qA = base->localFrameA.q; + b3Quat qB = b3MulQuat( relativeBodyRotation, base->localFrameB.q ); + + // This keeps the twist angle in the range [-pi, pi] + if ( b3DotQuat( qA, qB ) < 0.0f ) + { + qA = -qA; + } + + b3Quat relQ = b3InvMulQuat( qA, qB ); + + switch ( joint->type ) + { + case b3_revoluteJoint: + base->revoluteJoint.targetAngle = b3GetTwistAngle( relQ ); + base->revoluteJoint.hertz = hertz; + break; + + case b3_sphericalJoint: + base->sphericalJoint.targetRotation = relQ; + base->sphericalJoint.hertz = hertz; + break; + + default: + break; + } +} +#endif + +void b3PrepareJoint( b3JointSim* joint, b3StepContext* context ) +{ + // Clamp joint hertz based on the time step to reduce jitter. + float hertz = b3MinFloat( joint->constraintHertz, 0.25f * context->inv_h ); + joint->constraintSoftness = b3MakeSoft( hertz, joint->constraintDampingRatio, context->h ); + + switch ( joint->type ) + { + case b3_parallelJoint: + b3PrepareParallelJoint( joint, context ); + break; + + case b3_distanceJoint: + b3PrepareDistanceJoint( joint, context ); + break; + + case b3_filterJoint: + break; + + case b3_motorJoint: + b3PrepareMotorJoint( joint, context ); + break; + + case b3_prismaticJoint: + b3PreparePrismaticJoint( joint, context ); + break; + + case b3_revoluteJoint: + b3PrepareRevoluteJoint( joint, context ); + break; + + case b3_sphericalJoint: + b3PrepareSphericalJoint( joint, context ); + break; + + case b3_weldJoint: + b3PrepareWeldJoint( joint, context ); + break; + + case b3_wheelJoint: + b3PrepareWheelJoint( joint, context ); + break; + + default: + B3_ASSERT( false ); + } +} + +void b3WarmStartJoint( b3JointSim* joint, b3StepContext* context ) +{ + switch ( joint->type ) + { + case b3_parallelJoint: + b3WarmStartParallelJoint( joint, context ); + break; + + case b3_distanceJoint: + b3WarmStartDistanceJoint( joint, context ); + break; + + case b3_filterJoint: + break; + + case b3_motorJoint: + b3WarmStartMotorJoint( joint, context ); + break; + + case b3_prismaticJoint: + b3WarmStartPrismaticJoint( joint, context ); + break; + + case b3_revoluteJoint: + b3WarmStartRevoluteJoint( joint, context ); + break; + + case b3_sphericalJoint: + b3WarmStartSphericalJoint( joint, context ); + break; + + case b3_weldJoint: + b3WarmStartWeldJoint( joint, context ); + break; + + case b3_wheelJoint: + b3WarmStartWheelJoint( joint, context ); + break; + + default: + B3_ASSERT( false ); + } +} + +void b3SolveJoint( b3JointSim* joint, b3StepContext* context, bool useBias ) +{ + B3_UNUSED( useBias ); + + switch ( joint->type ) + { + case b3_parallelJoint: + b3SolveParallelJoint( joint, context ); + break; + + case b3_distanceJoint: + b3SolveDistanceJoint( joint, context, useBias ); + break; + + case b3_filterJoint: + break; + + case b3_motorJoint: + b3SolveMotorJoint( joint, context ); + break; + + case b3_prismaticJoint: + b3SolvePrismaticJoint( joint, context, useBias ); + break; + + case b3_revoluteJoint: + b3SolveRevoluteJoint( joint, context, useBias ); + break; + + case b3_sphericalJoint: + b3SolveSphericalJoint( joint, context, useBias ); + break; + + case b3_weldJoint: + b3SolveWeldJoint( joint, context, useBias ); + break; + + case b3_wheelJoint: + b3SolveWheelJoint( joint, context, useBias ); + break; + + default: + B3_ASSERT( false ); + } +} + +void b3PrepareJoints_Overflow( b3StepContext* context ) +{ + b3TracyCZoneNC( prepare_joints, "PrepJoints", b3_colorOldLace, true ); + + b3ConstraintGraph* graph = context->graph; + b3JointSim* joints = graph->colors[B3_OVERFLOW_INDEX].jointSims.data; + int jointCount = graph->colors[B3_OVERFLOW_INDEX].jointSims.count; + + for ( int i = 0; i < jointCount; ++i ) + { + b3JointSim* joint = joints + i; + b3PrepareJoint( joint, context ); + } + + b3TracyCZoneEnd( prepare_joints ); +} + +void b3WarmStartJoints_Overflow( b3StepContext* context ) +{ + b3TracyCZoneNC( prepare_joints, "PrepJoints", b3_colorOldLace, true ); + + b3ConstraintGraph* graph = context->graph; + b3JointSim* joints = graph->colors[B3_OVERFLOW_INDEX].jointSims.data; + int jointCount = graph->colors[B3_OVERFLOW_INDEX].jointSims.count; + + for ( int i = 0; i < jointCount; ++i ) + { + b3JointSim* joint = joints + i; + b3WarmStartJoint( joint, context ); + } + + b3TracyCZoneEnd( prepare_joints ); +} + +void b3SolveJoints_Overflow( b3StepContext* context, bool useBias ) +{ + b3TracyCZoneNC( solve_joints, "SolveJoints", b3_colorLemonChiffon, true ); + + b3ConstraintGraph* graph = context->graph; + b3JointSim* joints = graph->colors[B3_OVERFLOW_INDEX].jointSims.data; + int jointCount = graph->colors[B3_OVERFLOW_INDEX].jointSims.count; + + for ( int i = 0; i < jointCount; ++i ) + { + b3JointSim* joint = joints + i; + b3SolveJoint( joint, context, useBias ); + } + + b3TracyCZoneEnd( solve_joints ); +} + +void b3DrawJoint( b3DebugDraw* draw, b3World* world, b3Joint* joint ) +{ + b3Body* bodyA = b3Array_Get( world->bodies, joint->edges[0].bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, joint->edges[1].bodyId ); + if ( bodyA->setIndex == b3_disabledSet || bodyB->setIndex == b3_disabledSet ) + { + return; + } + + b3JointSim* jointSim = b3GetJointSim( world, joint ); + + b3WorldTransform transformA = b3GetBodyTransformQuick( world, bodyA ); + b3WorldTransform transformB = b3GetBodyTransformQuick( world, bodyB ); + b3Pos pA = b3TransformWorldPoint( transformA, jointSim->localFrameA.p ); + b3Pos pB = b3TransformWorldPoint( transformB, jointSim->localFrameB.p ); + + b3HexColor color = b3_colorDarkSeaGreen; + + float scale = b3MaxFloat( 0.0001f, draw->jointScale * joint->drawScale ); + + switch ( joint->type ) + { + case b3_parallelJoint: + b3DrawParallelJoint( draw, jointSim, transformA, transformB, scale ); + break; + + case b3_distanceJoint: + b3DrawDistanceJoint( draw, jointSim, transformA, transformB ); + break; + + case b3_filterJoint: + draw->DrawSegmentFcn( pA, pB, b3_colorGold, draw->context ); + break; + + case b3_motorJoint: + draw->DrawSegmentFcn( pA, pB, b3_colorPlum, draw->context ); + draw->DrawPointFcn( pA, 8.0f, b3_colorYellowGreen, draw->context ); + draw->DrawPointFcn( pB, 8.0f, b3_colorPlum, draw->context ); + break; + + case b3_prismaticJoint: + b3DrawPrismaticJoint( draw, jointSim, transformA, transformB, scale ); + break; + + case b3_revoluteJoint: + b3DrawRevoluteJoint( draw, jointSim, transformA, transformB, scale ); + break; + + case b3_sphericalJoint: + b3DrawSphericalJoint( draw, jointSim, transformA, transformB, scale ); + break; + + case b3_weldJoint: + b3DrawWeldJoint( draw, jointSim, transformA, transformB, scale ); + break; + + case b3_wheelJoint: + b3DrawWheelJoint( draw, jointSim, transformA, transformB, scale ); + break; + + default: + draw->DrawSegmentFcn( transformA.p, pA, color, draw->context ); + draw->DrawSegmentFcn( pA, pB, color, draw->context ); + draw->DrawSegmentFcn( transformB.p, pB, color, draw->context ); + break; + } + + if ( draw->drawGraphColors ) + { + b3HexColor graphColors[B3_GRAPH_COLOR_COUNT] = { + b3_colorRed, b3_colorOrange, b3_colorYellow, b3_colorGreen, b3_colorCyan, b3_colorBlue, + b3_colorViolet, b3_colorPink, b3_colorChocolate, b3_colorGoldenRod, b3_colorCoral, b3_colorRosyBrown, + b3_colorAqua, b3_colorPeru, b3_colorLime, b3_colorGold, b3_colorPlum, b3_colorSnow, + b3_colorTeal, b3_colorKhaki, b3_colorSalmon, b3_colorPeachPuff, b3_colorHoneyDew, b3_colorBlack, + }; + + int colorIndex = joint->colorIndex; + if ( colorIndex != B3_NULL_INDEX ) + { + b3Pos p = b3LerpPosition( pA, pB, 0.5f ); + draw->DrawPointFcn( p, 5.0f, graphColors[colorIndex], draw->context ); + } + } + + if ( draw->drawJointExtras ) + { + b3Vec3 force = b3GetJointConstraintForce( world, joint ); + b3Vec3 torque = b3GetJointConstraintTorque( world, joint ); + b3Pos p = b3LerpPosition( pA, pB, 0.5f ); + + draw->DrawSegmentFcn( p, b3OffsetPos( p, b3MulSV( 0.001f, force ) ), b3_colorAzure, draw->context ); + + char buffer[64]; + snprintf( buffer, 64, "f = %g, t = %g", b3Length( force ), b3Length( torque ) ); + draw->DrawStringFcn( p, buffer, b3_colorAzure, draw->context ); + } +} diff --git a/vendor/box3d/src/src/joint.h b/vendor/box3d/src/src/joint.h new file mode 100644 index 000000000..64171b957 --- /dev/null +++ b/vendor/box3d/src/src/joint.h @@ -0,0 +1,415 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "math_internal.h" +#include "solver.h" + +#include "box3d/types.h" + +typedef struct b3DebugDraw b3DebugDraw; +typedef struct b3StepContext b3StepContext; +typedef struct b3World b3World; + +/// A joint edge is used to connect bodies and joints together +/// in a joint graph where each body is a node and each joint +/// is an edge. A joint edge belongs to a doubly linked list +/// maintained in each attached body. Each joint has two joint +/// nodes, one for each attached body. +typedef struct b3JointEdge +{ + int bodyId; + int prevKey; + int nextKey; +} b3JointEdge; + +// Map from b3JointId to b3Joint in the solver sets +typedef struct b3Joint +{ + void* userData; + + // index of simulation set stored in b3World + // B3_NULL_INDEX when slot is free + int setIndex; + + // index into the constraint graph color array, may be B3_NULL_INDEX for sleeping/disabled joints + // B3_NULL_INDEX when slot is free + int colorIndex; + + // joint index within set or graph color + // B3_NULL_INDEX when slot is free + int localIndex; + + b3JointEdge edges[2]; + + int jointId; + int islandId; + + // Index into the island's joints array for O(1) swap-removal. + // B3_NULL_INDEX when not in an island. + int islandIndex; + + float drawScale; + + b3JointType type; + + // This is monotonically advanced when a body is allocated in this slot + // Used to check for invalid b3JointId + uint16_t generation; + + bool collideConnected; +} b3Joint; + +typedef struct b3DistanceJoint +{ + float length; + float hertz; + float dampingRatio; + float lowerSpringForce; + float upperSpringForce; + float minLength; + float maxLength; + + float maxMotorForce; + float motorSpeed; + + float impulse; + float lowerImpulse; + float upperImpulse; + float motorImpulse; + + int indexA; + int indexB; + b3Vec3 anchorA; + b3Vec3 anchorB; + b3Vec3 deltaCenter; + b3Softness distanceSoftness; + float axialMass; + + bool enableSpring; + bool enableLimit; + bool enableMotor; +} b3DistanceJoint; + +typedef struct b3MotorJoint +{ + b3Vec3 linearVelocity; + b3Vec3 angularVelocity; + float maxVelocityForce; + float maxVelocityTorque; + float linearHertz; + float linearDampingRatio; + float maxSpringForce; + float angularHertz; + float angularDampingRatio; + float maxSpringTorque; + + b3Vec3 linearVelocityImpulse; + b3Vec3 angularVelocityImpulse; + b3Vec3 linearSpringImpulse; + b3Vec3 angularSpringImpulse; + + b3Softness linearSpring; + b3Softness angularSpring; + + int indexA; + int indexB; + b3Transform frameA; + b3Transform frameB; + b3Vec3 deltaCenter; + b3Matrix3 angularMass; +} b3MotorJoint; + +typedef struct b3ParallelJoint +{ + float hertz; + float dampingRatio; + float maxTorque; + + b3Vec2 perpImpulse; + b3Vec3 perpAxisX; + b3Vec3 perpAxisY; + + b3Quat quatA; + b3Quat quatB; + int indexA; + int indexB; + b3Softness softness; +} b3ParallelJoint; + +typedef struct b3PrismaticJoint +{ + b3Vec2 perpImpulse; + b3Vec3 angularImpulse; + float springImpulse; + float motorImpulse; + float lowerImpulse; + float upperImpulse; + float hertz; + float dampingRatio; + float maxMotorForce; + float motorSpeed; + float targetTranslation; + float lowerTranslation; + float upperTranslation; + + int indexA; + int indexB; + b3Transform frameA; + b3Transform frameB; + b3Vec3 jointAxis; + b3Vec3 perpAxisY; + b3Vec3 perpAxisZ; + b3Vec3 deltaCenter; + float deltaAngle; + b3Matrix3 rotationMass; + b3Softness springSoftness; + + bool enableSpring; + bool enableLimit; + bool enableMotor; +} b3PrismaticJoint; + +typedef struct b3RevoluteJoint +{ + b3Vec3 linearImpulse; + b3Vec2 perpImpulse; + float springImpulse; + float motorImpulse; + float lowerImpulse; + float upperImpulse; + float hertz; + float dampingRatio; + float maxMotorTorque; + float motorSpeed; + float targetAngle; + float lowerAngle; + float upperAngle; + + int indexA; + int indexB; + b3Transform frameA; + b3Transform frameB; + b3Vec3 rotationAxisZ; + b3Vec3 perpAxisX; + b3Vec3 perpAxisY; + b3Vec3 deltaCenter; + float deltaAngle; + float axialMass; + b3Softness springSoftness; + + bool enableSpring; + bool enableMotor; + bool enableLimit; +} b3RevoluteJoint; + +typedef struct b3SphericalJoint +{ + b3Vec3 linearImpulse; + b3Vec3 springImpulse; + b3Vec3 motorImpulse; + float lowerTwistImpulse; + float upperTwistImpulse; + float swingImpulse; + float hertz; + float dampingRatio; + float maxMotorTorque; + b3Vec3 motorVelocity; + float lowerTwistAngle; + float upperTwistAngle; + float coneAngle; + b3Quat targetRotation; + + int indexA; + int indexB; + b3Transform frameA; + b3Transform frameB; + b3Vec3 deltaCenter; + b3Vec3 swingAxis; + b3Vec3 twistJacobian; + + b3Matrix3 rotationMass; + float swingMass; + float twistMass; + b3Softness springSoftness; + + bool enableSpring; + bool enableMotor; + bool enableConeLimit; + bool enableTwistLimit; +} b3SphericalJoint; + +typedef struct b3WeldJoint +{ + float linearHertz; + float linearDampingRatio; + float angularHertz; + float angularDampingRatio; + + b3Softness linearSpring; + b3Softness angularSpring; + b3Vec3 linearImpulse; + b3Vec3 angularImpulse; + + int indexA; + int indexB; + b3Transform frameA; + b3Transform frameB; + b3Vec3 deltaCenter; + + b3Matrix3 angularMass; +} b3WeldJoint; + +typedef struct b3WheelJoint +{ + b3Vec2 linearImpulse; + b3Vec2 angularImpulse; + float spinImpulse; + float maxSpinTorque; + float spinSpeed; + float suspensionSpringImpulse; + float lowerSuspensionImpulse; + float upperSuspensionImpulse; + float lowerSuspensionLimit; + float upperSuspensionLimit; + float suspensionHertz; + float suspensionDampingRatio; + float steeringSpringImpulse; + float lowerSteeringImpulse; + float upperSteeringImpulse; + float lowerSteeringLimit; + float upperSteeringLimit; + float targetSteeringAngle; + float maxSteeringTorque; + float steeringHertz; + float steeringDampingRatio; + + int indexA; + int indexB; + b3Transform frameA; + b3Transform frameB; + b3Vec3 deltaCenter; + float spinMass; + float suspensionMass; + float steeringMass; + b3Softness suspensionSoftness; + b3Softness steeringSoftness; + + bool enableSpinMotor; + bool enableSuspensionSpring; + bool enableSuspensionLimit; + bool enableSteering; + bool enableSteeringLimit; + bool enableSteeringMotor; +} b3WheelJoint; + +/// The base joint class. Joints are used to constraint two bodies together in +/// various fashions. Some joints also feature limits and motors. +typedef struct b3JointSim +{ + int jointId; + + int bodyIdA; + int bodyIdB; + + b3JointType type; + + // Joint frames local to body origin + b3Transform localFrameA; + b3Transform localFrameB; + + float invMassA, invMassB; + b3Matrix3 invIA, invIB; + + float constraintHertz; + float constraintDampingRatio; + + b3Softness constraintSoftness; + + float forceThreshold; + float torqueThreshold; + + bool fixedRotation; + + union + { + b3DistanceJoint distanceJoint; + b3MotorJoint motorJoint; + b3ParallelJoint parallelJoint; + b3RevoluteJoint revoluteJoint; + b3SphericalJoint sphericalJoint; + b3PrismaticJoint prismaticJoint; + b3WeldJoint weldJoint; + b3WheelJoint wheelJoint; + }; +} b3JointSim; + +void b3DestroyJointInternal( b3World* world, b3Joint* joint, bool wakeBodies ); + +b3Joint* b3GetJointFullId( b3World* world, b3JointId jointId ); +b3JointSim* b3GetJointSim( b3World* world, b3Joint* joint ); +b3JointSim* b3GetJointSimCheckType( b3JointId jointId, b3JointType type ); + +void b3PrepareJoint( b3JointSim* joint, b3StepContext* context ); +void b3WarmStartJoint( b3JointSim* joint, b3StepContext* context ); +void b3SolveJoint( b3JointSim* joint, b3StepContext* context, bool useBias ); + +void b3PrepareJoints_Overflow( b3StepContext* context ); +void b3WarmStartJoints_Overflow( b3StepContext* context ); +void b3SolveJoints_Overflow( b3StepContext* context, bool useBias ); + +void b3GetJointReaction( b3World* world, b3JointSim* sim, float invTimeStep, float* force, float* torque ); + +void b3DrawJoint( b3DebugDraw* draw, b3World* world, b3Joint* joint ); + +b3Vec3 b3GetDistanceJointForce( b3World* world, b3JointSim* base ); +b3Vec3 b3GetMotorJointForce( b3World* world, b3JointSim* base ); +b3Vec3 b3GetPrismaticJointForce( b3World* world, b3JointSim* base ); +b3Vec3 b3GetRevoluteJointForce( b3World* world, b3JointSim* base ); +b3Vec3 b3GetSphericalJointForce( b3World* world, b3JointSim* base ); +b3Vec3 b3GetWeldJointForce( b3World* world, b3JointSim* base ); +b3Vec3 b3GetWheelJointForce( b3World* world, b3JointSim* base ); + +b3Vec3 b3GetMotorJointTorque( b3World* world, b3JointSim* base ); +b3Vec3 b3GetParallelJointTorque( b3World* world, b3JointSim* base ); +b3Vec3 b3GetPrismaticJointTorque( b3World* world, b3JointSim* base ); +b3Vec3 b3GetRevoluteJointTorque( b3World* world, b3JointSim* base ); +b3Vec3 b3GetSphericalJointTorque( b3World* world, b3JointSim* base ); +b3Vec3 b3GetWeldJointTorque( b3World* world, b3JointSim* base ); +b3Vec3 b3GetWheelJointTorque( b3World* world, b3JointSim* base ); + +void b3PrepareDistanceJoint( b3JointSim* base, b3StepContext* context ); +void b3PrepareMotorJoint( b3JointSim* base, b3StepContext* context ); +void b3PrepareParallelJoint( b3JointSim* base, b3StepContext* context ); +void b3PreparePrismaticJoint( b3JointSim* base, b3StepContext* context ); +void b3PrepareRevoluteJoint( b3JointSim* base, b3StepContext* context ); +void b3PrepareSphericalJoint( b3JointSim* base, b3StepContext* context ); +void b3PrepareWeldJoint( b3JointSim* base, b3StepContext* context ); +void b3PrepareWheelJoint( b3JointSim* base, b3StepContext* context ); + +void b3WarmStartDistanceJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartMotorJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartParallelJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartPrismaticJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartRevoluteJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartSphericalJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartWeldJoint( b3JointSim* base, b3StepContext* context ); +void b3WarmStartWheelJoint( b3JointSim* base, b3StepContext* context ); + +void b3SolveDistanceJoint( b3JointSim* base, b3StepContext* context, bool useBias ); +void b3SolveMotorJoint( b3JointSim* base, b3StepContext* context ); +void b3SolveParallelJoint( b3JointSim* base, b3StepContext* context ); +void b3SolvePrismaticJoint( b3JointSim* base, b3StepContext* context, bool useBias ); +void b3SolveRevoluteJoint( b3JointSim* base, b3StepContext* context, bool useBias ); +void b3SolveSphericalJoint( b3JointSim* base, b3StepContext* context, bool useBias ); +void b3SolveWeldJoint( b3JointSim* base, b3StepContext* context, bool useBias ); +void b3SolveWheelJoint( b3JointSim* base, b3StepContext* context, bool useBias ); + +void b3DrawDistanceJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB ); +void b3DrawParallelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ); +void b3DrawPrismaticJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ); +void b3DrawRevoluteJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ); +void b3DrawSphericalJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ); +void b3DrawWeldJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ); +void b3DrawWheelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ); diff --git a/vendor/box3d/src/src/manifold.c b/vendor/box3d/src/src/manifold.c new file mode 100644 index 000000000..40e60ad57 --- /dev/null +++ b/vendor/box3d/src/src/manifold.c @@ -0,0 +1,246 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// Dirk Gregorius contributed portions of this code + +#include "manifold.h" + +#include "algorithm.h" +#include "shape.h" + +#include "box3d/math_functions.h" + +// p1 : origin on edge 1 +// e1 : edge 1 +// c1 : shape 1 centroid +// p2 : origin on edge 2 +// e2 : edge 2 +// c2 : shape 2 centroid +float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e2, b3Vec3 c2 ) +{ + // Build search direction + b3Vec3 u = b3Cross( e1, e2 ); + float length = b3Length( u ); + + // Skip near parallel edges: |e1 x e1| = sin(alpha) * |e1| * |e2| + const float kTolerance = 0.005f; + if ( length < kTolerance * sqrtf( b3LengthSquared( e1 ) * b3LengthSquared( e2 ) ) ) + { + return -FLT_MAX; + } + + if ( length * length < 1000.0f * FLT_MIN ) + { + return -FLT_MAX; + } + + b3Vec3 n = b3MulSV( 1.0f / length, u ); + + // Make sure normal points away from the first shape + // For a triangle, it is possible that N is aligned with the triangle normal and the sign + // value can be close to zero and flicker between small negative and positive values, leading to + // an incorrect separation value. So we assume the other hull has some volume and pick the most + // significant sign value to orient N. + float sign1 = b3Dot( n, b3Sub( p1, c1 ) ); + float sign2 = b3Dot( n, b3Sub( p2, c2 ) ); + if ( b3AbsFloat( sign1 ) > b3AbsFloat( sign2 ) ) + { + if ( sign1 < 0.0f ) + { + n = b3Neg( n ); + } + } + else + { + if ( sign2 > 0.0f ) + { + n = b3Neg( n ); + } + } + + // s = Dot(n, p2) - d = Dot(n, p2) - Dot(n, p1) = Dot(n, p2 - p1) + return b3Dot( n, b3Sub( p2, p1 ) ); +} + +// This was extended to make the wedge shape get the correct incident face. +// Instead of looking directly for the most anti-parallel face, we first find the closest vertex (passed in). +// Then we look for all edges coming out of that vertex and look for the edge that is +// most perpendicular to the reference normal. +// Then from that edge, we select the adjacent face that is most anti-parallel to the reference normal. +int b3FindIncidentFace( const b3HullData* hull, b3Vec3 refNormal, int vertexIndex ) +{ + const b3HullVertex* vertices = b3GetHullVertices( hull ); + const b3HullHalfEdge* edges = b3GetHullEdges( hull ); + const b3Plane* planes = b3GetHullPlanes( hull ); + const b3Vec3* points = b3GetHullPoints( hull ); + + int minEdgeIndex = -1; + float minEdgeProjection = FLT_MAX; + + const b3HullVertex* vertex = vertices + vertexIndex; + B3_ASSERT( vertex ); + + int edgeIndex = vertex->edge; + const b3HullHalfEdge* edge = edges + edgeIndex; + b3Vec3 edgeOrigin = points[edge->origin]; + B3_ASSERT( edge->origin == vertexIndex ); + + do + { + const b3HullHalfEdge* twin = edges + edge->twin; + b3Vec3 twinOrigin = points[twin->origin]; + + b3Vec3 axis = b3Normalize( b3Sub( twinOrigin, edgeOrigin ) ); + float edgeProjection = b3AbsFloat( b3Dot( axis, refNormal ) ); + if ( edgeProjection < minEdgeProjection ) + { + minEdgeIndex = edgeIndex; + minEdgeProjection = edgeProjection; + } + + edgeIndex = twin->next; + edge = edges + edgeIndex; + B3_ASSERT( edge->origin == vertexIndex ); + } + while ( edge != edges + vertex->edge ); + B3_ASSERT( minEdgeIndex >= 0 ); + + const b3HullHalfEdge* minEdge = edges + minEdgeIndex; + int minFaceIndex1 = minEdge->face; + b3Plane minPlane1 = planes[minFaceIndex1]; + + const b3HullHalfEdge* minTwin = edges + minEdge->twin; + int minFaceIndex2 = minTwin->face; + b3Plane minPlane2 = planes[minFaceIndex2]; + + return b3Dot( minPlane1.normal, refNormal ) < b3Dot( minPlane2.normal, refNormal ) ? minFaceIndex1 : minFaceIndex2; +} + +b3FeaturePair b3MakeFeaturePair( b3FeatureOwner owner1, int index1, b3FeatureOwner owner2, int index2 ) +{ + B3_ASSERT( 0 <= index1 && index1 <= UINT8_MAX ); + B3_ASSERT( 0 <= index2 && index2 <= UINT8_MAX ); + + b3FeaturePair pair; + pair.index1 = (uint8_t)index1; + pair.owner1 = (uint8_t)owner1; + pair.index2 = (uint8_t)index2; + pair.owner2 = (uint8_t)owner2; + return pair; +} + +// This logic seems wrong but it is designed so that choosing +// face A or B as the reference face does not change the resulting +// feature pair. This way the contact impulses are persisted even +// if there is reference face flip-flop. This is verified in the +// HullAndHull sample using the b3SATCache with a manual feature +// specified such as b3_manualFaceAxisA. +b3FeaturePair b3FlipPair( b3FeaturePair pair ) +{ + B3_ASSERT( pair.owner1 == 0 || pair.owner1 == 1 ); + B3_ASSERT( pair.owner2 == 0 || pair.owner2 == 1 ); + B3_SWAP( pair.owner1, pair.owner2 ); + pair.owner1 = 1 - pair.owner1; + pair.owner2 = 1 - pair.owner2; + B3_SWAP( pair.index1, pair.index2 ); + return pair; +} + +#if B3_ENABLE_VALIDATION +bool b3ValidatePolygon( b3ClipVertex* polygon, int count ) +{ + // Empty polygons are valid (we can clip away all points when re-constructing manifolds from cache) + if ( count == 0 ) + { + return true; + } + + // Validate that incoming and outgoing edges match + b3ClipVertex vertex1 = polygon[count - 1]; + for ( int i = 0; i < count; ++i ) + { + b3ClipVertex vertex2 = polygon[i]; + + if ( vertex1.pair.owner2 != vertex2.pair.owner1 ) + { + return false; + } + + if ( vertex1.pair.index2 != vertex2.pair.index1 ) + { + return false; + } + + vertex1 = vertex2; + } + + return true; +} +#endif + +int b3ClipPolygon( b3ClipVertex* out, b3ClipVertex* polygon, int count, b3Plane clipPlane, int edge, b3Plane refPlane ) +{ + B3_ASSERT( count >= 3 ); + + b3ClipVertex vertex1 = polygon[count - 1]; + float distance1 = b3PlaneSeparation( clipPlane, vertex1.position ); + int outCount = 0; + + for ( int index = 0; index < count; ++index ) + { + b3ClipVertex vertex2 = polygon[index]; + float distance2 = b3PlaneSeparation( clipPlane, vertex2.position ); + + // Clip edge against plane (Sutherland-Hodgman clipping) + if ( distance1 <= 0.0f && distance2 <= 0.0f ) + { + // Both vertices are behind the plane - keep vertex2 + out[outCount] = vertex2; + outCount += 1; + } + else if ( distance1 <= 0.0f && distance2 > 0.0f ) + { + // Vertex1 is behind of the plane, vertex2 is in front -> intersection point + float fraction = distance1 / ( distance1 - distance2 ); + b3Vec3 position = b3MulAdd( vertex1.position, fraction, b3Sub( vertex2.position, vertex1.position ) ); + + // Keep intersection point and adjust outgoing edge + b3ClipVertex vertex; + vertex.position = position; + vertex.separation = b3PlaneSeparation( refPlane, position ); + vertex.pair = vertex2.pair; + vertex.pair.owner2 = b3_featureShapeA; + vertex.pair.index2 = (uint8_t)edge; + out[outCount] = vertex; + outCount += 1; + } + else if ( distance2 <= 0.0f && distance1 > 0.0f ) + { + // Vertex1 is in front, vertex2 is behind of the plane, -> intersection point + float fraction = distance1 / ( distance1 - distance2 ); + b3Vec3 position = b3MulAdd( vertex1.position, fraction, b3Sub( vertex2.position, vertex1.position ) ); + + // Keep intersection point and adjust incoming edge + b3ClipVertex vertex; + vertex.position = position; + vertex.separation = b3PlaneSeparation( refPlane, position ); + vertex.pair = vertex1.pair; + vertex.pair.owner1 = b3_featureShapeA; + vertex.pair.index1 = (uint8_t)edge; + out[outCount] = vertex; + outCount += 1; + + // And also keep vertex2 + out[outCount] = vertex2; + outCount += 1; + } + + // Keep vertex2 as starting vertex for next edge + vertex1 = vertex2; + distance1 = distance2; + } + + B3_VALIDATE( b3ValidatePolygon( out, outCount ) ); + + return outCount; +} diff --git a/vendor/box3d/src/src/manifold.h b/vendor/box3d/src/src/manifold.h new file mode 100644 index 000000000..9e529ab97 --- /dev/null +++ b/vendor/box3d/src/src/manifold.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/collision.h" +#include "box3d/math_functions.h" + +#define B3_MAX_CLIP_POINTS 64 + +typedef struct b3FaceQuery +{ + float separation; + int faceIndex; + int vertexIndex; +} b3FaceQuery; + +typedef struct b3EdgeQuery +{ + float separation; + int indexA; + int indexB; +} b3EdgeQuery; + +typedef struct b3ClipVertex +{ + b3Vec3 position; + float separation; + b3FeaturePair pair; +} b3ClipVertex; + +typedef enum b3FeatureOwner +{ + b3_featureShapeA = 0, + b3_featureShapeB = 1 +} b3FeatureOwner; + +float b3EdgeEdgeSeparation( b3Vec3 p1, b3Vec3 e1, b3Vec3 c1, b3Vec3 p2, b3Vec3 e2, b3Vec3 c2 ); +int b3FindIncidentFace( const b3HullData* hull, b3Vec3 refNormal, int vertexIndex ); +b3FeaturePair b3MakeFeaturePair( b3FeatureOwner owner1, int index1, b3FeatureOwner owner2, int index2 ); + +b3FeaturePair b3FlipPair( b3FeaturePair pair ); + +int b3ClipPolygon( b3ClipVertex* out, b3ClipVertex* polygon, int count, b3Plane clipPlane, int edge, b3Plane refPlane ); + +#if B3_ENABLE_VALIDATION +bool b3ValidatePolygon( b3ClipVertex* polygon, int count ); +#endif + +// For single point contact, such as sphere-sphere, sphere-capsule, sphere-triangle +static const b3FeaturePair b3FeaturePair_single = { 0 }; + +static inline uint32_t b3MakeFeatureId( b3FeaturePair pair ) +{ + return ( (uint32_t)pair.owner1 << 24 ) | ( (uint32_t)pair.index1 << 16 ) | ( (uint32_t)pair.owner2 << 8 ) | + (uint32_t)pair.index2; +} diff --git a/vendor/box3d/src/src/math_functions.c b/vendor/box3d/src/src/math_functions.c new file mode 100644 index 000000000..a339d95ff --- /dev/null +++ b/vendor/box3d/src/src/math_functions.c @@ -0,0 +1,616 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "box3d/math_functions.h" + +#include "math_internal.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" + +#include +#include + +bool b3IsValidFloat( float a ) +{ + if ( isnan( a ) ) + { + return false; + } + + if ( isinf( a ) ) + { + return false; + } + + return true; +} + +bool b3IsValidVec3( b3Vec3 a ) +{ + if ( isnan( a.x ) || isnan( a.y ) || isnan( a.z ) ) + { + return false; + } + + if ( isinf( a.x ) || isinf( a.y ) || isinf( a.z ) ) + { + return false; + } + + return true; +} + +bool b3IsValidQuat( b3Quat a ) +{ + if ( isnan( a.v.x ) || isnan( a.v.y ) || isnan( a.v.z ) || isnan( a.s ) ) + { + return false; + } + + if ( isinf( a.v.x ) || isinf( a.v.y ) || isinf( a.v.z ) || isinf( a.s ) ) + { + return false; + } + + return b3IsNormalizedQuat( a ); +} + +bool b3IsValidTransform( b3Transform a ) +{ + return b3IsValidVec3( a.p ) && b3IsValidQuat( a.q ); +} + +bool b3IsValidMatrix3( b3Matrix3 a ) +{ + return b3IsValidVec3( a.cx ) && b3IsValidVec3( a.cy ) && b3IsValidVec3( a.cz ); +} + +bool b3IsValidAABB( b3AABB a ) +{ + if ( b3IsValidVec3( a.lowerBound ) == false ) + { + return false; + } + + if ( b3IsValidVec3( a.upperBound ) == false ) + { + return false; + } + + if ( a.lowerBound.x > a.upperBound.x ) + { + return false; + } + + if ( a.lowerBound.y > a.upperBound.y ) + { + return false; + } + + if ( a.lowerBound.z > a.upperBound.z ) + { + return false; + } + + return true; +} + +bool b3IsBoundedAABB( b3AABB a ) +{ + if ( a.lowerBound.x < -B3_HUGE || a.lowerBound.y < -B3_HUGE || a.lowerBound.z < -B3_HUGE ) + { + return false; + } + + if ( a.upperBound.x > B3_HUGE || a.upperBound.y > B3_HUGE || a.upperBound.z > B3_HUGE ) + { + return false; + } + + return true; +} + +bool b3IsSaneAABB( b3AABB a ) +{ + if ( b3IsValidAABB( a ) == false ) + { + return false; + } + + if ( a.lowerBound.x < -B3_HUGE || a.lowerBound.y < -B3_HUGE || a.lowerBound.z < -B3_HUGE ) + { + return false; + } + + if ( a.upperBound.x > B3_HUGE || a.upperBound.y > B3_HUGE || a.upperBound.z > B3_HUGE ) + { + return false; + } + + return true; +} + +bool b3IsValidPlane( b3Plane a ) +{ + if ( b3IsValidVec3( a.normal ) == false ) + { + return false; + } + + if ( b3IsNormalized( a.normal ) == false ) + { + return false; + } + + return b3IsValidFloat( a.offset ); +} + +bool b3IsValidPosition( b3Pos p ) +{ + if ( isnan( p.x ) || isnan( p.y ) || isnan( p.z ) ) + { + return false; + } + + if ( isinf( p.x ) || isinf( p.y ) || isinf( p.z ) ) + { + return false; + } + + return true; +} + +bool b3IsValidWorldTransform( b3WorldTransform t ) +{ + return b3IsValidPosition( t.p ) && b3IsValidQuat( t.q ); +} + +// https://stackoverflow.com/questions/46210708/atan2-approximation-with-11bits-in-mantissa-on-x86with-sse2-and-armwith-vfpv4 +float b3Atan2( float y, float x ) +{ + // Added check for (0,0) to match atan2f and avoid NaN + if ( x == 0.0f && y == 0.0f ) + { + return 0.0f; + } + + float ax = b3AbsFloat( x ); + float ay = b3AbsFloat( y ); + float mx = b3MaxFloat( ay, ax ); + float mn = b3MinFloat( ay, ax ); + float a = mn / mx; + + // Minimax polynomial approximation to atan(a) on [0,1] + float s = a * a; + float c = s * a; + float q = s * s; + float r = 0.024840285f * q + 0.18681418f; + float t = -0.094097948f * q - 0.33213072f; + r = r * s + t; + r = r * c + a; + + // Map to full circle + if ( ay > ax ) + { + r = 1.57079637f - r; + } + + if ( x < 0 ) + { + r = 3.14159274f - r; + } + + if ( y < 0 ) + { + r = -r; + } + + return r; +} + +// Approximate cosine and sine for determinism. In my testing cosf and sinf produced +// the same results on x64 and ARM using MSVC, GCC, and Clang. However, I don't trust +// this result. +// https://en.wikipedia.org/wiki/Bh%C4%81skara_I%27s_sine_approximation_formula +b3CosSin b3ComputeCosSin( float radians ) +{ +#if 0 + return { + cosf( radians ), + sinf( radians ), + }; +#else + float x = b3UnwindAngle( radians ); + float pi2 = B3_PI * B3_PI; + + // cosine needs angle in [-pi/2, pi/2] + float c; + if ( x < -0.5f * B3_PI ) + { + float y = x + B3_PI; + float y2 = y * y; + c = -( pi2 - 4.0f * y2 ) / ( pi2 + y2 ); + } + else if ( x > 0.5f * B3_PI ) + { + float y = x - B3_PI; + float y2 = y * y; + c = -( pi2 - 4.0f * y2 ) / ( pi2 + y2 ); + } + else + { + float y2 = x * x; + c = ( pi2 - 4.0f * y2 ) / ( pi2 + y2 ); + } + + // sine needs angle in [0, pi] + float s; + if ( x < 0.0f ) + { + float y = x + B3_PI; + s = -16.0f * y * ( B3_PI - y ) / ( 5.0f * pi2 - 4.0f * y * ( B3_PI - y ) ); + } + else + { + s = 16.0f * x * ( B3_PI - x ) / ( 5.0f * pi2 - 4.0f * x * ( B3_PI - x ) ); + } + + float mag = sqrtf( s * s + c * c ); + float invMag = mag > 0.0f ? 1.0f / mag : 0.0f; + b3CosSin cs = { c * invMag, s * invMag }; + return cs; +#endif +} + +b3Quat b3MakeQuatFromMatrix( const b3Matrix3* m ) +{ + b3Vec3 c1 = m->cx; + b3Vec3 c2 = m->cy; + b3Vec3 c3 = m->cz; + + b3Quat q; + + float trace = m->cx.x + m->cy.y + m->cz.z; + if ( trace >= 0.0f ) + { + q.v.x = c2.z - c3.y; + q.v.y = c3.x - c1.z; + q.v.z = c1.y - c2.x; + q.s = trace + 1.0f; + } + else + { + if ( c1.x > c2.y && c1.x > c3.z ) + { + q.v.x = c1.x - c2.y - c3.z + 1.0f; + q.v.y = c2.x + c1.y; + q.v.z = c3.x + c1.z; + q.s = c2.z - c3.y; + } + else if ( c2.y > c3.z ) + { + q.v.x = c1.y + c2.x; + q.v.y = c2.y - c3.z - c1.x + 1.0f; + q.v.z = c3.y + c2.z; + q.s = c3.x - c1.z; + } + else + { + q.v.x = c1.z + c3.x; + q.v.y = c2.z + c3.y; + q.v.z = c3.z - c1.x - c2.y + 1.0f; + q.s = c1.y - c2.x; + } + } + + // The algorithm is simplified and made more accurate by normalizing at the end + return b3NormalizeQuat( q ); +} + +b3Quat b3ComputeQuatBetweenUnitVectors( b3Vec3 v1, b3Vec3 v2 ) +{ + B3_ASSERT( b3IsNormalized( v1 ) ); + B3_ASSERT( b3IsNormalized( v2 ) ); + + b3Quat out; + + b3Vec3 m = b3Lerp( v1, v2, 0.5f ); + float tolerance = 100.0f * FLT_EPSILON; + if ( b3LengthSquared( m ) > tolerance * tolerance ) + { + out.v = b3Cross( v1, m ); + out.s = b3Dot( v1, m ); + } + else + { + // Anti-parallel: Use a perpendicular vector + if ( b3AbsFloat( v1.x ) > 0.5f ) + { + out.v.x = v1.y; + out.v.y = -v1.x; + out.v.z = 0.0f; + } + else + { + out.v.x = 0.0f; + out.v.y = v1.z; + out.v.z = -v1.y; + } + + out.s = 0.0f; + } + + // The algorithm is simplified and made more accurate by normalizing at the end + return b3NormalizeQuat( out ); +} + +b3SegmentDistanceResult b3LineDistance( b3Vec3 p1, b3Vec3 d1, b3Vec3 p2, b3Vec3 d2 ) +{ + b3SegmentDistanceResult result; + + // Solve A*x = b + float a11 = b3Dot( d1, d1 ); + float a12 = -b3Dot( d1, d2 ); + float a21 = b3Dot( d2, d1 ); + float a22 = -b3Dot( d2, d2 ); + + b3Vec3 w = b3Sub( p1, p2 ); + float b1 = -b3Dot( d1, w ); + float b2 = -b3Dot( d2, w ); + + float det = a11 * a22 - a12 * a21; + if ( det * det < 1000.0f * FLT_MIN ) + { + // Lines are parallel - project p2 onto line L1: x1 = p1 + s1 * d1 + float s1 = b3Dot( b3Sub( p2, p1 ), d1 ) / b3Dot( d1, d1 ); + float s2 = 0.0f; + + result.point1 = b3MulAdd( p1, s1, d1 ); + result.fraction1 = s1; + result.point2 = b3MulAdd( p2, s2, d2 ); + result.fraction2 = s2; + + return result; + } + + float s1 = ( a22 * b1 - a12 * b2 ) / det; + float s2 = ( a11 * b2 - a21 * b1 ) / det; + + result.point1 = b3MulAdd( p1, s1, d1 ); + result.fraction1 = s1; + result.point2 = b3MulAdd( p2, s2, d2 ); + result.fraction2 = s2; + return result; +} + +b3SegmentDistanceResult b3SegmentDistance( b3Vec3 p1, b3Vec3 q1, b3Vec3 p2, b3Vec3 q2 ) +{ + b3SegmentDistanceResult result; + + b3Vec3 d1 = b3Sub( q1, p1 ); + b3Vec3 d2 = b3Sub( q2, p2 ); + b3Vec3 r = b3Sub( p1, p2 ); + + float a = b3Dot( d1, d1 ); + float b = b3Dot( d1, d2 ); + float c = b3Dot( d1, r ); + float e = b3Dot( d2, d2 ); + float f = b3Dot( d2, r ); + + // Check if one of the segments degenerates into a point + if ( a < 100.0f * FLT_EPSILON && e < 100.0f * FLT_EPSILON ) + { + // Both segments degenerate into points + result.point1 = p1; + result.fraction1 = 0.0f; + result.point2 = p2; + result.fraction2 = 0.0f; + + return result; + } + + if ( a < 100.0f * FLT_EPSILON ) + { + // First segment degenerates into a point + float s2 = b3ClampFloat( f / e, 0.0f, 1.0f ); + + result.point1 = p1; + result.fraction1 = 0.0f; + result.point2 = b3MulAdd( p2, s2, d2 ); + result.fraction2 = s2; + + return result; + } + + if ( e < 100.0f * FLT_EPSILON ) + { + // Second segment degenerates into a point + float s1 = b3ClampFloat( -c / a, 0.0f, 1.0f ); + + result.point1 = b3MulAdd( p1, s1, d1 ); + result.fraction1 = s1; + result.point2 = p2; + result.fraction2 = 0.0f; + + return result; + } + + // Non-degenerate case + float denom = a * e - b * b; + float s1 = denom > 1000.0f * FLT_MIN ? b3ClampFloat( ( b * f - c * e ) / denom, 0.0f, 1.0f ) : 0.0f; + float s2 = ( b * s1 + f ) / e; + + // Clamp lambda2 and recompute lambda1 if necessary + if ( s2 < 0.0f ) + { + s1 = b3ClampFloat( -c / a, 0.0f, 1.0f ); + s2 = 0.0f; + } + else if ( s2 > 1.0f ) + { + s1 = b3ClampFloat( ( b - c ) / a, 0.0f, 1.0f ); + s2 = 1.0f; + } + + result.point1 = b3MulAdd( p1, s1, d1 ); + result.fraction1 = s1; + result.point2 = b3MulAdd( p2, s2, d2 ); + result.fraction2 = s2; + + return result; +} + +b3Vec3 b3PointToSegmentDistance( b3Vec3 a, b3Vec3 b, b3Vec3 q ) +{ + b3Vec3 ab = b3Sub( b, a ); + b3Vec3 aq = b3Sub( q, a ); + + float alpha = b3Dot( ab, aq ); + + if ( alpha <= 0.0f ) + { + // q projects outside interval [a, b] on the side of a + return a; + } + else + { + float denominator = b3Dot( ab, ab ); + if ( alpha > denominator ) + { + // q projects outside interval [a, b] on the side of b + return b; + } + else + { + // q projects inside interval [a, b] + alpha /= denominator; + return b3MulAdd( a, alpha, ab ); + } + } +} + +b3TrianglePoint b3ClosestPointOnTriangle( b3Vec3 a, b3Vec3 b, b3Vec3 c, b3Vec3 q ) +{ + // Check if P lies in vertex region of A + b3Vec3 ab = b3Sub( b, a ); + b3Vec3 ac = b3Sub( c, a ); + b3Vec3 aq = b3Sub( q, a ); + + float d1 = b3Dot( ab, aq ); + float d2 = b3Dot( ac, aq ); + if ( d1 <= 0.0f && d2 <= 0.0f ) + { + return (b3TrianglePoint){ a, b3_featureVertex1 }; + } + + // Check if P lies in vertex region of B + b3Vec3 bq = b3Sub( q, b ); + + float d3 = b3Dot( ab, bq ); + float d4 = b3Dot( ac, bq ); + if ( d3 > 0.0f && d4 <= d3 ) + { + return (b3TrianglePoint){ b, b3_featureVertex2 }; + } + + // Check if P lies in edge region AB + float vc = d1 * d4 - d3 * d2; + if ( vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f ) + { + float t = d1 / ( d1 - d3 ); + return (b3TrianglePoint){ b3MulAdd( a, t, ab ), b3_featureEdge1 }; + } + + // Check if P lies in vertex region of C + b3Vec3 cq = b3Sub( q, c ); + + float d5 = b3Dot( ab, cq ); + float d6 = b3Dot( ac, cq ); + if ( d6 >= 0.0f && d5 <= d6 ) + { + return (b3TrianglePoint){ c, b3_featureVertex3 }; + } + + // Check if P lies in edge region AC + float vb = d5 * d2 - d1 * d6; + if ( vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f ) + { + float t = d2 / ( d2 - d6 ); + return (b3TrianglePoint){ b3MulAdd( a, t, ac ), b3_featureEdge3 }; + } + + // Check if P lies in edge region of BC + float va = d3 * d6 - d5 * d4; + if ( va <= 0.0f && d4 >= d3 && d5 >= d6 ) + { + b3Vec3 bc = b3Sub( c, b ); + + float t = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); + return (b3TrianglePoint){ b3MulAdd( b, t, bc ), b3_featureEdge2 }; + } + + // P inside face region ABC + float t1 = vb / ( va + vb + vc ); + float t2 = vc / ( va + vb + vc ); + + b3Vec3 p = b3MulAdd( a, t1, ab ); + p = b3MulAdd( p, t2, ac ); + return (b3TrianglePoint){ p, b3_featureTriangleFace }; +} + +b3Matrix3 b3SphereInertia( float mass, float radius ) +{ + float i = 0.4f * mass * radius * radius; + return b3MakeDiagonalMatrix( i, i, i ); +} + +b3Matrix3 b3CylinderInertia( float mass, float radius, float height ) +{ + float ixx = mass * ( 3 * radius * radius + height * height ) / 12.0f; + float iyy = 0.5f * mass * radius * radius; + return b3MakeDiagonalMatrix( ixx, iyy, ixx ); +} + +b3Matrix3 b3BoxInertia( float mass, b3Vec3 min, b3Vec3 max ) +{ + b3Vec3 delta = b3Sub( max, min ); + float ixx = mass * ( delta.y * delta.y + delta.z * delta.z ) / 12.0f; + float iyy = mass * ( delta.x * delta.x + delta.z * delta.z ) / 12.0f; + float izz = mass * ( delta.x * delta.x + delta.y * delta.y ) / 12.0f; + + return b3MakeDiagonalMatrix( ixx, iyy, izz ); +} + +// https://en.wikipedia.org/wiki/Parallel_axis_theorem +b3Matrix3 b3Steiner( float mass, b3Vec3 origin ) +{ + // Usage: Io = Ic + Is and Ic = Io - Is + float ixx = mass * ( origin.y * origin.y + origin.z * origin.z ); + float iyy = mass * ( origin.x * origin.x + origin.z * origin.z ); + float izz = mass * ( origin.x * origin.x + origin.y * origin.y ); + float ixy = -mass * origin.x * origin.y; + float ixz = -mass * origin.x * origin.z; + float iyz = -mass * origin.y * origin.z; + + // Write + b3Matrix3 out; + out.cx.x = ixx; + out.cy.x = ixy; + out.cz.x = ixz; + out.cx.y = ixy; + out.cy.y = iyy; + out.cz.y = iyz; + out.cx.z = ixz; + out.cy.z = iyz; + out.cz.z = izz; + + return out; +} + +bool b3IsValidRay( const b3RayCastInput* input ) +{ + bool isValid = b3IsValidVec3( input->origin ) && b3IsValidVec3( input->translation ) && + b3IsValidFloat( input->maxFraction ) && 0.0f <= input->maxFraction && input->maxFraction < B3_HUGE; + return isValid; +} diff --git a/vendor/box3d/src/src/math_internal.h b/vendor/box3d/src/src/math_internal.h new file mode 100644 index 000000000..4d152c5fc --- /dev/null +++ b/vendor/box3d/src/src/math_internal.h @@ -0,0 +1,480 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core.h" + +#include "box3d/collision.h" +#include "box3d/math_functions.h" + +struct b3Sweep; +struct b3Plane; + +#define B3_TWO_PI 6.283185307f +#define B3_PI_OVER_TWO 1.570796327f +#define B3_PI_OVER_FOUR 0.785398163f +#define B3_SQRT3 1.732050808f + +// todo eliminate this +static const b3AABB B3_BOUNDS3_EMPTY = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } }; + +typedef struct b3Matrix2 +{ + b3Vec2 cx, cy; +} b3Matrix2; + +typedef struct b3Triangle +{ + b3Vec3 vertices[3]; + int i1, i2, i3; + int flags; +} b3Triangle; + +typedef struct b3TrianglePoint +{ + b3Vec3 point; + b3TriangleFeature feature; +} b3TrianglePoint; + +typedef struct b3ShapeExtent +{ + float minExtent; + b3Vec3 maxExtent; +} b3ShapeExtent; + +b3TrianglePoint b3ClosestPointOnTriangle( b3Vec3 a, b3Vec3 b, b3Vec3 c, b3Vec3 q ); + +float b3IntersectSegmentTriangle( b3Vec3 p, b3Vec3 q, b3Vec3 a, b3Vec3 b, b3Vec3 c ); +float b3IntersectSegmentSphere( b3Vec3 p, b3Vec3 q, b3Vec3 c, float r ); + +b3MassData b3ComputeMassProperties( int triangleCount, const int* triangles, int vertexCount, const b3Vec3* vertices, + float density ); + +bool b3IsValidMassData( const b3MassData* massData ); + +b3Matrix3 b3SphereInertia( float mass, float radius ); +b3Matrix3 b3CylinderInertia( float mass, float radius, float height ); +b3Matrix3 b3BoxInertia( float mass, b3Vec3 min, b3Vec3 max ); + +// Inertia helper (Io = Ic + Is and Ic = Io - Is) +int b3GetProxySupport( const b3ShapeProxy* proxy, b3Vec3 axis ); +int b3GetPointSupport( const b3Vec3* points, int count, b3Vec3 axis ); + +static inline size_t b3AlignUp8( size_t x ) +{ + return ( x + 7u ) & ~(size_t)7u; +} + +// https://en.wikipedia.org/wiki/Floor_and_ceiling_functions +static inline int b3CeilingInt( int numerator, int denominator ) +{ + B3_VALIDATE( denominator > 0 ); + return ( numerator + denominator - 1 ) / denominator; +} + +// Assumes denominator == 2^exponent +static inline int b3CeilingPow2( int numerator, int denominator, int exponent ) +{ + B3_VALIDATE( exponent > 0 && ( denominator == 1 << exponent ) ); + return ( numerator + denominator - 1 ) >> exponent; +} + +bool b3IsSweepNormalized( b3Sweep* sweep ); + +static inline float b3Dot2( b3Vec2 v1, b3Vec2 v2 ) +{ + return v1.x * v2.x + v1.y * v2.y; +} + +static inline float b3Length2( b3Vec2 v ) +{ + return sqrtf( b3Dot2( v, v ) ); +} + +static inline float b3LengthSquared2( b3Vec2 v ) +{ + return b3Dot2( v, v ); +} + +static inline b3Vec2 b3MinVec2( b3Vec2 v1, b3Vec2 v2 ) +{ + b3Vec2 v; + v.x = b3MinFloat( v1.x, v2.x ); + v.y = b3MinFloat( v1.y, v2.y ); + return v; +} + +static inline b3Vec2 b3MaxVec2( b3Vec2 v1, b3Vec2 v2 ) +{ + b3Vec2 v; + v.x = b3MaxFloat( v1.x, v2.x ); + v.y = b3MaxFloat( v1.y, v2.y ); + return v; +} + +static inline void b3Store( float* dst, b3Vec3 src ) +{ + dst[0] = src.x; + dst[1] = src.y; + dst[2] = src.z; +} + +static inline b3Vec3 b3ClampLength( b3Vec3 v, float maxLength ) +{ + float lengthSq = b3LengthSquared( v ); + if ( lengthSq <= maxLength * maxLength ) + { + return v; + } + + float length = sqrtf( lengthSq ); + return b3MulSV( maxLength / length, v ); +} + +// Assume v is a unit vector +static inline b3Vec3 b3ArbitraryPerp( b3Vec3 v ) +{ + // Suppose vector a has all equal components and is a unit vector: a = (s, s, s) + // Then 3*s*s = 1, s = sqrt(1/3) = 0.57735. This means that at least one component + // of a unit vector must be greater or equal to 0.57735. + b3Vec3 p; + if ( v.x < -0.5f || 0.5f < v.x ) + { + // x is non-zero and it should not go into the x component + // dot([ay + bz, cx, dx], [x, y, z]) = ayx + bzx + cxy + dzx + // for the dot product to be zero need: c = -a, d = -b + float a = 0.67f; + float b = -0.42f; + p = B3_LITERAL( b3Vec3 ){ a * v.y + b * v.z, -a * v.x, -b * v.x }; + } + else if ( v.y < -0.5f || 0.5f < v.y ) + { + // y is non-zero and it should not go into the y component + // p = [ay, bx + cz, dy] + // axy + bxy + cyz + dyz = 0 + // b = -a, d = -c + float a = 0.67f; + float c = -0.42f; + p = B3_LITERAL( b3Vec3 ){ a * v.y, -a * v.x + c * v.z, -c * v.y }; + } + else + { + // This would trip if the input is not a unit vector + B3_VALIDATE( v.z < -0.5f || 0.5f < v.z ); + + // z is non-zero and it should not go into the z component + // p = [az, bz, cx + dy] + // axz + byz + cxz + dyz = 0 + // c = -a, d = -b + float a = 0.67f; + float b = -0.42f; + p = B3_LITERAL( b3Vec3 ){ a * v.z, b * v.z, -a * v.x - b * v.y }; + } + + B3_VALIDATE( b3LengthSquared( p ) > 0.1f ); + B3_VALIDATE( b3AbsFloat( b3Dot( p, v ) ) < 100.0f * FLT_EPSILON ); + + return b3Normalize( p ); +} + +static inline b3Quat b3QuatFromExponentialMap( b3Vec3 v ) +{ + // Exponential map (Grassia) + float threshold = 0.018581361f; + + float angle = b3Length( v ); + if ( angle < threshold ) + { + // Taylor expansion + b3Quat out; + out.v = b3MulSV( 0.5f + angle * angle / 48.0f, v ); + out.s = b3Cos( 0.5f * angle ); + + return out; + } + + return b3MakeQuatFromAxisAngle( b3MulSV( 1.0f / angle, v ), angle ); +} + +/// Integrate rotation from angular velocity +/// @param q1 initial rotation +/// @param deltaRotation the angular displacement vector in radians (angular velocity multiplied by the time step) +/// q2 = q1 + 0.5 * omega * q1 +static inline b3Quat b3IntegrateRotation( b3Quat q1, b3Vec3 deltaRotation ) +{ +#if 1 + // https://fgiesen.wordpress.com/2012/08/24/quaternion-differentiation/ + b3Quat qd = { b3MulSV( 0.5f, deltaRotation ), 0.0f }; + qd = b3MulQuat( qd, q1 ); + b3Quat q2 = { b3Add( q1.v, qd.v ), qd.s + q1.s }; + q2 = b3NormalizeQuat( q2 ); + return q2; +#else + return b3NormalizeQuat( b3MulQuat(b3QuatFromExponentialMap( deltaRotation ), q1) ); +#endif +} + +// Pseudo angular velocity from a quaternion target +// w = 2 * (target - q) * conj(q) +static inline b3Vec3 b3DeltaQuatToRotation( b3Quat q, b3Quat target ) +{ + b3Quat s = q; + if ( b3DotQuat( q, target ) < 0.0f ) + { + // Correct polarity + s = b3NegateQuat( q ); + } + + b3Quat diff = { b3Sub( target.v, s.v ), target.s - s.s }; + b3Quat product = b3MulQuat( diff, b3Conjugate( s ) ); + return b3MulSV( 2.0f, product.v ); +} + +static inline float b3ScalarTripleProduct( b3Vec3 a, b3Vec3 b, b3Vec3 c ) +{ + b3Vec3 d; + d.x = b.y * c.z - b.z * c.y; + d.y = b.z * c.x - b.x * c.z; + d.z = b.x * c.y - b.y * c.x; + return a.x * d.x + a.y * d.y + a.z * d.z; +} + +// Get a value by index. Avoid undefined behavior of code like (&v.x)[2]. +static inline float b3GetByIndex( b3Vec3 v, int index ) +{ + B3_VALIDATE( 0 <= index && index < 3 ); + float temp[3] = { v.x, v.y, v.z }; + return temp[index]; +} + +static inline int b3MajorAxis( b3Vec3 v ) +{ + return v.x < v.y ? ( v.y < v.z ? 2 : 1 ) : ( v.x < v.z ? 2 : 0 ); +} + +static inline float b3MinElement( b3Vec3 v ) +{ + return b3MinFloat( v.x, b3MinFloat( v.y, v.z ) ); +} + +static inline float b3MaxElement( b3Vec3 v ) +{ + return b3MaxFloat( v.x, b3MaxFloat( v.y, v.z ) ); +} + +static inline int b3MaxElementIndex( b3Vec3 v ) +{ + return v.x < v.y ? ( v.y < v.z ? 2 : 1 ) : ( v.x < v.z ? 2 : 0 ); +} + +static inline b3Vec2 b3Add2( b3Vec2 a, b3Vec2 b ) +{ + b3Vec2 c = { a.x + b.x, a.y + b.y }; + return c; +} + +static inline b3Vec2 b3Sub2( b3Vec2 a, b3Vec2 b ) +{ + b3Vec2 c = { a.x - b.x, a.y - b.y }; + return c; +} + +static inline b3Vec2 b3Neg2( b3Vec2 v ) +{ + b3Vec2 c = { -v.x, -v.y }; + return c; +} + +static inline b3Vec2 b3MulSV2( float s, b3Vec2 v ) +{ + b3Vec2 c = { s * v.x, s * v.y }; + return c; +} + +// a + s * b +static inline b3Vec2 b3MulAdd2( b3Vec2 a, float s, b3Vec2 b ) +{ + b3Vec2 c = { a.x + s * b.x, a.y + s * b.y }; + return c; +} + +// a - s * b +static inline b3Vec2 b3MulSub2( b3Vec2 a, float s, b3Vec2 b ) +{ + b3Vec2 c = { a.x - s * b.x, a.y - s * b.y }; + return c; +} + +static inline float b3Cross2( b3Vec2 a, b3Vec2 b ) +{ + return a.x * b.y - a.y * b.x; +} + +static inline float b3DistanceSquared2( b3Vec2 a, b3Vec2 b ) +{ + float dx = b.x - a.x; + float dy = b.y - a.y; + return dx * dx + dy * dy; +} + +static inline b3Vec2 b3MulMV2( b3Matrix2 m, b3Vec2 a ) +{ + b3Vec2 b = { m.cx.x * a.x + m.cy.x * a.y, m.cx.y * a.x + m.cy.y * a.y }; + return b; +} + +static inline b3Matrix2 b3MulMM2( b3Matrix2 m1, b3Matrix2 m2 ) +{ + b3Matrix2 out; + out.cx = b3MulMV2( m1, m2.cx ); + out.cy = b3MulMV2( m1, m2.cy ); + return out; +} + +static inline float b3Det2( b3Matrix2 m ) +{ + return m.cx.x * m.cy.y - m.cx.y * m.cy.x; +} + +static inline b3Matrix2 b3Invert2( b3Matrix2 m ) +{ + float det = b3Det2( m ); + if ( b3AbsFloat( det ) > 1000.0f * FLT_MIN ) + { + float invDet = 1.0f / det; + return B3_LITERAL( b3Matrix2 ){ + { invDet * m.cy.y, -invDet * m.cx.y }, + { -invDet * m.cy.x, invDet * m.cx.x }, + }; + } + + return B3_LITERAL( b3Matrix2 ){ { 0.0f, 0.0f }, { 0.0f, 0.0f } }; +} + +// Assumes positive semi-definite +static inline b3Vec2 b3Solve2( b3Matrix2 m, b3Vec2 b ) +{ + float det = b3Det2( m ); + if ( det > 1000.0f * FLT_MIN ) + { + float invDet = 1.0f / det; + return B3_LITERAL( b3Vec2 ){ + invDet * m.cy.y * b.x - invDet * m.cy.x * b.y, + -invDet * m.cx.y * b.x + invDet * m.cx.x * b.y, + }; + } + + return B3_LITERAL( b3Vec2 ){ 0.0f, 0.0f }; +} + +// Convenience function: s * a + t * b + u * c +static inline b3Vec3 b3Blend3( float s, b3Vec3 a, float t, b3Vec3 b, float u, b3Vec3 c ) +{ + b3Vec3 d = { + s * a.x + t * b.x + u * c.x, + s * a.y + t * b.y + u * c.y, + s * a.z + t * b.z + u * c.z, + }; + return d; +} + +static inline b3Vec3 b3ModifiedCross( b3Vec3 a, b3Vec3 b ) +{ + b3Vec3 c; + c.x = a.y * b.z + a.z * b.y; + c.y = a.z * b.x + a.x * b.z; + c.z = a.x * b.y + a.y * b.x; + return c; +} + +static inline b3Matrix3 b3MakeDiagonalMatrix( float a, float b, float c ) +{ + return (b3Matrix3){ { a, 0.0f, 0.0f }, { 0.0f, b, 0.0f }, { 0.0f, 0.0f, c } }; +} + +static inline b3Matrix3 b3Skew( b3Vec3 v ) +{ + b3Matrix3 out; + out.cx = (b3Vec3){ 0, v.z, -v.y }; + out.cy = (b3Vec3){ -v.z, 0, v.x }; + out.cz = (b3Vec3){ v.y, -v.x, 0 }; + + return out; +} + +static inline b3Plane b3NormalizePlane( b3Plane plane ) +{ + float invLength = 1.0f / b3Length( plane.normal ); + return (b3Plane){ b3MulSV( invLength, plane.normal ), invLength * plane.offset }; +} + +static inline b3Plane b3MakePlaneFromNormalAndPoint( b3Vec3 normal, b3Vec3 point ) +{ + return (b3Plane){ normal, b3Dot( normal, point ) }; +} + +static inline b3Plane b3MakePlaneFromPoints( b3Vec3 point1, b3Vec3 point2, b3Vec3 point3 ) +{ + b3Plane plane; + plane.normal = b3Cross( b3Sub( point2, point1 ), b3Sub( point3, point1 ) ); + plane.normal = b3Normalize( plane.normal ); + plane.offset = b3Dot( plane.normal, point1 ); + return plane; +} + +static inline b3Vec3 b3MakeNormalFromPoints( b3Vec3 point1, b3Vec3 point2, b3Vec3 point3 ) +{ + b3Vec3 normal = b3Cross( b3Sub( point2, point1 ), b3Sub( point3, point1 ) ); + return b3Normalize( normal ); +} + +// normal2 = q * normal1 +// offset2 = dot(normal2, p) + offset1 +static inline b3Plane b3TransformPlane( b3Transform transform, b3Plane plane ) +{ + b3Vec3 normal = b3RotateVector( transform.q, plane.normal ); + return B3_LITERAL( b3Plane ){ normal, plane.offset + b3Dot( normal, transform.p ) }; +} + +/// Signed separation of a point from a plane +static inline float b3PlaneSeparation( b3Plane plane, b3Vec3 point ) +{ + return b3Dot( plane.normal, point ) - plane.offset; +} + +// Negative if p is below the triangle v1-v2-v3 +static inline float b3SignedVolume( b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, b3Vec3 p ) +{ + b3Vec3 e1 = b3Sub( v2, v1 ); + b3Vec3 e2 = b3Sub( v3, v1 ); + b3Vec3 n = b3Cross( e1, e2 ); + return b3Dot( n, b3Sub( p, v1 ) ); +} + +// todo eliminate this +static inline bool b3IsWithinSegments( const b3SegmentDistanceResult* result ) +{ + return ( 0.0f <= result->fraction1 && result->fraction1 <= 1.0f ) && + ( 0.0f <= result->fraction2 && result->fraction2 <= 1.0f ); +} + +static inline b3Matrix3 b3RotateInertia( b3Quat q, b3Matrix3 centralInertia ) +{ + b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( q ); + b3Matrix3 inertia = b3MulMM( rotationMatrix, b3MulMM( centralInertia, b3Transpose( rotationMatrix ) ) ); + return inertia; +} + +static inline b3Matrix3 b3TransformInertia( b3Transform transform, b3Matrix3 centralInertia, float mass ) +{ + b3Matrix3 inertia = b3RotateInertia( transform.q, centralInertia ); + inertia = b3AddMM( inertia, b3Steiner( mass, transform.p ) ); + return inertia; +} + +// Add a point to an AABB. +static inline b3AABB b3AABB_AddPoint( b3AABB a, b3Vec3 point ) +{ + return (b3AABB){ b3Min( a.lowerBound, point ), b3Max( a.upperBound, point ) }; +} diff --git a/vendor/box3d/src/src/mesh.c b/vendor/box3d/src/src/mesh.c new file mode 100644 index 000000000..c03489955 --- /dev/null +++ b/vendor/box3d/src/src/mesh.c @@ -0,0 +1,2411 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// Dirk Gregorius contributed portions of this code + +#include "container.h" +#include "math_internal.h" +#include "shape.h" +#include "simd.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" + +#include + +b3DeclareArray( b3VertexNode ); +b3DeclareArray( b3MeshNode ); +b3DeclareArray( b3MeshTriangle ); +b3DeclareArray( b3Vec3 ); +b3DeclareArray( b3Primitive ); +b3DeclareArrayNative( uint8_t ); + +#define B3_BIN_COUNT 8 +#define B3_DESIRED_TRIANGLES_PER_LEAF 4 +#define B3_LEAF_NODE 3 +#define B3_MAXIMUM_TRIANGLES_PER_LEAF 8 +#define B3_MESH_STACK_SIZE 256 + +static bool b3IsLeaf( const b3MeshNode* node ) +{ + return node->data.asLeaf.type == B3_LEAF_NODE; +} + +static b3MeshNode* b3GetMeshNodesWrite( b3MeshData* mesh ) +{ + if ( mesh->nodeOffset == 0 ) + { + return NULL; + } + + return (b3MeshNode*)( (intptr_t)mesh + mesh->nodeOffset ); +} + +static b3MeshNode* b3GetLeftChildWrite( b3MeshNode* node ) +{ + // The left child follows its parent. + B3_ASSERT( !b3IsLeaf( node ) ); + return node + 1; +} + +static const b3MeshNode* b3GetLeftChild( const b3MeshNode* node ) +{ + // The left child follows its parent. + B3_ASSERT( !b3IsLeaf( node ) ); + return node + 1; +} + +static b3MeshNode* b3GetRightChildWrite( b3MeshNode* node ) +{ + // We store the offset of the right child relative to its parent + B3_ASSERT( !b3IsLeaf( node ) ); + return node + node->data.asNode.childOffset; +} + +static const b3MeshNode* b3GetRightChild( const b3MeshNode* node ) +{ + // We store the offset of the right child relative to its parent + B3_ASSERT( !b3IsLeaf( node ) ); + return node + node->data.asNode.childOffset; +} + +static const b3MeshNode* b3GetRoot( const b3MeshData* mesh ) +{ + // The first node is the root + return b3GetMeshNodes( mesh ); +} + +static b3MeshNode* b3GetRootWrite( b3MeshData* mesh ) +{ + // The first node is the root + return b3GetMeshNodesWrite( mesh ); +} + +static b3MeshTriangle* b3GetMeshTrianglesWrite( b3MeshData* mesh ) +{ + if ( mesh->triangleOffset == 0 ) + { + return NULL; + } + + return (b3MeshTriangle*)( (intptr_t)mesh + mesh->triangleOffset ); +} + +static b3Vec3* b3GetMeshVerticesWrite( b3MeshData* mesh ) +{ + if ( mesh->vertexOffset == 0 ) + { + return NULL; + } + + return (b3Vec3*)( (intptr_t)mesh + mesh->vertexOffset ); +} + +// static b3Vec3 b3GetVertex( b3MeshData& mesh, int vertexIndex ) +//{ +// B3_ASSERT( 0 <= vertexIndex && vertexIndex < mesh.vertexCount ); +// b3Vec3* vertices = b3GetMeshVertices( &mesh ); +// return vertices[vertexIndex]; +// } + +static uint8_t* b3GetMeshMaterialIndicesWrite( b3MeshData* mesh ) +{ + if ( mesh->materialOffset == 0 ) + { + return NULL; + } + + return (uint8_t*)( (intptr_t)mesh + mesh->materialOffset ); +} + +static uint8_t* b3GetMeshFlagsWrite( b3MeshData* mesh ) +{ + if ( mesh->flagsOffset == 0 ) + { + return NULL; + } + + return (uint8_t*)( (intptr_t)mesh + mesh->flagsOffset ); +} + +static int b3GetNodeHeight( const b3MeshNode* node ) +{ + if ( b3IsLeaf( node ) ) + { + return 0; + } + + const b3MeshNode* leftChild = b3GetLeftChild( node ); + int leftHeight = b3GetNodeHeight( leftChild ); + const b3MeshNode* rightChild = b3GetRightChild( node ); + int rightHeight = b3GetNodeHeight( rightChild ); + + return 1 + b3MaxInt( leftHeight, rightHeight ); +} + +int b3GetHeight( const b3MeshData* mesh ) +{ + const b3MeshNode* root = b3GetRoot( mesh ); + if ( root == NULL ) + { + return 0; + } + + return b3GetNodeHeight( root ); +} + +#if B3_ENABLE_VALIDATION == 1 +static bool b3IsDegenerate( b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, float minArea ) +{ + b3Vec3 normal = b3Cross( b3Sub( v2, v1 ), b3Sub( v3, v1 ) ); + float lengthSq = b3LengthSquared( normal ); + return lengthSq < minArea * minArea; +} + +static bool b3IsNonDegenerate( const b3MeshData* mesh, float minArea ) +{ + const b3MeshTriangle* triangles = b3GetMeshTriangles( mesh ); + const b3Vec3* vertices = b3GetMeshVertices( mesh ); + + // Check triangles + for ( int index = 0; index < mesh->triangleCount; ++index ) + { + // Index range + b3MeshTriangle triangle = triangles[index]; + if ( triangle.index1 >= mesh->vertexCount ) + { + return false; + } + + if ( triangle.index2 >= mesh->vertexCount ) + { + return false; + } + + if ( triangle.index3 >= mesh->vertexCount ) + { + return false; + } + + // Degenerate topology + if ( triangle.index1 == triangle.index2 ) + { + return false; + } + if ( triangle.index1 == triangle.index3 ) + { + return false; + } + if ( triangle.index2 == triangle.index3 ) + { + return false; + } + + // Degenerate geometry + b3Vec3 vertex1 = vertices[triangle.index1]; + b3Vec3 vertex2 = vertices[triangle.index2]; + b3Vec3 vertex3 = vertices[triangle.index3]; + if ( b3IsDegenerate( vertex1, vertex2, vertex3, minArea ) ) + { + return false; + } + } + + return true; +} + +static inline b3AABB b3GetNodeAABB( const b3MeshNode* node ) +{ + return (b3AABB){ + node->lowerBound, + node->upperBound, + }; +} + +static bool b3IsConsistent( const b3MeshData* mesh ) +{ + const b3MeshTriangle* triangles = b3GetMeshTriangles( mesh ); + const b3Vec3* vertices = b3GetMeshVertices( mesh ); + + // Check nodes + int count = 0; + const b3MeshNode* stack[64]; + stack[count++] = b3GetRoot( mesh ); + + while ( count > 0 ) + { + const b3MeshNode* node = stack[--count]; + b3AABB nodeBounds = b3GetNodeAABB( node ); + + if ( b3IsLeaf( node ) == false ) + { + const b3MeshNode* child1 = b3GetLeftChild( node ); + b3AABB bounds1 = b3GetNodeAABB( child1 ); + const b3MeshNode* child2 = b3GetRightChild( node ); + b3AABB bounds2 = b3GetNodeAABB( child2 ); + + if ( !b3AABB_Contains( nodeBounds, bounds1 ) ) + { + return false; + } + + if ( !b3AABB_Contains( nodeBounds, bounds2 ) ) + { + return false; + } + + stack[count++] = child2; + stack[count++] = child1; + } + else + { + b3AABB triangleBounds = B3_BOUNDS3_EMPTY; + for ( uint32_t index = 0; index < node->data.asLeaf.triangleCount; ++index ) + { + int triangleIndex = node->triangleOffset + index; + B3_ASSERT( 0 <= triangleIndex && triangleIndex < mesh->triangleCount ); + + b3MeshTriangle triangle = triangles[triangleIndex]; + + b3AABB vertexBounds = B3_BOUNDS3_EMPTY; + vertexBounds = b3AABB_AddPoint( vertexBounds, vertices[triangle.index1] ); + vertexBounds = b3AABB_AddPoint( vertexBounds, vertices[triangle.index2] ); + vertexBounds = b3AABB_AddPoint( vertexBounds, vertices[triangle.index3] ); + + triangleBounds = b3AABB_Union( triangleBounds, vertexBounds ); + } + + if ( !b3AABB_Contains( nodeBounds, triangleBounds ) ) + { + return false; + } + } + } + + return true; +} + +bool b3IsValidMesh( const b3MeshData* meshData ) +{ + if ( meshData == NULL ) + { + return false; + } + + if ( meshData->version != B3_MESH_VERSION ) + { + return false; + } + + if ( meshData->byteCount < (int)sizeof( b3MeshData ) ) + { + return false; + } + + return b3IsConsistent( meshData ); +} + +#else + +bool b3IsValidMesh( const b3MeshData* meshData ) +{ + if ( meshData == NULL ) + { + return false; + } + + if ( meshData->version != B3_MESH_VERSION ) + { + return false; + } + + if ( meshData->byteCount < (int)sizeof( b3MeshData ) ) + { + return false; + } + + return true; +} + +#endif + +// Node for a vertex linked list +typedef struct b3VertexNode +{ + int32_t vertexIndex; + int nextNodeIndex; +} b3VertexNode; + +#define NAME b3VertexMap +#define KEY_TY uint64_t +#define VAL_TY int +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +typedef struct b3SpatialHash +{ + b3Array( b3VertexNode ) nodes; + const b3Vec3* vertices; + int vertexCount; + b3VertexMap vertexMap; + float cellSize; + float tolerance; +} b3SpatialHash; + +static void b3SpatialHash_Create( b3SpatialHash* h, const b3Vec3* vertices, int vertexCount, float tolerance ) +{ + h->vertices = vertices; + h->vertexCount = vertexCount; + h->tolerance = tolerance; + h->cellSize = 2.0f * tolerance; + b3Array_CreateN( h->nodes, vertexCount ); + + b3VertexMap_init( &h->vertexMap ); + b3VertexMap_reserve( &h->vertexMap, vertexCount ); + + B3_ASSERT( h->cellSize > 0.0f ); +} + +static void b3SpatialHash_Destroy( b3SpatialHash* h ) +{ + b3VertexMap_cleanup( &h->vertexMap ); + b3Array_Destroy( h->nodes ); +} + +// Welding works by bucketing nearby vertices into identical keys in a hash table. +// Bucketing is done manually with an array. +static int32_t b3SpatialHash_FindDuplicate( b3SpatialHash* h, int32_t currentIndex ) +{ + B3_ASSERT( currentIndex < h->vertexCount ); + b3Vec3 vertex = h->vertices[currentIndex]; + float cellSize = h->cellSize; + float tolerance = h->tolerance; + + // Get the grid coordinates for the current vertex + int32_t baseX = (int32_t)( floorf( vertex.x / cellSize ) ); + int32_t baseY = (int32_t)( floorf( vertex.y / cellSize ) ); + int32_t baseZ = (int32_t)( floorf( vertex.z / cellSize ) ); + + // Check the current cell and all 26 neighboring cells (3x3x3 - 1) + for ( int dx = -1; dx <= 1; ++dx ) + { + for ( int dy = -1; dy <= 1; ++dy ) + { + for ( int dz = -1; dz <= 1; ++dz ) + { + int32_t x = baseX + dx; + int32_t y = baseY + dy; + int32_t z = baseZ + dz; + + // Compute hash for this neighboring cell (this is the key in the map) + uint64_t key = 0; + key ^= (uint64_t)( x ) + 0x9e3779b9 + ( key << 6 ) + ( key >> 2 ); + key ^= (uint64_t)( y ) + 0x9e3779b9 + ( key << 6 ) + ( key >> 2 ); + key ^= (uint64_t)( z ) + 0x9e3779b9 + ( key << 6 ) + ( key >> 2 ); + + b3VertexMap_itr it = b3VertexMap_get( &h->vertexMap, key ); + if ( b3VertexMap_is_end( it ) == false ) + { + // Check all vertices in this key + int nodeIndex = it.data->val; + + while ( nodeIndex != B3_NULL_INDEX ) + { + b3VertexNode node = h->nodes.data[nodeIndex]; + + int32_t existingIndex = node.vertexIndex; + B3_ASSERT( existingIndex < currentIndex ); + B3_ASSERT( existingIndex < h->vertexCount ); + + b3Vec3 other = h->vertices[existingIndex]; + + // IsEqual inlined: check if vertices are within tolerance + if ( fabsf( vertex.x - other.x ) <= tolerance && fabsf( vertex.y - other.y ) <= tolerance && + fabsf( vertex.z - other.z ) <= tolerance ) + { + // Found duplicate + return existingIndex; + } + + nodeIndex = node.nextNodeIndex; + } + } + } + } + } + + // No duplicate found, add to hash table + uint64_t currentKey = 0; + currentKey ^= (uint64_t)( baseX ) + 0x9e3779b9 + ( currentKey << 6 ) + ( currentKey >> 2 ); + currentKey ^= (uint64_t)( baseY ) + 0x9e3779b9 + ( currentKey << 6 ) + ( currentKey >> 2 ); + currentKey ^= (uint64_t)( baseZ ) + 0x9e3779b9 + ( currentKey << 6 ) + ( currentKey >> 2 ); + + b3VertexMap_itr it = b3VertexMap_get( &h->vertexMap, currentKey ); + if ( b3VertexMap_is_end( it ) == false ) + { + int nodeIndex = it.data->val; + + b3VertexNode node = { + .vertexIndex = currentIndex, + .nextNodeIndex = nodeIndex, + }; + + it.data->val = h->nodes.count; + b3Array_Push( h->nodes, node ); + } + else + { + b3VertexNode node = { + .vertexIndex = currentIndex, + .nextNodeIndex = B3_NULL_INDEX, + }; + + b3VertexMap_insert( &h->vertexMap, currentKey, h->nodes.count ); + b3Array_Push( h->nodes, node ); + } + + // Not welded + return B3_NULL_INDEX; +} + +typedef struct b3WeldData +{ + const b3Vec3* srcVertices; + const int32_t* srcIndices; + + b3Vec3* dstVertices; + int32_t* dstIndices; + + int vertexCount; + int indexCount; +} b3WeldData; + +static int b3WeldVertices( b3WeldData* data, float tolerance ) +{ + int vertexCount = data->vertexCount; + int uniqueCount = 0; + + // Create spatial hash and find duplicates + b3SpatialHash spatialHash; + b3SpatialHash_Create( &spatialHash, data->srcVertices, vertexCount, tolerance ); + b3Array( int ) vertexMapping = { 0 }; + b3Array_Resize( vertexMapping, vertexCount ); + + for ( int i = 0; i < vertexCount; ++i ) + { + int32_t duplicateIndex = b3SpatialHash_FindDuplicate( &spatialHash, i ); + + if ( duplicateIndex == B3_NULL_INDEX ) + { + // New unique vertex + vertexMapping.data[i] = uniqueCount; + data->dstVertices[uniqueCount] = data->srcVertices[i]; + uniqueCount += 1; + } + else + { + // Found duplicate, map to existing vertex + vertexMapping.data[i] = vertexMapping.data[duplicateIndex]; + } + } + + // Update indices to reference the new vertex array + int indexCount = data->indexCount; + for ( int i = 0; i < indexCount; ++i ) + { + int srcIndex = data->srcIndices[i]; + B3_ASSERT( srcIndex < vertexCount ); + data->dstIndices[i] = vertexMapping.data[srcIndex]; + } + + b3SpatialHash_Destroy( &spatialHash ); + b3Array_Destroy( vertexMapping ); + + return uniqueCount; +} + +static inline void b3StoreLeaf( b3MeshNode* node, const b3AABB* aabb, int triangleCount, int triangleOffset ) +{ + node->data.asLeaf.type = B3_LEAF_NODE; + node->data.asLeaf.triangleCount = triangleCount; + node->triangleOffset = triangleOffset; + node->lowerBound = aabb->lowerBound; + node->upperBound = aabb->upperBound; +} + +typedef struct b3Primitive +{ + b3AABB aabb; + b3Vec3 center; + int triangleIndex; +} b3Primitive; + +typedef struct b3Bucket +{ + int count; + b3AABB bounds; +} b3Bucket; + +typedef struct b3Split +{ + b3AABB leftBounds; + b3AABB rightBounds; + int axis; + int index; +} b3Split; + +static b3Split b3SplitBinnedSah( int count, b3Primitive* primitives ) +{ + b3Split split; + split.axis = -1; + split.index = -1; + + // Compute bounds of primitive centroids and choose split axis + b3AABB bounds = { primitives[0].center, primitives[0].center }; + for ( int i = 1; i < count; ++i ) + { + bounds = b3AABB_AddPoint( bounds, primitives[i].center ); + } + + // Compute costs for splitting after each bucket and keep track of best split + // This is a small O(n^2) loop. This can be further optimized, but it is already + // very fast and is kept for simplicity right now. + int bestBucket = -1; + float bestCost = FLT_MAX; + + for ( int axis = 0; axis < 3; ++axis ) + { + b3Vec3 extent = b3AABB_Extents( bounds ); + if ( b3GetByIndex( extent, axis ) < B3_LINEAR_SLOP ) + { + continue; + } + + // Initialize buckets + b3Bucket buckets[B3_BIN_COUNT]; + for ( int i = 0; i < B3_BIN_COUNT; ++i ) + { + buckets[i].count = 0; + buckets[i].bounds = B3_BOUNDS3_EMPTY; + } + + // Fill buckets + float factor = B3_BIN_COUNT * ( 1.0f - FLT_EPSILON ) / + ( b3GetByIndex( bounds.upperBound, axis ) - b3GetByIndex( bounds.lowerBound, axis ) ); + for ( int i = 0; i < count; ++i ) + { + b3Vec3 center = primitives[i].center; + int index = (int)( factor * ( b3GetByIndex( center, axis ) - b3GetByIndex( bounds.lowerBound, axis ) ) ); + B3_ASSERT( 0 <= index && index < B3_BIN_COUNT ); + + buckets[index].count++; + buckets[index].bounds = b3AABB_Union( buckets[index].bounds, primitives[i].aabb ); + } + + // Evaluate splits + for ( int i = 0; i < B3_BIN_COUNT - 1; ++i ) + { + int leftCount = 0; + b3AABB leftBounds = B3_BOUNDS3_EMPTY; + for ( int k = 0; k <= i; ++k ) + { + leftCount += buckets[k].count; + leftBounds = b3AABB_Union( leftBounds, buckets[k].bounds ); + } + + int rightCount = 0; + b3AABB rightBounds = B3_BOUNDS3_EMPTY; + for ( int k = i + 1; k < B3_BIN_COUNT; ++k ) + { + rightCount += buckets[k].count; + rightBounds = b3AABB_Union( rightBounds, buckets[k].bounds ); + } + + B3_ASSERT( leftCount + rightCount == count ); + if ( leftCount > 0 && rightCount > 0 ) + { + float cost = leftCount * b3AABB_Area( leftBounds ) + rightCount * b3AABB_Area( rightBounds ); + + if ( cost < bestCost ) + { + bestBucket = i; + bestCost = cost; + + split.axis = axis; + split.index = leftCount; + split.leftBounds = leftBounds; + split.rightBounds = rightBounds; + } + } + } + } + + // Partition + if ( bestBucket >= 0 ) + { + int axis = split.axis; + float factor = B3_BIN_COUNT * ( 1.0f - FLT_EPSILON ) / + ( b3GetByIndex( bounds.upperBound, axis ) - b3GetByIndex( bounds.lowerBound, axis ) ); + + int splitIndex = 0; + for ( int i = 0; i < count; ++i ) + { + b3Vec3 center = primitives[i].center; + int index = (int)( factor * ( b3GetByIndex( center, axis ) - b3GetByIndex( bounds.lowerBound, axis ) ) ); + + if ( index <= bestBucket ) + { + b3Primitive temp = primitives[i]; + primitives[i] = primitives[splitIndex]; + primitives[splitIndex] = temp; + splitIndex++; + } + } + B3_ASSERT( splitIndex == split.index ); + } + + return split; +} + +static b3Split b3SplitHalf( int count, b3Primitive* primitives ) +{ + // Split in the middle + int splitIndex = count / 2; + + b3AABB leftBounds = B3_BOUNDS3_EMPTY; + for ( int i = 0; i < splitIndex; ++i ) + { + leftBounds = b3AABB_Union( leftBounds, primitives[i].aabb ); + } + + b3AABB rightBounds = B3_BOUNDS3_EMPTY; + for ( int i = splitIndex; i < count; ++i ) + { + + rightBounds = b3AABB_Union( rightBounds, primitives[i].aabb ); + } + + b3AABB bounds = b3AABB_Union( leftBounds, rightBounds ); + int axis = b3MajorAxis( b3AABB_Extents( bounds ) ); + + b3Split split; + split.axis = axis; + split.index = splitIndex; + split.leftBounds = leftBounds; + split.rightBounds = rightBounds; + + return split; +} + +static b3Split b3SplitMedian( int count, b3Primitive* primitives ) +{ + B3_ASSERT( count > 2 ); + + b3Vec3 lowerBound = primitives[0].center; + b3Vec3 upperBound = primitives[0].center; + + for ( int i = 1; i < count; ++i ) + { + lowerBound = b3Min( lowerBound, primitives[i].center ); + upperBound = b3Max( upperBound, primitives[i].center ); + } + + b3Vec3 d = b3Sub( upperBound, lowerBound ); + b3Vec3 c = b3MulSV( 0.5f, b3Add( lowerBound, upperBound ) ); + + b3Split split = { 0 }; + split.index = -1; + + // Partition longest axis using the Hoare partition scheme + // https://en.wikipedia.org/wiki/Quicksort + // https://nicholasvadivelu.com/2021/01/11/array-partition/ + int i1 = 0, i2 = count; + if ( d.x >= d.y && d.x >= d.z ) + { + split.axis = 0; + + float pivot = c.x; + + while ( i1 < i2 ) + { + while ( i1 < i2 && primitives[i1].center.x < pivot ) + { + i1 += 1; + }; + + while ( i1 < i2 && primitives[i2 - 1].center.x >= pivot ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap primitives + b3Primitive temp = primitives[i1]; + primitives[i1] = primitives[i2 - 1]; + primitives[i2 - 1] = temp; + + i1 += 1; + i2 -= 1; + } + } + } + else if ( d.y >= d.z ) + { + split.axis = 1; + + float pivot = c.y; + + while ( i1 < i2 ) + { + while ( i1 < i2 && primitives[i1].center.y < pivot ) + { + i1 += 1; + }; + + while ( i1 < i2 && primitives[i2 - 1].center.y >= pivot ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap primitives + b3Primitive temp = primitives[i1]; + primitives[i1] = primitives[i2 - 1]; + primitives[i2 - 1] = temp; + + i1 += 1; + i2 -= 1; + } + } + } + else + { + split.axis = 2; + + float pivot = c.z; + + while ( i1 < i2 ) + { + while ( i1 < i2 && primitives[i1].center.z < pivot ) + { + i1 += 1; + }; + + while ( i1 < i2 && primitives[i2 - 1].center.z >= pivot ) + { + i2 -= 1; + }; + + if ( i1 < i2 ) + { + // Swap primitives + b3Primitive temp = primitives[i1]; + primitives[i1] = primitives[i2 - 1]; + primitives[i2 - 1] = temp; + + i1 += 1; + i2 -= 1; + } + } + } + B3_ASSERT( i1 == i2 ); + B3_ASSERT( 0 <= i1 && i1 < count ); + + if ( i1 == 0 || i1 == count - 1 ) + { + // failed to split + i1 = count / 2; + } + + b3AABB leftBounds = B3_BOUNDS3_EMPTY; + for ( int i = 0; i < i1; ++i ) + { + leftBounds = b3AABB_Union( leftBounds, primitives[i].aabb ); + } + + b3AABB rightBounds = B3_BOUNDS3_EMPTY; + for ( int i = i1; i < count; ++i ) + { + rightBounds = b3AABB_Union( rightBounds, primitives[i].aabb ); + } + + split.index = i1; + split.leftBounds = leftBounds; + split.rightBounds = rightBounds; + return split; +} + +#if B3_ENABLE_VALIDATION == 1 + +static bool b3ValidateSplit( int count, b3Primitive* primitives, const b3Split* split ) +{ + if ( split->axis < 0 ) + { + return false; + } + + for ( int i = 0; i < split->index; ++i ) + { + if ( !b3AABB_Contains( split->leftBounds, primitives[i].aabb ) ) + { + return false; + } + } + + for ( int i = split->index; i < count; ++i ) + { + if ( !b3AABB_Contains( split->rightBounds, primitives[i].aabb ) ) + { + return false; + } + } + + return true; +} + +#endif + +static int b3BuildRecursive( b3Array( b3MeshNode ) * nodes, int count, b3Primitive* primitives, b3Primitive* base, + bool useMedianSplit, int* height ) +{ + if ( count > B3_DESIRED_TRIANGLES_PER_LEAF ) + { + // Try to split the input set using the SAH + b3Split split; + if ( useMedianSplit ) + { + split = b3SplitMedian( count, primitives ); + } + else + { + split = b3SplitBinnedSah( count, primitives ); + } + + if ( split.axis < 0 ) + { + if ( count > B3_MAXIMUM_TRIANGLES_PER_LEAF ) + { + // Re-split. This is a less optimal split and can create more false positives! + split = b3SplitHalf( count, primitives ); + } + else + { + b3AABB bounds = B3_BOUNDS3_EMPTY; + for ( int i = 0; i < count; ++i ) + { + bounds = b3AABB_Union( bounds, primitives[i].aabb ); + } + + // We have only a few triangles left. Create a leaf. + int index = b3Array_AddIndex( *nodes ); + b3StoreLeaf( &nodes->data[index], &bounds, count, (int)( primitives - base ) ); + + return index; + } + } + B3_VALIDATE( b3ValidateSplit( count, primitives, &split ) ); + + // Allocate node and recurse + int index = b3Array_AddIndex( *nodes ); + int heightLeft = 0, heightRight = 0; + int leftIndex = b3BuildRecursive( nodes, split.index, primitives, base, useMedianSplit, &heightLeft ); + int rightIndex = + b3BuildRecursive( nodes, count - split.index, primitives + split.index, base, useMedianSplit, &heightRight ); + + *height = b3MaxInt( heightLeft, heightRight ) + 1; + + B3_UNUSED( leftIndex ); + B3_ASSERT( leftIndex - index == 1 && rightIndex - index > 1 ); + + b3AABB aabb = b3AABB_Union( split.leftBounds, split.rightBounds ); + b3MeshNode* node = b3Array_Get( *nodes, index ); + node->data.asNode.axis = split.axis; + node->data.asNode.childOffset = rightIndex - index; + node->lowerBound = aabb.lowerBound; + node->upperBound = aabb.upperBound; + // triangleOffset is leaf-only, but lives outside the union — zero it so mesh->hash is deterministic + node->triangleOffset = 0; + + return index; + } + + b3AABB aabb = B3_BOUNDS3_EMPTY; + for ( int i = 0; i < count; ++i ) + { + aabb = b3AABB_Union( aabb, primitives[i].aabb ); + } + + int index = b3Array_AddIndex( *nodes ); + b3StoreLeaf( &nodes->data[index], &aabb, count, (int)( primitives - base ) ); + + *height = 1; + + return index; +} + +static bool b3SortMeshTriangles( b3MeshData* mesh ) +{ + b3MeshTriangle* triangles = b3GetMeshTrianglesWrite( mesh ); + uint8_t* materialIndices = b3GetMeshMaterialIndicesWrite( mesh ); + + // Sort triangles in depth-first-order + int offset = 0; + b3Array( b3MeshTriangle ) tempTriangles; + b3Array_CreateN( tempTriangles, mesh->triangleCount ); + + b3Array( uint8_t ) tempMaterialIndices; + b3Array_CreateN( tempMaterialIndices, mesh->triangleCount ); + + int count = 0; + b3MeshNode* stack[B3_MESH_STACK_SIZE]; + stack[count++] = b3GetRootWrite( mesh ); + + while ( count > 0 ) + { + b3MeshNode* node = stack[--count]; + + if ( b3IsLeaf( node ) == false ) + { + if ( count >= B3_MESH_STACK_SIZE - 2 ) + { + return false; + } + + stack[count++] = b3GetRightChildWrite( node ); + stack[count++] = b3GetLeftChildWrite( node ); + } + else + { + int triangleCount = node->data.asLeaf.triangleCount; + int triangleOffset = node->triangleOffset; + + for ( int triangle = 0; triangle < triangleCount; ++triangle ) + { + int index = triangleOffset + triangle; + b3Array_Push( tempTriangles, triangles[index] ); + b3Array_Push( tempMaterialIndices, materialIndices[index] ); + } + + node->triangleOffset = offset; + offset += triangleCount; + } + } + + B3_ASSERT( offset == tempTriangles.count ); + B3_ASSERT( tempTriangles.count == mesh->triangleCount ); + B3_ASSERT( tempMaterialIndices.count == mesh->triangleCount ); + + // Copy sorted triangle array back to tree + memcpy( triangles, tempTriangles.data, mesh->triangleCount * sizeof( b3MeshTriangle ) ); + memcpy( materialIndices, tempMaterialIndices.data, mesh->triangleCount * sizeof( uint8_t ) ); + + b3Array_Destroy( tempTriangles ); + b3Array_Destroy( tempMaterialIndices ); + + return true; +} + +typedef struct +{ + int vertex1; + int vertex2; + int triangle1; + int triangle2; + uint16_t triangleCount; + + // The index of an edge within the parent triangle: 0, 1, or 2. 0xFF is unset + uint8_t triangleEdgeIndex1; + uint8_t triangleEdgeIndex2; +} b3MeshEdge; + +#define NAME b3EdgeMap +#define KEY_TY uint64_t +#define VAL_TY int +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +#if 0 +// todo this is for testing other hash tables +struct b3TestHash +{ + size_t operator()( uint64_t key ) const + { + key ^= key >> 23; + key *= 0x2127599bf4325c37ull; + key ^= key >> 47; + return (size_t)key; + } +}; +#endif + +// Results for MeshCreationBenchmark +// eastl::hash_map : 4.9703 ms and 5.1445ms with FastHash function +// std::unordered_map : 4.8755 ms and 4.4780ms with FastHash function +// verstable : 3.3968 ms with default FastHash +// no edge identification : 1.7396 ms +static void b3IdentifyEdges( b3MeshData* mesh ) +{ + b3MeshTriangle* triangles = b3GetMeshTrianglesWrite( mesh ); + const b3Vec3* vertices = b3GetMeshVertices( mesh ); + uint8_t* flags = b3GetMeshFlagsWrite( mesh ); + + int triangleCount = mesh->triangleCount; + int edgeCount = 3 * triangleCount; + b3MeshEdge* edges = B3_ALLOC( b3MeshEdge, edgeCount ); + b3Vec3* normals = B3_ALLOC( b3Vec3, triangleCount ); + + for ( int i = 0; i < triangleCount; ++i ) + { + b3MeshTriangle* triangle = triangles + i; + int i1 = triangle->index1; + int i2 = triangle->index2; + int i3 = triangle->index3; + + edges[3 * i + 0].vertex1 = b3MinInt( i1, i2 ); + edges[3 * i + 0].vertex2 = b3MaxInt( i1, i2 ); + edges[3 * i + 0].triangle1 = i; + edges[3 * i + 0].triangle2 = B3_NULL_INDEX; + edges[3 * i + 0].triangleEdgeIndex1 = 0; + edges[3 * i + 0].triangleEdgeIndex2 = 0xFF; + edges[3 * i + 0].triangleCount = 1; + + edges[3 * i + 1].vertex1 = b3MinInt( i2, i3 ); + edges[3 * i + 1].vertex2 = b3MaxInt( i2, i3 ); + edges[3 * i + 1].triangle1 = i; + edges[3 * i + 1].triangle2 = B3_NULL_INDEX; + edges[3 * i + 1].triangleEdgeIndex1 = 1; + edges[3 * i + 1].triangleEdgeIndex2 = 0xFF; + edges[3 * i + 1].triangleCount = 1; + + edges[3 * i + 2].vertex1 = b3MinInt( i3, i1 ); + edges[3 * i + 2].vertex2 = b3MaxInt( i3, i1 ); + edges[3 * i + 2].triangle1 = i; + edges[3 * i + 2].triangle2 = B3_NULL_INDEX; + edges[3 * i + 2].triangleEdgeIndex1 = 2; + edges[3 * i + 2].triangleEdgeIndex2 = 0xFF; + edges[3 * i + 2].triangleCount = 1; + + b3Vec3 v1 = vertices[i1]; + b3Vec3 v2 = vertices[i2]; + b3Vec3 v3 = vertices[i3]; + + b3Vec3 e1 = b3Sub( v2, v1 ); + b3Vec3 e2 = b3Sub( v3, v1 ); + b3Vec3 n = b3Cross( e1, e2 ); + + normals[i] = b3Normalize( n ); + } + + b3EdgeMap map; + b3EdgeMap_init( &map ); + b3EdgeMap_reserve( &map, edgeCount ); + + uint64_t key = (uint64_t)edges[0].vertex1 << 32 | (uint64_t)edges[0].vertex2; + b3EdgeMap_insert( &map, key, 0 ); + + // Find unique edges and assign adjacency + for ( int i = 1; i < edgeCount; ++i ) + { + b3MeshEdge* edge = edges + i; + key = (uint64_t)edge->vertex1 << 32 | (uint64_t)edge->vertex2; + b3EdgeMap_itr itr = b3EdgeMap_get( &map, key ); + + if ( b3EdgeMap_is_end( itr ) ) + { + b3EdgeMap_insert( &map, key, i ); + } + else + { + int otherIndex = itr.data->val; + B3_ASSERT( otherIndex < i ); + + b3MeshEdge* base = edges + otherIndex; + if ( base->triangleCount == 1 ) + { + base->triangle2 = edge->triangle1; + base->triangleEdgeIndex2 = edge->triangleEdgeIndex1; + } + + base->triangleCount += 1; + } + } + + b3EdgeMap_cleanup( &map ); + + for ( int i = 0; i < edgeCount; ++i ) + { + b3MeshEdge* edge = edges + i; + if ( edge->triangleCount != 2 ) + { + continue; + } + + B3_ASSERT( edge->triangleEdgeIndex1 < 3 ); + B3_ASSERT( edge->triangleEdgeIndex2 < 3 ); + + b3MeshTriangle* triangle1 = triangles + edge->triangle1; + b3MeshTriangle* triangle2 = triangles + edge->triangle2; + uint8_t* flag1 = flags + edge->triangle1; + uint8_t* flag2 = flags + edge->triangle2; + + int j1 = triangle2->index1; + int j2 = triangle2->index2; + int j3 = triangle2->index3; + + int opposite = B3_NULL_INDEX; + + switch ( edge->triangleEdgeIndex2 ) + { + case 0: + opposite = j3; + break; + + case 1: + opposite = j1; + break; + + case 2: + opposite = j2; + break; + + default: + B3_ASSERT( false ); + } + + int i1 = triangle1->index1; + int i2 = triangle1->index2; + int i3 = triangle1->index3; + + b3Vec3 v1 = vertices[i1]; + b3Vec3 v2 = vertices[i2]; + b3Vec3 v3 = vertices[i3]; + b3Vec3 p = vertices[opposite]; + + float cos5Deg = 0.9962f; + float signedVolume = b3SignedVolume( v1, v2, v3, p ); + b3Vec3 n1 = normals[edge->triangle1]; + b3Vec3 n2 = normals[edge->triangle2]; + float cosAngle = b3Dot( n1, n2 ); + if ( signedVolume > 0.0f || cosAngle > cos5Deg ) + { + int edgeFlags[3] = { b3_concaveEdge1, b3_concaveEdge2, b3_concaveEdge3 }; + *flag1 |= edgeFlags[edge->triangleEdgeIndex1]; + *flag2 |= edgeFlags[edge->triangleEdgeIndex2]; + } + + if ( signedVolume < 0.0f || cosAngle > cos5Deg ) + { + int edgeFlags[3] = { b3_inverseConcaveEdge1, b3_inverseConcaveEdge2, b3_inverseConcaveEdge3 }; + *flag1 |= edgeFlags[edge->triangleEdgeIndex1]; + *flag2 |= edgeFlags[edge->triangleEdgeIndex2]; + } + } + + B3_FREE( normals, b3Vec3, triangleCount ); + B3_FREE( edges, b3MeshEdge, edgeCount ); +} + +b3MeshData* b3CreateGridMesh( int xCount, int zCount, float cellWidth, int materialCount, bool identifyEdges ) +{ + B3_ASSERT( 0 <= materialCount && materialCount <= UINT8_MAX ); + + // Create vertices + int vertexCount = ( xCount + 1 ) * ( zCount + 1 ); + + b3Array( b3Vec3 ) vertices = { 0 }; + b3Array_Resize( vertices, vertexCount ); + int index = 0; + + float xWidth = cellWidth * xCount; + float zWidth = cellWidth * zCount; + + float x = -0.5f * xWidth; + for ( int ix = 0; ix <= xCount; ++ix ) + { + float z = -0.5f * zWidth; + for ( int iz = 0; iz <= zCount; ++iz ) + { + vertices.data[index] = (b3Vec3){ x, 0.0f, z }; + z += cellWidth; + index += 1; + } + x += cellWidth; + } + B3_ASSERT( index == vertexCount ); + + // Triangles + int triangleCount = 2 * xCount * zCount; + + b3Array( int ) indices = { 0 }; + b3Array_Resize( indices, 3 * triangleCount ); + + b3Array( uint8_t ) materialIndices = { 0 }; + b3Array_Resize( materialIndices, triangleCount ); + + int materialIndex = 0; + index = 0; + for ( int ix = 0; ix < xCount; ++ix ) + { + for ( int iz = 0; iz < zCount; ++iz ) + { + int index1 = iz + ( zCount + 1 ) * ix; + int index2 = index1 + 1; + int index3 = index2 + ( zCount + 1 ); + int index4 = index3 - 1; + + B3_ASSERT( index1 < vertexCount ); + B3_ASSERT( index2 < vertexCount ); + B3_ASSERT( index3 < vertexCount ); + B3_ASSERT( index4 < vertexCount ); + + indices.data[index + 0] = index1; + indices.data[index + 1] = index2; + indices.data[index + 2] = index3; + + indices.data[index + 3] = index3; + indices.data[index + 4] = index4; + indices.data[index + 5] = index1; + + if ( materialCount > 0 ) + { + materialIndices.data[2 * materialIndex + 0] = (uint8_t)( materialIndex % materialCount ); + materialIndices.data[2 * materialIndex + 1] = (uint8_t)( materialIndex % materialCount ); + } + + materialIndex += 1; + index += 6; + } + } + B3_ASSERT( index == 3 * triangleCount ); + + b3MeshDef def = { 0 }; + def.vertexCount = vertices.count; + def.vertices = vertices.data; + def.triangleCount = indices.count / 3; + def.indices = indices.data; + def.materialIndices = materialCount > 0 ? materialIndices.data : NULL; + def.useMedianSplit = true; + def.identifyEdges = identifyEdges; + + b3MeshData* meshData = b3CreateMesh( &def, NULL, 0 ); + + b3Array_Destroy( indices ); + b3Array_Destroy( vertices ); + b3Array_Destroy( materialIndices ); + + return meshData; +} + +b3MeshData* b3CreateWaveMesh( int xCount, int zCount, float cellWidth, float amplitude, float rowFrequency, + float columnFrequency ) +{ + // Create vertices + int vertexCount = ( xCount + 1 ) * ( zCount + 1 ); + + b3Array( b3Vec3 ) vertices = { 0 }; + b3Array_Resize( vertices, vertexCount ); + int index = 0; + + float xWidth = cellWidth * xCount; + float zWidth = cellWidth * zCount; + + float omegaZ = 2.0f * B3_PI * rowFrequency * cellWidth; + float omegaX = 2.0f * B3_PI * columnFrequency * cellWidth; + + float x = -0.5f * xWidth; + for ( int ix = 0; ix <= xCount; ++ix ) + { + float rowHeight = sinf( omegaX * ix ); + + float z = -0.5f * zWidth; + for ( int iz = 0; iz <= zCount; ++iz ) + { + float columnHeight = sinf( omegaZ * iz ); + + float y = amplitude * rowHeight * columnHeight; + vertices.data[index] = (b3Vec3){ x, y, z }; + z += cellWidth; + index += 1; + } + x += cellWidth; + } + B3_ASSERT( index == vertexCount ); + + // Triangles + int triangleCount = 2 * xCount * zCount; + + b3Array( int ) indices = { 0 }; + b3Array_Resize( indices, 3 * triangleCount ); + + index = 0; + for ( int ix = 0; ix < xCount; ++ix ) + { + for ( int iz = 0; iz < zCount; ++iz ) + { + int index1 = iz + ( zCount + 1 ) * ix; + int index2 = index1 + 1; + int index3 = index2 + ( zCount + 1 ); + int index4 = index3 - 1; + + B3_ASSERT( index1 < vertexCount ); + B3_ASSERT( index2 < vertexCount ); + B3_ASSERT( index3 < vertexCount ); + B3_ASSERT( index4 < vertexCount ); + + indices.data[index + 0] = index1; + indices.data[index + 1] = index2; + indices.data[index + 2] = index3; + + indices.data[index + 3] = index3; + indices.data[index + 4] = index4; + indices.data[index + 5] = index1; + + index += 6; + } + } + B3_ASSERT( index == 3 * triangleCount ); + + b3MeshDef def = { 0 }; + def.vertexCount = vertices.count; + def.vertices = vertices.data; + def.triangleCount = indices.count / 3; + def.indices = indices.data; + def.useMedianSplit = true; + def.identifyEdges = true; + + b3MeshData* meshData = b3CreateMesh( &def, NULL, 0 ); + + b3Array_Destroy( indices ); + b3Array_Destroy( vertices ); + + return meshData; +} + +b3MeshData* b3CreateTorusMesh( int radialResolution, int tubularResolution, float radius, float thickness ) +{ + // Create vertices + b3Array( b3Vec3 ) vertices = { 0 }; + + for ( int radialIndex = 0; radialIndex < radialResolution; radialIndex++ ) + { + for ( int tubularIndex = 0; tubularIndex < tubularResolution; tubularIndex++ ) + { + float u = (float)tubularIndex / tubularResolution * B3_TWO_PI; + float v = (float)radialIndex / radialResolution * B3_TWO_PI; + + float x = ( radius + thickness * b3Cos( v ) ) * b3Cos( u ); + float y = ( radius + thickness * b3Cos( v ) ) * b3Sin( u ); + float z = thickness * b3Sin( v ); + + b3Vec3 vertex = { x, y, z }; + b3Array_Push( vertices, vertex ); + } + } + + // Triangles + b3Array( int ) indices = { 0 }; + for ( int radialIndex1 = 0; radialIndex1 < radialResolution; radialIndex1++ ) + { + int radialIndex2 = ( radialIndex1 + 1 ) % radialResolution; + for ( int tubularIndex1 = 0; tubularIndex1 < tubularResolution; tubularIndex1++ ) + { + int tubularIndex2 = ( tubularIndex1 + 1 ) % tubularResolution; + int index1 = radialIndex1 * tubularResolution + tubularIndex1; + int index2 = radialIndex1 * tubularResolution + tubularIndex2; + int index3 = radialIndex2 * tubularResolution + tubularIndex2; + int index4 = radialIndex2 * tubularResolution + tubularIndex1; + + b3Array_Push( indices, index1 ); + b3Array_Push( indices, index2 ); + b3Array_Push( indices, index3 ); + + b3Array_Push( indices, index3 ); + b3Array_Push( indices, index4 ); + b3Array_Push( indices, index1 ); + } + } + + b3MeshDef def = { 0 }; + def.vertexCount = vertices.count; + def.vertices = vertices.data; + def.triangleCount = indices.count / 3; + def.indices = indices.data; + def.useMedianSplit = false; + def.identifyEdges = true; + + b3MeshData* meshData = b3CreateMesh( &def, NULL, 0 ); + + b3Array_Destroy( vertices ); + b3Array_Destroy( indices ); + return meshData; +} + +b3MeshData* b3CreateBoxMesh( b3Vec3 center, b3Vec3 extent, bool identifyEdges ) +{ + float x = extent.x; + float y = extent.y; + float z = extent.z; + b3Vec3 vertices[] = { + { x, y, z }, { -x, y, z }, { -x, -y, z }, { x, -y, z }, { x, y, -z }, { -x, y, -z }, { -x, -y, -z }, { x, -y, -z }, + }; + + for ( int i = 0; i < 8; ++i ) + { + vertices[i] = b3Add( vertices[i], center ); + } + + int indices[] = { + 0, 1, 3, 1, 2, 3, // front + 0, 4, 1, 1, 4, 5, // top + 0, 3, 7, 4, 0, 7, // right + 4, 7, 5, 6, 5, 7, // back + 1, 5, 2, 6, 2, 5, // left + 3, 2, 7, 6, 7, 2, // bottom + }; + + b3MeshDef def = { 0 }; + def.vertexCount = 8; + def.vertices = vertices; + def.triangleCount = 12; + def.indices = indices; + def.useMedianSplit = false; + def.identifyEdges = identifyEdges; + + return b3CreateMesh( &def, NULL, 0 ); +} + +b3MeshData* b3CreateHollowBoxMesh(b3Vec3 center, b3Vec3 extent) +{ + float x = extent.x; + float y = extent.y; + float z = extent.z; + b3Vec3 vertices[] = { + { x, y, z }, { -x, y, z }, { -x, -y, z }, { x, -y, z }, { x, y, -z }, { -x, y, -z }, { -x, -y, -z }, { x, -y, -z }, + }; + + for ( int i = 0; i < 8; ++i ) + { + vertices[i] = b3Add( vertices[i], center ); + } + + int indices[] = { + 3, 1, 0, 3, 2, 1, // front + 1, 4, 0, 5, 4, 1, // top + 7, 3, 0, 7, 0, 4, // right + 5, 7, 4, 7, 5, 6, // back + 2, 5, 1, 5, 2, 6, // left + 7, 2, 3, 2, 7, 6, // bottom + }; + + b3MeshDef def = { 0 }; + def.vertexCount = 8; + def.vertices = vertices; + def.triangleCount = 12; + def.indices = indices; + def.useMedianSplit = false; + def.identifyEdges = true; + + return b3CreateMesh( &def, NULL, 0 ); +} + +b3MeshData* b3CreatePlatformMesh( b3Vec3 center, float height, float topWidth, float bottomWidth ) +{ + float hb = 0.5f * bottomWidth; + float ht = 0.5f * topWidth; + float hy = 0.5f * height; + b3Vec3 vertices[] = { + { ht, hy, ht }, { -ht, hy, ht }, { -hb, -hy, hb }, { hb, -hy, hb }, + { ht, hy, -ht }, { -ht, hy, -ht }, { -hb, -hy, -hb }, { hb, -hy, -hb }, + }; + + for ( int i = 0; i < 8; ++i ) + { + vertices[i] = b3Add( vertices[i], center ); + } + + int indices[] = { + 0, 1, 3, 1, 2, 3, // front + 0, 4, 1, 1, 4, 5, // top + 0, 3, 7, 4, 0, 7, // right + 4, 7, 5, 6, 5, 7, // back + 1, 5, 2, 6, 2, 5, // left + 3, 2, 7, 6, 7, 2, // bottom + }; + + b3MeshDef def = { 0 }; + def.vertexCount = 8; + def.vertices = vertices; + def.triangleCount = 12; + def.indices = indices; + def.useMedianSplit = true; + def.identifyEdges = true; + + return b3CreateMesh( &def, NULL, 0 ); +} + +// todo this should fail if the mesh has a height greater than B3_MESH_STACK_SIZE +b3MeshData* b3CreateMesh( const b3MeshDef* def, int* degenerateTriangleIndices, int degenerateCapacity ) +{ + if ( def->vertexCount < 3 || def->vertices == NULL || def->triangleCount <= 0 || def->indices == NULL ) + { + return NULL; + } + + int triangleCount = def->triangleCount; + if ( triangleCount == 0 ) + { + return NULL; + } + + int vertexCount = def->vertexCount; + + b3AABB meshBounds = B3_BOUNDS3_EMPTY; + + // Clone indices and vertices to support welding + b3Array( int ) indices; + b3Array_CreateN( indices, 3 * triangleCount ); + + b3Array( b3Vec3 ) vertices; + b3Array_CreateN( vertices, vertexCount ); + + if ( def->weldVertices && def->weldTolerance > 0.0f ) + { + b3Array_Resize( vertices, vertexCount ); + b3Array_Resize( indices, 3 * triangleCount ); + b3WeldData data = { + .srcVertices = def->vertices, + .srcIndices = def->indices, + .dstVertices = vertices.data, + .dstIndices = indices.data, + .vertexCount = vertexCount, + .indexCount = 3 * triangleCount, + }; + vertices.count = b3WeldVertices( &data, def->weldTolerance ); + vertexCount = vertices.count; + B3_ASSERT( vertexCount <= def->vertexCount ); + } + else + { + b3Array_Append( vertices, def->vertices, vertexCount ); + b3Array_Append( indices, def->indices, 3 * triangleCount ); + } + + b3Array( b3Primitive ) primitives; + b3Array_CreateN( primitives, triangleCount ); + int degenerateCount = 0; + float minArea = 0.01f * B3_LINEAR_SLOP * B3_LINEAR_SLOP; + float surfaceArea = 0.0f; + int materialCount = 1; + + for ( int index = 0; index < triangleCount; ++index ) + { + int index1 = indices.data[3 * index + 0]; + int index2 = indices.data[3 * index + 1]; + int index3 = indices.data[3 * index + 2]; + + b3Vec3 vertex1 = vertices.data[index1]; + b3Vec3 vertex2 = vertices.data[index2]; + b3Vec3 vertex3 = vertices.data[index3]; + + b3Vec3 normal = b3Cross( b3Sub( vertex2, vertex1 ), b3Sub( vertex3, vertex1 ) ); + float area = 0.5f * b3Length( normal ); + + if ( area < minArea ) + { + // b3Log( "degenerate: %d %d %d\n", index1, index2, index3 ); + + if ( index1 != index2 && index1 != index3 && index2 != index3 ) + { + degenerateCount += 1; + if ( degenerateTriangleIndices != NULL && degenerateCount < degenerateCapacity ) + { + degenerateTriangleIndices[degenerateCount - 1] = index; + } + } + + continue; + } + + surfaceArea += area; + + b3AABB box = { + b3Min( vertex1, b3Min( vertex2, vertex3 ) ), + b3Max( vertex1, b3Max( vertex2, vertex3 ) ), + }; + + b3Vec3 center = b3AABB_Center( box ); + + b3Primitive primitive = { + .aabb = box, + .center = center, + .triangleIndex = index, + }; + b3Array_Push( primitives, primitive ); + + if ( def->materialIndices != NULL ) + { + materialCount = b3MaxInt( materialCount, def->materialIndices[index] + 1 ); + } + + meshBounds = b3AABB_Union( meshBounds, box ); + } + + // Update triangle count due to degenerates being skipped + triangleCount = primitives.count; + + if ( b3IsSaneAABB( meshBounds ) == false ) + { + b3Array_Destroy( primitives ); + return NULL; + } + + // Build the tree (this reorders the builder triangles) + b3Array( b3MeshNode ) tempNodes; + b3Array_CreateN( tempNodes, 2 * triangleCount - 1 ); + + int treeHeight = 0; + b3BuildRecursive( &tempNodes, triangleCount, primitives.data, primitives.data, def->useMedianSplit, &treeHeight ); + + // Allocate the mesh + size_t byteCount = b3AlignUp8( sizeof( b3MeshData ) ); + int nodeOffset = (int)byteCount; + byteCount += b3AlignUp8( tempNodes.count * sizeof( b3MeshNode ) ); + int vertexOffset = (int)byteCount; + byteCount += b3AlignUp8( vertexCount * sizeof( b3Vec3 ) ); + int triangleOffset = (int)byteCount; + byteCount += b3AlignUp8( triangleCount * sizeof( b3MeshTriangle ) ); + int materialIndicesOffset = (int)byteCount; + byteCount += b3AlignUp8( triangleCount * sizeof( uint8_t ) ); + int flagsOffset = (int)byteCount; + byteCount += b3AlignUp8( triangleCount * sizeof( uint8_t ) ); + + b3MeshData* mesh = b3Alloc( byteCount ); + + // zero initialize for determinism + memset( mesh, 0, byteCount ); + + mesh->version = B3_MESH_VERSION; + mesh->byteCount = (int)byteCount; + mesh->bounds = meshBounds; + mesh->surfaceArea = surfaceArea; + mesh->nodeCount = tempNodes.count; + mesh->treeHeight = treeHeight; + mesh->vertexCount = vertexCount; + mesh->triangleCount = triangleCount; + mesh->degenerateCount = degenerateCount; + mesh->nodeOffset = nodeOffset; + mesh->vertexOffset = vertexOffset; + mesh->triangleOffset = triangleOffset; + mesh->materialOffset = materialIndicesOffset; + mesh->materialCount = materialCount; + mesh->flagsOffset = flagsOffset; + + b3MeshNode* nodes = b3GetMeshNodesWrite( mesh ); + b3MeshTriangle* triangles = b3GetMeshTrianglesWrite( mesh ); + b3Vec3* meshVertices = b3GetMeshVerticesWrite( mesh ); + uint8_t* materialIndices = b3GetMeshMaterialIndicesWrite( mesh ); + uint8_t* flags = b3GetMeshFlagsWrite( mesh ); + + memcpy( nodes, tempNodes.data, tempNodes.count * sizeof( b3MeshNode ) ); + memcpy( meshVertices, vertices.data, vertexCount * sizeof( b3Vec3 ) ); + + for ( int index = 0; index < triangleCount; ++index ) + { + b3Primitive primitive = primitives.data[index]; + triangles[index].index1 = indices.data[3 * primitive.triangleIndex + 0]; + triangles[index].index2 = indices.data[3 * primitive.triangleIndex + 1]; + triangles[index].index3 = indices.data[3 * primitive.triangleIndex + 2]; + flags[index] = 0; + + // Copy material indices if they exist. Otherwise the material indices are all zeroes. + if ( def->materialIndices != NULL ) + { + uint8_t materialIndex = def->materialIndices[primitive.triangleIndex]; + materialIndices[index] = materialIndex; + } + } + + // Sort triangle in DFS order. Casts and volume queries will return sorted arrays. + // This also sorts material indices, but not the materials. + // This can fail if the BVH height is too large. + bool success = b3SortMeshTriangles( mesh ); + if ( success == false ) + { + b3Array_Destroy( tempNodes ); + b3Array_Destroy( primitives ); + return NULL; + } + + if ( def->identifyEdges ) + { + b3IdentifyEdges( mesh ); + } + + B3_VALIDATE( b3IsNonDegenerate( mesh, minArea ) ); + B3_VALIDATE( b3IsConsistent( mesh ) ); + + b3Array_Destroy( tempNodes ); + b3Array_Destroy( primitives ); + b3Array_Destroy( indices ); + b3Array_Destroy( vertices ); + + mesh->hash = 0; + mesh->hash = b3NonZeroHash( b3Hash( B3_HASH_INIT, (uint8_t*)mesh, mesh->byteCount ) ); + + return mesh; +} + +void b3DestroyMesh( b3MeshData* mesh ) +{ + b3Free( mesh, mesh->byteCount ); +} + +bool b3OverlapMesh( const b3Mesh* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy ) +{ + B3_ASSERT( proxy->count > 0 ); + b3SimplexCache cache = { 0 }; + + b3Vec3 buffer[B3_MAX_SHAPE_CAST_POINTS]; + b3ShapeProxy localProxy = b3MakeLocalProxy( proxy, shapeTransform, buffer ); + b3AABB aabb = b3ComputeProxyAABB( &localProxy ); + + b3Vec3 meshScale = shape->scale; + + // Scale may have reflection so min/max may become invalid when unscaled + b3V32 scale = b3LoadV( &meshScale.x ); + b3V32 invScale = b3DivV( b3_oneV, scale ); + b3V32 temp1 = b3MulV( invScale, b3LoadV( &aabb.lowerBound.x ) ); + b3V32 temp2 = b3MulV( invScale, b3LoadV( &aabb.upperBound.x ) ); + b3V32 invScaledBoundsMin = b3MinV( temp1, temp2 ); + b3V32 invScaledBoundsMax = b3MaxV( temp1, temp2 ); + b3V32 invScaledBoundsCenter = b3MulV( b3_halfV, b3AddV( invScaledBoundsMin, invScaledBoundsMax ) ); + b3V32 invScaledBoundsExtent = b3SubV( invScaledBoundsMax, invScaledBoundsCenter ); + + b3DistanceInput input; + input.proxyB = localProxy; + input.transform = b3Transform_identity; + input.useRadii = true; + + int count = 0; + const b3MeshNode* stack[B3_MESH_STACK_SIZE]; + const b3MeshNode* node = b3GetRoot( shape->data ); + const b3MeshTriangle* triangles = b3GetMeshTriangles( shape->data ); + const b3Vec3* vertices = b3GetMeshVertices( shape->data ); + + while ( true ) + { + // Test node overlap in unscaled space + b3V32 nodeMin = b3LoadV( &node->lowerBound.x ); + b3V32 nodeMax = b3LoadV( &node->upperBound.x ); + if ( b3TestBoundsOverlap( nodeMin, nodeMax, invScaledBoundsMin, invScaledBoundsMax ) ) + { + if ( b3IsLeaf( node ) ) + { + int triangleCount = node->data.asLeaf.triangleCount; + int triangleOffset = node->triangleOffset; + + for ( int index = 0; index < triangleCount; ++index ) + { + int triangleIndex = triangleOffset + index; + b3MeshTriangle triangle = triangles[triangleIndex]; + + b3Vec3 vertex1 = vertices[triangle.index1]; + b3Vec3 vertex2 = vertices[triangle.index2]; + b3Vec3 vertex3 = vertices[triangle.index3]; + b3V32 v1 = b3LoadV( &vertex1.x ); + b3V32 v2 = b3LoadV( &vertex2.x ); + b3V32 v3 = b3LoadV( &vertex3.x ); + + // Bounding box overlap test in unscaled space + if ( b3TestBoundsTriangleOverlap( invScaledBoundsCenter, invScaledBoundsExtent, v1, v2, v3 ) ) + { + // Shape-triangle overlap test in scaled space. Winding order doesn't matter. + b3Vec3 triangleVertices[] = { b3Mul( meshScale, vertex1 ), b3Mul( meshScale, vertex2 ), + b3Mul( meshScale, vertex3 ) }; + input.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + + // reset the cache + cache.count = 0; + + // get distance between triangle and query shape + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + + float tolerance = 0.1f * B3_LINEAR_SLOP; + if ( output.distance < tolerance ) + { + // overlap detected + return true; + } + } + } + } + else + { + // Recurse + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetRightChild( node ); + node = b3GetLeftChild( node ); + + continue; + } + } + + if ( count == 0 ) + { + break; + } + node = stack[--count]; + } + + return false; +} + +b3AABB b3ComputeMeshAABB( const b3MeshData* shape, b3Transform transform, b3Vec3 scale ) +{ + b3Vec3 scaledLower = b3Mul( scale, shape->bounds.lowerBound ); + b3Vec3 scaledUpper = b3Mul( scale, shape->bounds.upperBound ); + b3AABB bounds = { b3Min( scaledLower, scaledUpper ), b3Max( scaledLower, scaledUpper ) }; + return b3AABB_Transform( transform, bounds ); +} + +b3CastOutput b3RayCastMesh( const b3Mesh* mesh, const b3RayCastInput* input ) +{ + const b3MeshData* data = mesh->data; + b3Vec3 meshScale = mesh->scale; + + b3CastOutput bestOutput = { 0 }; + bestOutput.fraction = input->maxFraction; + bestOutput.triangleIndex = B3_NULL_INDEX; + + b3V32 lambda = b3SplatV( input->maxFraction ); + + b3V32 rayStart = b3LoadV( &input->origin.x ); + b3V32 rayDelta = b3LoadV( &input->translation.x ); + + b3V32 scale = b3LoadV( &meshScale.x ); + b3V32 invScale = b3DivV( b3_oneV, scale ); + bool clockwise = meshScale.x * meshScale.y * meshScale.z < 0.0f; + + // Use the inverse scaled ray for traversal of the BVH + b3V32 invScaledRayStart = b3MulV( invScale, rayStart ); + b3V32 invScaledRayDelta = b3MulV( invScale, rayDelta ); + b3V32 invScaledRayEnd = b3AddV( invScaledRayStart, b3MulV( lambda, invScaledRayDelta ) ); + b3V32 invScaledRayMin = b3MinV( invScaledRayStart, invScaledRayEnd ); + b3V32 invScaledRayMax = b3MaxV( invScaledRayStart, invScaledRayEnd ); + + int count = 0; + const b3MeshNode* stack[B3_MESH_STACK_SIZE]; + const b3MeshNode* node = b3GetRoot( data ); + const b3MeshTriangle* triangles = b3GetMeshTriangles( data ); + const b3Vec3* vertices = b3GetMeshVertices( data ); + const uint8_t* materialIndices = b3GetMeshMaterialIndices( data ); + + while ( true ) + { + // Test node/ray overlap using SAT + b3V32 nodeMin = b3LoadV( &node->lowerBound.x ); + b3V32 nodeMax = b3LoadV( &node->upperBound.x ); + if ( b3TestBoundsOverlap( nodeMin, nodeMax, invScaledRayMin, invScaledRayMax ) && + b3TestBoundsRayOverlap( nodeMin, nodeMax, invScaledRayStart, invScaledRayDelta ) ) + { + // SAT: The node and ray overlap - process leaf node or recurse + if ( b3IsLeaf( node ) ) + { + int triangleCount = node->data.asLeaf.triangleCount; + int triangleOffset = node->triangleOffset; + + for ( int index = 0; index < triangleCount; ++index ) + { + int triangleIndex = triangleOffset + index; + b3MeshTriangle triangle = triangles[triangleIndex]; + + // Collide ray with triangle in scaled space + b3Vec3 vertex1 = b3Mul( meshScale, vertices[triangle.index1] ); + b3Vec3 vertex2, vertex3; + + // The CPU should predict this branch + if ( clockwise ) + { + vertex2 = b3Mul( meshScale, vertices[triangle.index3] ); + vertex3 = b3Mul( meshScale, vertices[triangle.index2] ); + } + else + { + vertex2 = b3Mul( meshScale, vertices[triangle.index2] ); + vertex3 = b3Mul( meshScale, vertices[triangle.index3] ); + } + + // Collide ray with triangle in scaled space + b3V32 v1 = b3LoadV( &vertex1.x ); + b3V32 v2 = b3LoadV( &vertex2.x ); + b3V32 v3 = b3LoadV( &vertex3.x ); + + float alpha = b3IntersectRayTriangle( rayStart, rayDelta, v1, v2, v3 ); + B3_ASSERT( 0 <= alpha && alpha <= 1.0f ); + + if ( alpha < bestOutput.fraction ) + { + b3Vec3 edge1 = b3Sub( vertex2, vertex1 ); + b3Vec3 edge2 = b3Sub( vertex3, vertex1 ); + bestOutput.normal = b3Normalize( b3Cross( edge1, edge2 ) ); + bestOutput.point = b3Add( input->origin, b3MulSV( alpha, input->translation ) ); + bestOutput.fraction = alpha; + bestOutput.triangleIndex = triangleIndex; + bestOutput.materialIndex = materialIndices[triangleIndex]; + bestOutput.hit = true; + + // Update ray bounds in unscaled space + lambda = b3SplatV( alpha ); + invScaledRayEnd = b3AddV( invScaledRayStart, b3MulV( lambda, invScaledRayDelta ) ); + invScaledRayMin = b3MinV( invScaledRayStart, invScaledRayEnd ); + invScaledRayMax = b3MaxV( invScaledRayStart, invScaledRayEnd ); + } + } + } + else + { + // Determine traversal order (front -> back) and recurse + int axis = node->data.asNode.axis; + if ( b3GetV( invScaledRayDelta, axis ) > 0.0f ) + { + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetRightChild( node ); + node = b3GetLeftChild( node ); + } + else + { + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetLeftChild( node ); + node = b3GetRightChild( node ); + } + + continue; + } + } + + if ( count == 0 ) + { + break; + } + node = stack[--count]; + } + + return bestOutput; +} + +b3CastOutput b3ShapeCastMesh( const b3Mesh* mesh, const b3ShapeCastInput* input ) +{ + const b3MeshData* data = mesh->data; + b3Vec3 meshScale = mesh->scale; + + b3CastOutput bestOutput = { 0 }; + bestOutput.fraction = input->maxFraction; + bestOutput.triangleIndex = B3_NULL_INDEX; + + b3V32 lambda = b3SplatV( input->maxFraction ); + + b3AABB shapeBounds = b3MakeAABB( input->proxy.points, input->proxy.count, input->proxy.radius ); + b3Vec3 center = b3AABB_Center( shapeBounds ); + b3Vec3 extents = b3AABB_Extents( shapeBounds ); + b3V32 shapeExtent = b3LoadV( &extents.x ); + + b3V32 rayStart = b3LoadV( ¢er.x ); + b3V32 rayDelta = b3LoadV( &input->translation.x ); + b3V32 rayEnd = b3AddV( rayStart, b3MulV( lambda, rayDelta ) ); + b3V32 rayMin = b3MinV( rayStart, rayEnd ); + b3V32 rayMax = b3MaxV( rayStart, rayEnd ); + + b3V32 scale = b3LoadV( &meshScale.x ); + b3V32 invScale = b3DivV( b3_oneV, scale ); + b3V32 absInvScale = b3AbsV( invScale ); + bool clockwise = meshScale.x * meshScale.y * meshScale.z < 0.0f; + + // Use the inverse scaled shape cast for traversal of the BVH + b3V32 invScaledRayStart = b3MulV( invScale, rayStart ); + b3V32 invScaledRayDelta = b3MulV( invScale, rayDelta ); + b3V32 invScaledRayEnd = b3AddV( invScaledRayStart, b3MulV( lambda, invScaledRayDelta ) ); + b3V32 invScaledRayMin = b3MinV( invScaledRayStart, invScaledRayEnd ); + b3V32 invScaledRayMax = b3MaxV( invScaledRayStart, invScaledRayEnd ); + b3V32 invScaledShapeExtent = b3MulV( absInvScale, shapeExtent ); + + int count = 0; + const b3MeshNode* stack[B3_MESH_STACK_SIZE]; + const b3MeshNode* node = b3GetRoot( data ); + const b3MeshTriangle* triangles = b3GetMeshTriangles( data ); + const b3Vec3* vertices = b3GetMeshVertices( data ); + const uint8_t* materialIndices = b3GetMeshMaterialIndices( data ); + + while ( true ) + { + // Test node/ray overlap using SAT in unscaled space + b3V32 nodeMin = b3SubV( b3LoadV( &node->lowerBound.x ), invScaledShapeExtent ); + b3V32 nodeMax = b3AddV( b3LoadV( &node->upperBound.x ), invScaledShapeExtent ); + + if ( b3TestBoundsOverlap( nodeMin, nodeMax, invScaledRayMin, invScaledRayMax ) && + b3TestBoundsRayOverlap( nodeMin, nodeMax, invScaledRayStart, invScaledRayDelta ) ) + { + // SAT: The node and ray overlap - process leaf node or recurse + if ( b3IsLeaf( node ) ) + { + int triangleCount = node->data.asLeaf.triangleCount; + int triangleOffset = node->triangleOffset; + + for ( int index = 0; index < triangleCount; ++index ) + { + int triangleIndex = triangleOffset + index; + b3MeshTriangle triangle = triangles[triangleIndex]; + + // Collide ray with triangle in scaled space + b3Vec3 vertex1 = b3Mul( meshScale, vertices[triangle.index1] ); + b3Vec3 vertex2, vertex3; + + // The CPU should predict this branch + if ( clockwise ) + { + vertex2 = b3Mul( meshScale, vertices[triangle.index3] ); + vertex3 = b3Mul( meshScale, vertices[triangle.index2] ); + } + else + { + vertex2 = b3Mul( meshScale, vertices[triangle.index2] ); + vertex3 = b3Mul( meshScale, vertices[triangle.index3] ); + } + + b3V32 v1 = b3LoadV( &vertex1.x ); + b3V32 v2 = b3LoadV( &vertex2.x ); + b3V32 v3 = b3LoadV( &vertex3.x ); + + b3V32 triangleMin = b3SubV( b3MinV( v1, b3MinV( v2, v3 ) ), shapeExtent ); + b3V32 triangleMax = b3AddV( b3MaxV( v1, b3MaxV( v2, v3 ) ), shapeExtent ); + + // Test triangle-ray overlap in scaled space + if ( b3TestBoundsOverlap( triangleMin, triangleMax, rayMin, rayMax ) ) + { + // Collide shape with triangle in scaled space + b3Vec3 origin = vertex1; + b3Vec3 triangleVertices[] = { b3Vec3_zero, b3Sub( vertex2, origin ), b3Sub( vertex3, origin ) }; + b3Transform shiftedOrigin = { b3Neg( origin ), b3Quat_identity }; + + b3ShapeCastPairInput pairInput; + pairInput.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + pairInput.proxyB = input->proxy; + pairInput.transform = shiftedOrigin; + pairInput.maxFraction = bestOutput.fraction; + pairInput.translationB = input->translation; + pairInput.canEncroach = input->canEncroach; + + b3CastOutput pairOutput = b3ShapeCast( &pairInput ); + + if ( pairOutput.hit ) + { + pairOutput.point = b3Add( pairOutput.point, origin ); + + bestOutput = pairOutput; + bestOutput.triangleIndex = triangleIndex; + bestOutput.materialIndex = materialIndices[triangleIndex]; + + // Update ray bounds in scaled space + lambda = b3SplatV( pairOutput.fraction ); + rayEnd = b3AddV( rayStart, b3MulV( lambda, rayDelta ) ); + rayMin = b3MinV( rayStart, rayEnd ); + rayMax = b3MaxV( rayStart, rayEnd ); + + // Ray bounds in unscaled space + invScaledRayEnd = b3AddV( invScaledRayStart, b3MulV( lambda, invScaledRayDelta ) ); + invScaledRayMin = b3MinV( invScaledRayStart, invScaledRayEnd ); + invScaledRayMax = b3MaxV( invScaledRayStart, invScaledRayEnd ); + } + } + } + } + else + { + // Determine traversal order (front -> back) and recurse + int axis = node->data.asNode.axis; + if ( b3GetV( invScaledRayDelta, axis ) > 0.0f ) + { + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetRightChild( node ); + node = b3GetLeftChild( node ); + } + else + { + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetLeftChild( node ); + node = b3GetRightChild( node ); + } + + continue; + } + } + + if ( count == 0 ) + { + break; + } + node = stack[--count]; + } + + return bestOutput; +} + +b3Triangle b3GetMeshTriangle( const b3Mesh* mesh, int triangleIndex ) +{ + B3_ASSERT( 0 <= triangleIndex && triangleIndex < mesh->data->triangleCount ); + + const b3MeshTriangle* triangles = b3GetMeshTriangles( mesh->data ); + const uint8_t* flags = b3GetMeshFlags( mesh->data ); + const b3Vec3* vertices = b3GetMeshVertices( mesh->data ); + + b3Triangle result; + b3MeshTriangle triangle = triangles[triangleIndex]; + uint8_t triangleFlags = flags[triangleIndex]; + + b3Vec3 scale = mesh->scale; + + result.vertices[0] = b3Mul( scale, vertices[triangle.index1] ); + result.i1 = triangle.index1; + + if ( scale.x * scale.y * scale.z < 0.0f ) + { + result.vertices[1] = b3Mul( scale, vertices[triangle.index3] ); + result.vertices[2] = b3Mul( scale, vertices[triangle.index2] ); + + result.i2 = triangle.index3; + result.i3 = triangle.index2; + + // mesh is inverted, so concave edges are now convex + result.flags = 0; + result.flags |= ( triangleFlags & b3_inverseConcaveEdge1 ) ? b3_concaveEdge1 : 0; + result.flags |= ( triangleFlags & b3_inverseConcaveEdge2 ) ? b3_concaveEdge2 : 0; + result.flags |= ( triangleFlags & b3_inverseConcaveEdge3 ) ? b3_concaveEdge3 : 0; + } + else + { + result.vertices[1] = b3Mul( scale, vertices[triangle.index2] ); + result.vertices[2] = b3Mul( scale, vertices[triangle.index3] ); + + result.i2 = triangle.index2; + result.i3 = triangle.index3; + result.flags = triangleFlags; + } + + return result; +} + +int b3CollideMoverAndMesh( b3PlaneResult* planes, int capacity, const b3Mesh* shape, const b3Capsule* mover ) +{ + if ( capacity == 0 ) + { + return 0; + } + + b3DistanceInput distanceInput = { 0 }; + distanceInput.proxyB = (b3ShapeProxy){ &mover->center1, 2, 0.0f }; + distanceInput.transform = b3Transform_identity; + distanceInput.useRadii = false; + + b3SimplexCache cache = { 0 }; + float radius = mover->radius; + + b3V32 center1 = b3LoadV( &mover->center1.x ); + b3V32 center2 = b3LoadV( &mover->center2.x ); + b3V32 r = b3SplatV( radius ); + b3V32 boundsMin = b3SubV( b3MinV( center1, center2 ), r ); + b3V32 boundsMax = b3AddV( b3MaxV( center1, center2 ), r ); + + // Scale may have reflection so min/max may become invalid when unscaled + b3Vec3 meshScale = shape->scale; + b3V32 scale = b3LoadV( &meshScale.x ); + b3V32 invScale = b3DivV( b3_oneV, scale ); + b3V32 temp1 = b3MulV( invScale, boundsMin ); + b3V32 temp2 = b3MulV( invScale, boundsMax ); + b3V32 invScaledBoundsMin = b3MinV( temp1, temp2 ); + b3V32 invScaledBoundsMax = b3MaxV( temp1, temp2 ); + b3V32 invScaledBoundsCenter = b3MulV( b3_halfV, b3AddV( invScaledBoundsMin, invScaledBoundsMax ) ); + b3V32 invScaledBoundsExtent = b3SubV( invScaledBoundsMax, invScaledBoundsCenter ); + + int count = 0; + const b3MeshNode* stack[B3_MESH_STACK_SIZE]; + const b3MeshNode* node = b3GetRoot( shape->data ); + const b3MeshTriangle* triangles = b3GetMeshTriangles( shape->data ); + const b3Vec3* vertices = b3GetMeshVertices( shape->data ); + + int planeCount = 0; + while ( planeCount < capacity ) + { + // Test node overlap in unscaled space + b3V32 nodeMin = b3LoadV( &node->lowerBound.x ); + b3V32 nodeMax = b3LoadV( &node->upperBound.x ); + if ( b3TestBoundsOverlap( nodeMin, nodeMax, invScaledBoundsMin, invScaledBoundsMax ) ) + { + if ( b3IsLeaf( node ) ) + { + int triangleCount = node->data.asLeaf.triangleCount; + int triangleOffset = node->triangleOffset; + + for ( int index = 0; index < triangleCount; ++index ) + { + int triangleIndex = triangleOffset + index; + b3MeshTriangle triangle = triangles[triangleIndex]; + + b3Vec3 vertex1 = vertices[triangle.index1]; + b3Vec3 vertex2 = vertices[triangle.index2]; + b3Vec3 vertex3 = vertices[triangle.index3]; + b3V32 v1 = b3LoadV( &vertex1.x ); + b3V32 v2 = b3LoadV( &vertex2.x ); + b3V32 v3 = b3LoadV( &vertex3.x ); + + // Test triangle bounds overlap in unscaled space + if ( b3TestBoundsTriangleOverlap( invScaledBoundsCenter, invScaledBoundsExtent, v1, v2, v3 ) ) + { + // Compute shape distance in scaled space. Winding order doesn't matter. + // todo implement one-sided collision? + b3Vec3 triangleVertices[] = { b3Mul( meshScale, vertex1 ), b3Mul( meshScale, vertex2 ), + b3Mul( meshScale, vertex3 ) }; + distanceInput.proxyA = (b3ShapeProxy){ triangleVertices, 3, 0.0f }; + + // reset the cache + cache.count = 0; + + // get distance between triangle and query shape + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, &cache, NULL, 0 ); + + if ( distanceOutput.distance == 0.0f ) + { + // todo SAT + } + else if ( distanceOutput.distance <= mover->radius ) + { + b3Plane plane = { distanceOutput.normal, mover->radius - distanceOutput.distance }; + planes[planeCount] = (b3PlaneResult){ plane, distanceOutput.pointA }; + planeCount += 1; + + if ( planeCount == capacity ) + { + return planeCount; + } + } + } + } + } + else + { + // Recurse + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetRightChild( node ); + node = b3GetLeftChild( node ); + + continue; + } + } + + if ( count == 0 ) + { + break; + } + node = stack[--count]; + } + + return planeCount; +} + +void b3QueryMesh( const b3Mesh* mesh, b3AABB bounds, b3MeshQueryFcn* fcn, void* context ) +{ + b3Vec3 meshScale = mesh->scale; + bool clockwise = meshScale.x * meshScale.y * meshScale.z > 0.0f; + + // Scale may have reflection so min/max may become invalid when unscaled + b3V32 scale = b3LoadV( &meshScale.x ); + b3V32 invScale = b3DivV( b3_oneV, scale ); + b3V32 temp1 = b3MulV( invScale, b3LoadV( &bounds.lowerBound.x ) ); + b3V32 temp2 = b3MulV( invScale, b3LoadV( &bounds.upperBound.x ) ); + b3V32 invScaledBoundsMin = b3MinV( temp1, temp2 ); + b3V32 invScaledBoundsMax = b3MaxV( temp1, temp2 ); + b3V32 invScaledBoundsCenter = b3MulV( b3_halfV, b3AddV( invScaledBoundsMin, invScaledBoundsMax ) ); + b3V32 invScaledBoundsExtent = b3SubV( invScaledBoundsMax, invScaledBoundsCenter ); + + const b3MeshData* data = mesh->data; + + int count = 0; + const b3MeshNode* stack[B3_MESH_STACK_SIZE]; + const b3MeshNode* node = b3GetRoot( data ); + const b3MeshTriangle* triangles = b3GetMeshTriangles( data ); + const b3Vec3* vertices = b3GetMeshVertices( data ); + + while ( true ) + { + // Test node overlap in unscaled space + b3V32 nodeMin = b3LoadV( &node->lowerBound.x ); + b3V32 nodeMax = b3LoadV( &node->upperBound.x ); + + if ( b3TestBoundsOverlap( nodeMin, nodeMax, invScaledBoundsMin, invScaledBoundsMax ) ) + { + if ( b3IsLeaf( node ) ) + { + int triangleCount = node->data.asLeaf.triangleCount; + int triangleOffset = node->triangleOffset; + + for ( int index = 0; index < triangleCount; ++index ) + { + int triangleIndex = triangleOffset + index; + b3MeshTriangle triangle = triangles[triangleIndex]; + + b3Vec3 vertex1 = vertices[triangle.index1]; + b3Vec3 vertex2 = vertices[triangle.index2]; + b3Vec3 vertex3 = vertices[triangle.index3]; + b3V32 v1 = b3LoadV( &vertex1.x ); + b3V32 v2 = b3LoadV( &vertex2.x ); + b3V32 v3 = b3LoadV( &vertex3.x ); + + // Perform triangle overlap test in unscaled space. Winding order doesn't matter. + // todo it is possible that some margins are getting scaled + if ( b3TestBoundsTriangleOverlap( invScaledBoundsCenter, invScaledBoundsExtent, v1, v2, v3 ) ) + { + b3Vec3 a = b3Mul( meshScale, vertex1 ); + b3Vec3 b, c; + if ( clockwise ) + { + b = b3Mul( meshScale, vertex2 ); + c = b3Mul( meshScale, vertex3 ); + } + else + { + b = b3Mul( meshScale, vertex3 ); + c = b3Mul( meshScale, vertex2 ); + } + + bool result = fcn( a, b, c, triangleIndex, context ); + if ( result == false ) + { + return; + } + } + } + } + else + { + // Recurse + B3_ASSERT( count <= B3_MESH_STACK_SIZE - 1 ); + stack[count++] = b3GetRightChild( node ); + node = b3GetLeftChild( node ); + + continue; + } + } + + if ( count == 0 ) + { + break; + } + node = stack[--count]; + } +} diff --git a/vendor/box3d/src/src/mesh_contact.c b/vendor/box3d/src/src/mesh_contact.c new file mode 100644 index 000000000..a7163477a --- /dev/null +++ b/vendor/box3d/src/src/mesh_contact.c @@ -0,0 +1,1186 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "contact.h" +#include "manifold.h" +#include "physics_world.h" +#include "qsort.h" +#include "shape.h" + +#include "box3d/types.h" + +#include + +// This guards against excessive memory usage and complex collision +#define B3_MAX_MESH_CONTACT_TRIANGLES 256 +#define B3_MAX_POINTS_PER_TRIANGLE 32 + +#if B3_ENABLE_VALIDATION +static bool b3IsSorted( const int* array, int count ) +{ + for ( int i = 0; i < count - 1; ++i ) + { + if ( array[i] >= array[i + 1] ) + { + return false; + } + } + + return true; +} +#endif + +typedef struct b3TriangleQueryContext +{ + int* indices; + int capacity; + int count; +} b3TriangleQueryContext; + +static bool b3CollectTriangleIndicesCallback( b3Vec3 a, b3Vec3 b, b3Vec3 c, int triangleIndex, void* context ) +{ + B3_UNUSED( a, b, c ); + b3TriangleQueryContext* triangleContext = (b3TriangleQueryContext*)context; + if ( triangleContext->count == triangleContext->capacity ) + { + return false; + } + triangleContext->indices[triangleContext->count] = triangleIndex; + triangleContext->count += 1; + return triangleContext->count < triangleContext->capacity; +} + +static int b3QueryMeshTriangles( int* indices, int capacity, const b3Mesh* mesh, b3AABB bounds ) +{ + b3TriangleQueryContext context = { + .indices = indices, + .capacity = capacity, + .count = 0, + }; + + b3QueryMesh( mesh, bounds, b3CollectTriangleIndicesCallback, &context ); + return context.count; +} + +static int b3QueryHeightFieldTriangles( int* indices, int capacity, const b3HeightFieldData* heightField, b3AABB bounds ) +{ + b3TriangleQueryContext context = { + .indices = indices, + .capacity = capacity, + .count = 0, + }; + + b3QueryHeightField( heightField, bounds, b3CollectTriangleIndicesCallback, &context ); + return context.count; +} + +static void b3RefreshCache( b3Contact* contact, const b3Shape* shapeA, b3WorldTransform xfA, const b3AABB* bounds ) +{ + B3_ASSERT( shapeA->type == b3_meshShape || shapeA->type == b3_heightShape ); + + b3MeshContact* meshContact = &contact->meshContact; + + // If the dynamic body didn't move out of the cached query bounds we are done! + if ( b3AABB_Contains( meshContact->queryBounds, *bounds ) ) + { + if ( shapeA->type == b3_meshShape ) + { + for ( int i = 0; i < contact->meshContact.triangleCache.count; ++i ) + { + B3_ASSERT( 0 <= contact->meshContact.triangleCache.data[i].triangleIndex && + contact->meshContact.triangleCache.data[i].triangleIndex < shapeA->mesh.data->triangleCount ); + } + } + + return; + } + + // Enlarge to the query bounds to absorb small movement + float radius = B3_MAX_AABB_MARGIN + B3_SPECULATIVE_DISTANCE; + b3Vec3 extension = { radius, radius, radius }; + meshContact->queryBounds.lowerBound = b3Sub( bounds->lowerBound, extension ); + meshContact->queryBounds.upperBound = b3Add( bounds->upperBound, extension ); + + // Query triangles + int triangleCapacity = B3_MAX_MESH_CONTACT_TRIANGLES; + + int triangleIndices[B3_MAX_MESH_CONTACT_TRIANGLES]; + + // Bounds are in world space. Convert to the local mesh frame. The broadphase bounds are float, + // so the demoted mesh transform is the matching float world frame (exact in float mode). + b3Transform meshTransform = b3ToRelativeTransform( xfA, b3Pos_zero ); + b3AABB localBounds = b3AABB_Transform( b3InvertTransform( meshTransform ), meshContact->queryBounds ); + int triangleCount; + if ( shapeA->type == b3_meshShape ) + { + triangleCount = b3QueryMeshTriangles( triangleIndices, triangleCapacity, &shapeA->mesh, localBounds ); + } + else + { + B3_ASSERT( shapeA->type == b3_heightShape ); + triangleCount = b3QueryHeightFieldTriangles( triangleIndices, triangleCapacity, shapeA->heightField, localBounds ); + } + + if ( triangleCount == triangleCapacity ) + { + static bool s_once = false; + if ( s_once == false ) + { + b3Log( "WARNING: complex mesh detected, triangle buffer capacity of %d reached", triangleCapacity ); + s_once = true; + } + } + + // Triangle indices must be sorted to match caches. + B3_VALIDATE( b3IsSorted( triangleIndices, triangleCount ) ); + + // Create new contact cache and match with old one + b3ContactCache contactCache[B3_MAX_MESH_CONTACT_TRIANGLES]; + + int index2 = 0; + for ( int index1 = 0; index1 < triangleCount; ++index1 ) + { + contactCache[index1] = (b3ContactCache){ 0 }; + + while ( index2 < contact->meshContact.triangleCache.count && + contact->meshContact.triangleCache.data[index2].triangleIndex < triangleIndices[index1] ) + { + index2 += 1; + } + + if ( index2 < contact->meshContact.triangleCache.count && + contact->meshContact.triangleCache.data[index2].triangleIndex == triangleIndices[index1] ) + { + contactCache[index1] = contact->meshContact.triangleCache.data[index2].cache; + } + } + + // Save new cache + b3Array_Resize( contact->meshContact.triangleCache, triangleCount ); + for ( int i = 0; i < triangleCount; ++i ) + { + contact->meshContact.triangleCache.data[i] = (b3TriangleCache){ triangleIndices[i], contactCache[i] }; + + if ( shapeA->type == b3_meshShape ) + { + B3_ASSERT( 0 <= contact->meshContact.triangleCache.data[i].triangleIndex && + contact->meshContact.triangleCache.data[i].triangleIndex < shapeA->mesh.data->triangleCount ); + } + } +} + +typedef struct b3TentativeTriangle +{ + float squaredDistance; + int index; +} b3TentativeTriangle; + +#define B3_MAX_EDGE_COUNT 64 + +typedef struct b3FoundEdges +{ + uint64_t keys[B3_MAX_EDGE_COUNT]; + int count; +} b3FoundEdges; + +static inline bool b3AddEdge( b3FoundEdges* edges, int vertex1, int vertex2 ) +{ + uint64_t i1 = (uint64_t)b3MinInt( vertex1, vertex2 ); + uint64_t i2 = (uint64_t)b3MaxInt( vertex1, vertex2 ); + uint64_t key = i1 << 32 | i2; + + int count = edges->count; + for ( int i = 0; i < count; ++i ) + { + if ( edges->keys[i] == key ) + { + return false; + } + } + + if ( count == B3_MAX_EDGE_COUNT ) + { + // This will lead to a potential ghost collision + return true; + } + + edges->keys[count] = key; + edges->count += 1; + + return true; +} + +static inline bool b3FindEdge( b3FoundEdges* edges, int vertex1, int vertex2 ) +{ + uint64_t i1 = (uint64_t)b3MinInt( vertex1, vertex2 ); + uint64_t i2 = (uint64_t)b3MaxInt( vertex1, vertex2 ); + uint64_t key = i1 << 32 | i2; + + int count = edges->count; + for ( int i = 0; i < count; ++i ) + { + if ( edges->keys[i] == key ) + { + return true; + } + } + + return false; +} + +#if 0 +// Two triangles share an edge iff they share at least two vertex indices. +static inline bool b3TrianglesShareEdge( int a1, int a2, int a3, int b1, int b2, int b3 ) +{ + int matches = 0; + matches += ( a1 == b1 || a1 == b2 || a1 == b3 ); + matches += ( a2 == b1 || a2 == b2 || a2 == b3 ); + matches += ( a3 == b1 || a3 == b2 || a3 == b3 ); + return matches >= 2; +} +#endif + +#define B3_MAX_VERTEX_COUNT 64 + +typedef struct b3FoundVertices +{ + int keys[B3_MAX_VERTEX_COUNT]; + int count; +} b3FoundVertices; + +static inline bool b3AddVertex( b3FoundVertices* vertices, int vertex ) +{ + int key = vertex; + + int count = vertices->count; + for ( int i = 0; i < count; ++i ) + { + if ( vertices->keys[i] == key ) + { + return false; + } + } + + if ( count == B3_MAX_VERTEX_COUNT ) + { + // This will lead to a potential ghost collision + return true; + } + + vertices->keys[count] = key; + vertices->count += 1; + + return true; +} + +// Returns true if (score, separation) should replace (bestScore, bestSeparation). +static inline bool b3IsBetterCullCandidate( float score, float separation, float bestScore, float bestSeparation, float scoreTol, + float separationTol ) +{ + if ( score > bestScore + scoreTol ) + { + return true; + } + if ( score < bestScore - scoreTol ) + { + return false; + } + + // Break the tie using separation + return separation < bestSeparation - separationTol; +} + +typedef struct b3Point2D +{ + b3Vec2 p; + float separation; + int originalIndex; +} b3Point2D; + +static int b3CullPoints( b3Point2D* points, int count ) +{ + if ( count <= 1 ) + { + return count; + } + + float tol = 0.25f * B3_LINEAR_SLOP; + float tolSqr = tol * tol; + float separationTol = B3_LINEAR_SLOP; + + b3Point2D finalPoints[4]; + int count1 = count; + + // Step 1: the two points with the largest distance, ties broken by deepest combined separation + float bestScore = 0.0f; + float bestSeparation = FLT_MAX; + int bestIndex1 = B3_NULL_INDEX; + int bestIndex2 = B3_NULL_INDEX; + + for ( int i = 0; i < count1; ++i ) + { + b3Vec2 p1 = points[i].p; + for ( int j = i + 1; j < count1; ++j ) + { + float score = b3DistanceSquared2( p1, points[j].p ); + // Separation sum heuristic + float separation = points[i].separation + points[j].separation; + + if ( b3IsBetterCullCandidate( score, separation, bestScore, bestSeparation, tolSqr, separationTol ) ) + { + bestIndex1 = i; + bestIndex2 = j; + bestScore = score; + bestSeparation = separation; + } + } + } + + if ( bestScore < tolSqr ) + { + // Choose deepest point + int deepestIndex = 0; + for ( int i = 1; i < count1; ++i ) + { + if ( points[i].separation < points[deepestIndex].separation ) + { + deepestIndex = i; + } + } + + if ( deepestIndex != 0 ) + { + points[0] = points[deepestIndex]; + } + return 1; + } + + finalPoints[0] = points[bestIndex1]; + finalPoints[1] = points[bestIndex2]; + + // Cull + points[bestIndex2] = points[count1 - 1]; + points[bestIndex1] = points[count1 - 2]; + count1 -= 2; + + if ( count1 == 0 ) + { + points[0] = finalPoints[0]; + points[1] = finalPoints[1]; + return 2; + } + + // First anchor point + b3Vec2 a = finalPoints[0].p; + + // Second anchor point + b3Vec2 b = finalPoints[1].p; + b3Vec2 ba = b3Sub2( b, a ); + // float length = b3Length2( ba ); + // float areaTol = tol * length; + + // Step 2: find the point with the maximum triangular area, ties broken by deepest separation + bestScore = 0.0f; + bestSeparation = FLT_MAX; + int bestIndex = B3_NULL_INDEX; + float bestSignedArea = 0.0f; + for ( int i = 0; i < count1; ++i ) + { + b3Vec2 p = points[i].p; + float signedArea = b3Cross2( ba, b3Sub2( p, a ) ); + float score = b3AbsFloat( signedArea ); + + if ( b3IsBetterCullCandidate( score, points[i].separation, bestScore, bestSeparation, tolSqr, separationTol ) ) + { + bestSignedArea = signedArea; + bestScore = score; + bestSeparation = points[i].separation; + bestIndex = i; + } + } + + if ( bestIndex == B3_NULL_INDEX ) + { + // All points collinear + points[0] = finalPoints[0]; + points[1] = finalPoints[1]; + return 2; + } + + // Store best point + finalPoints[2] = points[bestIndex]; + + if ( count1 == 1 ) + { + points[0] = finalPoints[0]; + points[1] = finalPoints[1]; + points[2] = finalPoints[2]; + return 3; + } + + // Cull + points[bestIndex] = points[count1 - 1]; + count1 -= 1; + + // Step 4: get the point that adds the most area outside the current triangle + + // Third anchor + b3Vec2 c = finalPoints[2].p; + + // Ensure CCW ordering + if ( bestSignedArea < 0.0f ) + { + B3_SWAP( b, c ); + ba = b3Sub2( b, a ); + } + + b3Vec2 cb = b3Sub2( c, b ); + b3Vec2 ac = b3Sub2( a, c ); + + bestScore = 0.0f; + bestSeparation = FLT_MAX; + bestIndex = B3_NULL_INDEX; + for ( int i = 0; i < count1; ++i ) + { + b3Vec2 p = points[i].p; + float u1 = b3Cross2( b3Sub2( p, a ), ba ); + float u2 = b3Cross2( b3Sub2( p, b ), cb ); + float u3 = b3Cross2( b3Sub2( p, c ), ac ); + float score = b3MaxFloat( u1, b3MaxFloat( u2, u3 ) ); + + // Use the area tolerance for collinear points and hysteresis + if ( b3IsBetterCullCandidate( score, points[i].separation, bestScore, bestSeparation, tolSqr, separationTol ) ) + { + bestScore = score; + bestSeparation = points[i].separation; + bestIndex = i; + } + } + + if ( bestIndex == B3_NULL_INDEX ) + { + // No additional area + points[0] = finalPoints[0]; + points[1] = finalPoints[1]; + points[2] = finalPoints[2]; + return 3; + } + + // Store best point + finalPoints[3] = points[bestIndex]; + + // Full quad + points[0] = finalPoints[0]; + points[1] = finalPoints[1]; + points[2] = finalPoints[2]; + points[3] = finalPoints[3]; + return 4; +} + +static int b3ReduceCluster( b3LocalManifoldPoint* points, int count1, b3Vec3 normal, b3Arena arena ) +{ + int targetCount = 1; + if ( count1 <= targetCount ) + { + return count1; + } + + b3Point2D* pts = b3Bump( &arena, count1 * sizeof( b3Point2D ) ); + b3Vec3 u = b3Perp( normal ); + b3Vec3 v = b3Cross( normal, u ); + b3Vec3 origin = points[0].point; + + for ( int i = 0; i < count1; ++i ) + { + b3Vec3 d = b3Sub( points[i].point, origin ); + pts[i].p = (b3Vec2){ b3Dot( d, u ), b3Dot( d, v ) }; + pts[i].separation = points[i].separation; + pts[i].originalIndex = i; + } + + int count2 = b3CullPoints( pts, count1 ); + B3_ASSERT( count2 <= B3_MAX_MANIFOLD_POINTS ); + + b3LocalManifoldPoint finalPoints[B3_MAX_MANIFOLD_POINTS]; + for ( int i = 0; i < count2; ++i ) + { + int index = pts[i].originalIndex; + B3_ASSERT( 0 <= index && index < count1 ); + finalPoints[i] = points[index]; + } + + memcpy( points, finalPoints, count2 * sizeof( b3LocalManifoldPoint ) ); + return count2; +} + +typedef struct b3Cluster +{ + b3Vec3 manifoldNormal; + b3Vec3 triangleNormal; + b3LocalManifoldPoint* points; + int pointCapacity; + int pointCount; +} b3Cluster; + +bool b3ComputeMeshManifolds( b3World* world, int workerIndex, b3Contact* contact, const b3Shape* shapeA, const int* materialMap, + b3WorldTransform xfA, const b3Shape* shapeB, b3WorldTransform xfB, bool isFast, b3Arena arena ) +{ + B3_ASSERT( shapeA->type == b3_meshShape || shapeA->type == b3_heightShape ); + B3_UNUSED( workerIndex ); + B3_UNUSED( isFast ); + B3_UNUSED( materialMap ); + + b3TaskContext* context = b3Array_Get( world->taskContexts, workerIndex ); + + b3RefreshCache( contact, shapeA, xfA, &shapeB->aabb ); + + // Collide with triangles and build manifolds + b3MeshContact* meshContact = &contact->meshContact; + int triangleCount = meshContact->triangleCache.count; + + b3LocalManifold** acceptedManifolds = b3Bump( &arena, triangleCount * sizeof( b3LocalManifold* ) ); + int acceptedManifoldCount = 0; + b3LocalManifold** tentativeManifolds = b3Bump( &arena, triangleCount * sizeof( b3LocalManifold* ) ); + int tentativeManifoldCount = 0; + b3TentativeTriangle* tentativeTriangles = b3Bump( &arena, triangleCount * sizeof( b3TentativeTriangle ) ); + int tentativeTriangleCount = 0; + + b3FoundEdges foundEdges; + b3FoundVertices foundVertices; + foundEdges.count = 0; + foundVertices.count = 0; + + // This transform converts from mesh frame into the shapeB frame + b3Transform transformAtoB = b3InvMulWorldTransforms( xfB, xfA ); + b3Matrix3 relativeMatrix = b3MakeMatrixFromQuat( transformAtoB.q ); + float linearSlop = B3_LINEAR_SLOP; + + // This should push apart shapes after a time of impact event. + // In the past I've called this `polygon skin`, but PhysX and Unreal + // call it `rest offset` which seems appropriate in this case. + // It leads to a small visual gap but seems to improve the quality of mesh + // collision, especially for hull versus mesh. + float restOffset = B3_MESH_REST_OFFSET; + bool enableSpeculative = contact->flags & b3_enableSpeculativePoints; + + // Make room for clip points + int pointBufferCapacity = B3_MAX_POINTS_PER_TRIANGLE * triangleCount; + + b3LocalManifoldPoint* pointBuffer = b3Bump( &arena, pointBufferCapacity * sizeof( b3LocalManifoldPoint ) ); + int totalPointCount = 0; + + b3LocalManifold* manifoldBuffer = b3Bump( &arena, triangleCount * sizeof( b3LocalManifold ) ); + int manifoldCount = 0; + + b3TriangleCache* triangleCaches = meshContact->triangleCache.data; + + const b3HullData* hullB = shapeB->type == b3_hullShape ? shapeB->hull : NULL; + + for ( int index = 0; index < triangleCount && totalPointCount + 3 < pointBufferCapacity; ++index ) + { + int triangleIndex = triangleCaches[index].triangleIndex; + + b3Triangle triangle; + if ( shapeA->type == b3_meshShape ) + { + triangle = b3GetMeshTriangle( &shapeA->mesh, triangleIndex ); + } + else + { + B3_ASSERT( shapeA->type == b3_heightShape ); + triangle = b3GetHeightFieldTriangle( shapeA->heightField, triangleIndex ); + } + + // Transform triangle into the shape frame + b3Vec3 vertices[3]; + vertices[0] = b3Add( b3MulMV( relativeMatrix, triangle.vertices[0] ), transformAtoB.p ); + vertices[1] = b3Add( b3MulMV( relativeMatrix, triangle.vertices[1] ), transformAtoB.p ); + vertices[2] = b3Add( b3MulMV( relativeMatrix, triangle.vertices[2] ), transformAtoB.p ); + + b3ContactCache* cache = &triangleCaches[index].cache; + int pointCapacity = pointBufferCapacity - totalPointCount; + b3LocalManifold* manifold = manifoldBuffer + manifoldCount; + manifold->points = pointBuffer + totalPointCount; + manifold->pointCount = 0; + manifold->triangleFlags = triangle.flags; + manifold->feature = b3_featureNone; + + switch ( shapeB->type ) + { + case b3_capsuleShape: + b3CollideCapsuleAndTriangle( manifold, pointCapacity, &shapeB->capsule, vertices, &cache->simplexCache ); + break; + + case b3_hullShape: + // Cached edge contact is dangerous at high speed because the hull can rotate around the edge and tunnel + // through the triangle. + if ( isFast && cache->satCache.type == b3_edgePairAxis ) + { + cache->satCache = (b3SATCache){ 0 }; + } + + b3CollideHullAndTriangle( manifold, pointCapacity, hullB, vertices[0], vertices[1], vertices[2], triangle.flags, + &cache->satCache, enableSpeculative ); + context->satCallCount += 1; + context->satCacheHitCount += cache->satCache.hit; + break; + + case b3_sphereShape: + b3CollideSphereAndTriangle( manifold, pointCapacity, &shapeB->sphere, vertices ); + break; + + default: + B3_ASSERT( false ); + return false; + } + + int manifoldPointCount = manifold->pointCount; + + if ( manifoldPointCount > 0 ) + { + B3_ASSERT( manifold->feature != b3_featureNone ); + + manifoldCount += 1; + totalPointCount += manifoldPointCount; + manifold->triangleIndex = triangleIndex; + manifold->triangleNormal = b3MakeNormalFromPoints( vertices[0], vertices[1], vertices[2] ); + manifold->i1 = triangle.i1; + manifold->i2 = triangle.i2; + manifold->i3 = triangle.i3; + + if ( manifold->feature == b3_featureTriangleFace || B3_FORCE_GHOST_COLLISIONS ) + { + (void)b3AddEdge( &foundEdges, triangle.i1, triangle.i2 ); + (void)b3AddEdge( &foundEdges, triangle.i2, triangle.i3 ); + (void)b3AddEdge( &foundEdges, triangle.i3, triangle.i1 ); + (void)b3AddVertex( &foundVertices, triangle.i1 ); + (void)b3AddVertex( &foundVertices, triangle.i2 ); + (void)b3AddVertex( &foundVertices, triangle.i3 ); + + acceptedManifolds[acceptedManifoldCount++] = manifold; + } + else if ( manifold->feature == b3_featureHullFace ) + { + float cosNormalAngle = b3Dot( manifold->triangleNormal, manifold->normal ); + if ( cosNormalAngle > 0.5f ) + { + (void)b3AddEdge( &foundEdges, triangle.i1, triangle.i2 ); + (void)b3AddEdge( &foundEdges, triangle.i2, triangle.i3 ); + (void)b3AddEdge( &foundEdges, triangle.i3, triangle.i1 ); + (void)b3AddVertex( &foundVertices, triangle.i1 ); + (void)b3AddVertex( &foundVertices, triangle.i2 ); + (void)b3AddVertex( &foundVertices, triangle.i3 ); + + acceptedManifolds[acceptedManifoldCount++] = manifold; + } + else + { + float minSeparation = manifold->points[0].separation; + for ( int i = 1; i < manifoldPointCount; ++i ) + { + minSeparation = b3MinFloat( minSeparation, manifold->points[i].separation ); + } + + if ( minSeparation < -2.0f * linearSlop ) + { + // Deep overlap + (void)b3AddEdge( &foundEdges, triangle.i1, triangle.i2 ); + (void)b3AddEdge( &foundEdges, triangle.i2, triangle.i3 ); + (void)b3AddEdge( &foundEdges, triangle.i3, triangle.i1 ); + (void)b3AddVertex( &foundVertices, triangle.i1 ); + (void)b3AddVertex( &foundVertices, triangle.i2 ); + (void)b3AddVertex( &foundVertices, triangle.i3 ); + acceptedManifolds[acceptedManifoldCount++] = manifold; + } + else + { + b3TentativeTriangle tentativeTriangle = { .squaredDistance = manifold->squaredDistance, + .index = tentativeManifoldCount }; + tentativeTriangles[tentativeTriangleCount++] = tentativeTriangle; + tentativeManifolds[tentativeManifoldCount++] = manifold; + } + } + } + else + { + b3TentativeTriangle tentativeTriangle = { .squaredDistance = manifold->squaredDistance, + .index = tentativeManifoldCount }; + tentativeTriangles[tentativeTriangleCount++] = tentativeTriangle; + tentativeManifolds[tentativeManifoldCount++] = manifold; + } + } + } + + B3_ASSERT( acceptedManifoldCount <= triangleCount ); + B3_ASSERT( tentativeManifoldCount <= triangleCount ); + B3_ASSERT( tentativeTriangleCount <= triangleCount ); + + if ( shapeB->type == b3_sphereShape ) + { + // Sort triangles so the closest triangles are processed first + { +#define LESS( i, j ) tentativeTriangles[(int)i].squaredDistance < tentativeTriangles[(int)j].squaredDistance +#define SWAP( i, j ) \ + do \ + { \ + b3TentativeTriangle tmp = tentativeTriangles[(int)i]; \ + tentativeTriangles[(int)i] = tentativeTriangles[(int)j]; \ + tentativeTriangles[(int)j] = tmp; \ + } \ + while ( 0 ) + QSORT( tentativeTriangleCount, LESS, SWAP ); +#undef LESS +#undef SWAP + } + + // Add tentative manifolds in sorted order. Avoid adding manifolds that generate ghost collisions. + for ( int i = 0; i < tentativeTriangleCount; ++i ) + { + b3LocalManifold* m = tentativeManifolds[tentativeTriangles[i].index]; + + bool addedEdge1 = b3AddEdge( &foundEdges, m->i1, m->i2 ); + bool addedEdge2 = b3AddEdge( &foundEdges, m->i2, m->i3 ); + bool addedEdge3 = b3AddEdge( &foundEdges, m->i3, m->i1 ); + bool addedVertex1 = b3AddVertex( &foundVertices, m->i1 ); + bool addedVertex2 = b3AddVertex( &foundVertices, m->i2 ); + bool addedVertex3 = b3AddVertex( &foundVertices, m->i3 ); + + b3TriangleFeature feature = m->feature; + bool shouldCollide = false; + switch ( feature ) + { + case b3_featureNone: + case b3_featureTriangleFace: + B3_ASSERT( false ); + break; + + case b3_featureEdge1: + shouldCollide = addedEdge1; + break; + + case b3_featureEdge2: + shouldCollide = addedEdge2; + break; + + case b3_featureEdge3: + shouldCollide = addedEdge3; + break; + + case b3_featureVertex1: + shouldCollide = addedVertex1; + break; + + case b3_featureVertex2: + shouldCollide = addedVertex2; + break; + + case b3_featureVertex3: + shouldCollide = addedVertex3; + break; + + default: + B3_ASSERT( false ); + break; + } + + if ( shouldCollide == true ) + { + acceptedManifolds[acceptedManifoldCount++] = m; + } + } + } + else + { + // Problem: hull can tunnel if time of impact is at concave edge + // Example: flat box sliding down a ramp to a flat bottom + // Solution: only ignore flat edges + for ( int i = 0; i < tentativeManifoldCount; ++i ) + { + b3LocalManifold* m = tentativeManifolds[i]; + int triangleFlags = m->triangleFlags; + + if ( ( triangleFlags & b3_allFlatEdges ) == b3_allFlatEdges ) + { + continue; + } + + if ( ( triangleFlags & b3_flatEdge1 ) == b3_flatEdge1 ) + { + if ( b3FindEdge( &foundEdges, m->i1, m->i2 ) ) + { + continue; + } + } + + if ( ( triangleFlags & b3_flatEdge2 ) == b3_flatEdge2 ) + { + if ( b3FindEdge( &foundEdges, m->i2, m->i3 ) ) + { + continue; + } + } + + if ( ( triangleFlags & b3_flatEdge3 ) == b3_flatEdge3 ) + { + if ( b3FindEdge( &foundEdges, m->i3, m->i1 ) ) + { + continue; + } + } + + acceptedManifolds[acceptedManifoldCount++] = m; + } + } + + B3_ASSERT( acceptedManifoldCount <= triangleCount ); + + if ( acceptedManifoldCount == 0 ) + { + if ( contact->manifoldCount > 0 ) + { + b3FreeManifolds( world, contact->manifolds, contact->manifoldCount ); + contact->manifolds = NULL; + contact->manifoldCount = 0; + } + return false; + } + + b3Cluster* clusters = b3Bump( &arena, acceptedManifoldCount * sizeof( b3Cluster ) ); + int* clusterMemberships = b3Bump( &arena, acceptedManifoldCount * sizeof( int ) ); + + // Cluster tolerance is tighter than the warm starting manifold matching tolerance. These + // serve different purposes. + const float clusterThreshold = 0.996f; + int clusterCount = 0; + int clusterPointCount = 0; + for ( int i = 0; i < acceptedManifoldCount; ++i ) + { + clusterMemberships[i] = B3_NULL_INDEX; + + const b3LocalManifold* manifold = acceptedManifolds[i]; + clusterPointCount += manifold->pointCount; + + // Cluster based on the triangle normal and contact normal. + // The first cluster found is accepted because the tolerance is tight. + // todo consider requiring the triangles to be connect by an edge. + // todo consider looking for the best cluster instead of the first one within tolerance + // This bool is here to allow quick testing with and without clustering. + bool allowClustering = true; + b3Vec3 manifoldNormal = manifold->normal; + b3Vec3 triangleNormal = manifold->triangleNormal; + int clusterIndex = B3_NULL_INDEX; + for ( int j = 0; j < clusterCount && allowClustering; ++j ) + { + float cosManifoldAngle = b3Dot( clusters[j].manifoldNormal, manifoldNormal ); + float cosTriangleAngle = b3Dot( clusters[j].triangleNormal, triangleNormal ); + if ( cosManifoldAngle <= clusterThreshold || cosTriangleAngle <= clusterThreshold ) + { + continue; + } + +#if 0 + // todo there could be later triangles that create the connection + // then failure to cluster breaks greedy impulse warm starting + bool edgeConnected = false; + + for ( int k = 0; k < i; ++k ) + { + if ( clusterMemberships[k] != j ) + { + continue; + } + + const b3LocalManifold* other = acceptedManifolds[k]; + if ( b3TrianglesShareEdge( manifold->i1, manifold->i2, manifold->i3, other->i1, other->i2, other->i3 ) ) + { + edgeConnected = true; + break; + } + } + + if ( edgeConnected ) + { + clusterIndex = j; + break; + } +#else + + // Found a cluster + clusterIndex = j; + break; +#endif + } + + if ( clusterIndex != B3_NULL_INDEX ) + { + clusterMemberships[i] = clusterIndex; + clusters[clusterIndex].pointCapacity += manifold->pointCount; + } + else + { + clusters[clusterCount].manifoldNormal = manifoldNormal; + clusters[clusterCount].triangleNormal = triangleNormal; + clusters[clusterCount].pointCapacity = manifold->pointCount; + clusterMemberships[i] = clusterCount; + clusterCount += 1; + } + } + + if ( clusterPointCount == 0 ) + { + return false; + } + + // Setup clusters + b3LocalManifoldPoint* clusterPoints = b3Bump( &arena, clusterPointCount * sizeof( b3LocalManifoldPoint ) ); + int pointOffset = 0; + + for ( int i = 0; i < clusterCount; ++i ) + { + b3Cluster* cluster = clusters + i; + cluster->points = clusterPoints + pointOffset; + cluster->pointCount = 0; + pointOffset += cluster->pointCapacity; + } + + // Populate clusters + for ( int i = 0; i < acceptedManifoldCount; ++i ) + { + int clusterIndex = clusterMemberships[i]; + if ( clusterIndex == B3_NULL_INDEX ) + { + continue; + } + + B3_ASSERT( 0 <= clusterIndex && clusterIndex < clusterCount ); + + b3LocalManifold* am = acceptedManifolds[i]; + b3Cluster* cm = clusters + clusterIndex; + for ( int j = 0; j < am->pointCount; ++j ) + { + B3_ASSERT( cm->pointCount < cm->pointCapacity ); + b3LocalManifoldPoint* ap = am->points + j; + b3LocalManifoldPoint* cp = cm->points + cm->pointCount; + + cp->triangleIndex = am->triangleIndex; + cp->point = ap->point; + cp->separation = ap->separation; + cp->pair = ap->pair; + cm->pointCount += 1; + } + } + + // Simplify clusters + for ( int i = 0; i < clusterCount; ++i ) + { + b3Cluster* cm = clusters + i; + B3_ASSERT( cm->pointCount == cm->pointCapacity ); + int reducedCount = b3ReduceCluster( cm->points, cm->pointCount, cm->triangleNormal, arena ); + cm->pointCount = reducedCount; + } + + // Make a temporary copy of previous manifolds + int oldManifoldCount = contact->manifoldCount; + b3Manifold* oldManifolds = NULL; + if ( oldManifoldCount > 0 ) + { + oldManifolds = b3Bump( &arena, oldManifoldCount * sizeof( b3Manifold ) ); + memcpy( oldManifolds, contact->manifolds, oldManifoldCount * sizeof( b3Manifold ) ); + } + + // Resize manifolds if needed + if ( oldManifoldCount != clusterCount ) + { + b3FreeManifolds( world, contact->manifolds, contact->manifoldCount ); + contact->manifolds = b3AllocateManifolds( world, clusterCount ); + contact->manifoldCount = (uint16_t)clusterCount; + } + else + { + // Mem zero manifolds + memset( contact->manifolds, 0, contact->manifoldCount * sizeof( b3Manifold ) ); + } + + bool* consumed = NULL; + if ( oldManifoldCount > 0 ) + { + consumed = b3Bump( &arena, oldManifoldCount * sizeof( bool ) ); + memset( consumed, 0, oldManifoldCount * sizeof( bool ) ); + } + + b3Matrix3 matrixB = b3MakeMatrixFromQuat( xfB.q ); + b3Vec3 offsetA = b3SubPos( xfB.p, xfA.p ); + + const float normalMatchTolerance = 0.995f; + for ( int i = 0; i < clusterCount; ++i ) + { + b3Cluster* cm = clusters + i; + int pointCount = cm->pointCount; + B3_ASSERT( 0 < pointCount && pointCount <= B3_MAX_MANIFOLD_POINTS ); + + b3Manifold* manifold = contact->manifolds + i; + manifold->pointCount = pointCount; + manifold->normal = b3MulMV( matrixB, cm->manifoldNormal ); + + b3Vec3 clusterNormal = b3MulMV( matrixB, cm->manifoldNormal ); + float bestDot = normalMatchTolerance; + int bestIndex = B3_NULL_INDEX; + + for ( int j = 0; j < oldManifoldCount; ++j ) + { + if ( consumed[j] == true ) + { + continue; + } + + float dot = b3Dot( oldManifolds[j].normal, clusterNormal ); + if ( dot > bestDot ) + { + bestIndex = j; + bestDot = dot; + } + } + + b3Manifold* matchedManifold = NULL; + if ( bestIndex != B3_NULL_INDEX ) + { + matchedManifold = oldManifolds + bestIndex; + manifold->frictionImpulse = matchedManifold->frictionImpulse; + manifold->rollingImpulse = matchedManifold->rollingImpulse; + manifold->twistImpulse = matchedManifold->twistImpulse; + consumed[bestIndex] = true; + } + + for ( int j = 0; j < pointCount; ++j ) + { + const b3LocalManifoldPoint* source = cm->points + j; + b3ManifoldPoint* target = manifold->points + j; + + // Contact points are computed in frame B + target->anchorB = b3MulMV( matrixB, source->point ); + target->anchorA = b3Add( target->anchorB, offsetA ); + target->separation = source->separation - restOffset; + target->featureId = b3MakeFeatureId( source->pair ); + target->triangleIndex = source->triangleIndex; + + // Preserve normal impulse if possible + if ( matchedManifold != NULL ) + { + int oldPointCount = matchedManifold->pointCount; + for ( int k = 0; k < oldPointCount; ++k ) + { + b3ManifoldPoint* oldPt = matchedManifold->points + k; + + if ( target->featureId == oldPt->featureId && target->triangleIndex == oldPt->triangleIndex ) + { + target->normalImpulse = oldPt->normalImpulse; + target->persisted = true; + + // claimed + oldPt->triangleIndex = B3_NULL_INDEX; + break; + } + } + } + } + } + + const b3SurfaceMaterial* materialsA = b3GetShapeMaterials( shapeA ); + const b3SurfaceMaterial* materialB = b3GetShapeMaterials( shapeB ); + b3Vec3 tangentVelocityA = b3Vec3_zero; + + // Update friction and restitution if the mesh has per triangle material + if ( shapeA->materialCount > 0 ) + { + float friction = 0.0f; + float restitution = 0.0f; + float sampleCount = 0.0f; + + const uint8_t* materialIndices; + if ( shapeA->type == b3_meshShape ) + { + materialIndices = b3GetMeshMaterialIndices( shapeA->mesh.data ); + } + else + { + materialIndices = b3GetHeightFieldMaterialIndices( shapeA->heightField ); + } + + for ( int i = 0; i < clusterCount; ++i ) + { + b3Manifold* manifold = contact->manifolds + i; + int pointCount = manifold->pointCount; + for ( int j = 0; j < pointCount; ++j ) + { + int triangleIndex = manifold->points[j].triangleIndex; + int materialIndex; + if ( shapeA->type == b3_meshShape ) + { + materialIndex = materialIndices[triangleIndex]; + + if ( materialMap != NULL ) + { + materialIndex = materialMap[materialIndex]; + } + } + else + { + materialIndex = materialIndices[triangleIndex >> 1]; + } + + materialIndex = b3ClampInt( materialIndex, 0, shapeA->materialCount - 1 ); + b3SurfaceMaterial material = materialsA[materialIndex]; + friction += world->frictionCallback( material.friction, material.userMaterialId, materialB->friction, + materialB->userMaterialId ); + restitution += world->restitutionCallback( material.restitution, material.userMaterialId, materialB->restitution, + materialB->userMaterialId ); + + tangentVelocityA = b3Add( tangentVelocityA, material.tangentVelocity ); + + sampleCount += 1.0f; + } + } + + if ( sampleCount > 0.0f ) + { + float invCount = 1.0f / sampleCount; + contact->friction = invCount * friction; + contact->restitution = invCount * restitution; + tangentVelocityA = b3MulSV( invCount, tangentVelocityA ); + } + + B3_ASSERT( b3IsValidFloat( contact->friction ) && contact->friction >= 0.0f ); + B3_ASSERT( b3IsValidFloat( contact->restitution ) && contact->restitution >= 0.0f ); + } + else + { + // Keep these updated in case the values on the shapes are modified + contact->friction = world->frictionCallback( materialsA[0].friction, materialsA[0].userMaterialId, materialB->friction, + materialB->userMaterialId ); + contact->restitution = world->restitutionCallback( materialsA[0].restitution, materialsA[0].userMaterialId, + materialB->restitution, materialB->userMaterialId ); + tangentVelocityA = materialsA[0].tangentVelocity; + } + + tangentVelocityA = b3RotateVector( xfA.q, tangentVelocityA ); + + float radiusB = 0.0f; + if ( shapeB->type == b3_sphereShape ) + { + radiusB = shapeB->sphere.radius; + } + else if ( shapeB->type == b3_capsuleShape ) + { + radiusB = shapeB->capsule.radius; + } + else if ( shapeB->type == b3_hullShape ) + { + radiusB = shapeB->hull->innerRadius; + } + + contact->rollingResistance = materialB->rollingResistance * radiusB; + + b3Vec3 tangentVelocityB = b3RotateVector( xfB.q, materialB->tangentVelocity ); + contact->tangentVelocity = b3Sub( tangentVelocityA, tangentVelocityB ); + return true; +} diff --git a/vendor/box3d/src/src/motor_joint.c b/vendor/box3d/src/src/motor_joint.c new file mode 100644 index 000000000..734d49767 --- /dev/null +++ b/vendor/box3d/src/src/motor_joint.c @@ -0,0 +1,439 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "core.h" +#include "joint.h" +#include "math_internal.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +void b3MotorJoint_SetLinearVelocity( b3JointId jointId, b3Vec3 velocity ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetLinearVelocity, jointId, velocity ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.linearVelocity = velocity; +} + +b3Vec3 b3MotorJoint_GetLinearVelocity( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.linearVelocity; +} + +void b3MotorJoint_SetAngularVelocity( b3JointId jointId, b3Vec3 velocity ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetAngularVelocity, jointId, velocity ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.angularVelocity = velocity; +} + +b3Vec3 b3MotorJoint_GetAngularVelocity( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.angularVelocity; +} + +void b3MotorJoint_SetMaxVelocityTorque( b3JointId jointId, float maxTorque ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetMaxVelocityTorque, jointId, maxTorque ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.maxVelocityTorque = maxTorque; +} + +float b3MotorJoint_GetMaxVelocityTorque( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.maxVelocityTorque; +} + +void b3MotorJoint_SetMaxVelocityForce( b3JointId jointId, float maxForce ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetMaxVelocityForce, jointId, maxForce ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.maxVelocityForce = maxForce; +} + +float b3MotorJoint_GetMaxVelocityForce( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.maxVelocityForce; +} + +void b3MotorJoint_SetLinearHertz( b3JointId jointId, float hertz ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetLinearHertz, jointId, hertz ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.linearHertz = hertz; +} + +float b3MotorJoint_GetLinearHertz( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.linearHertz; +} + +void b3MotorJoint_SetLinearDampingRatio( b3JointId jointId, float damping ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetLinearDampingRatio, jointId, damping ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.linearDampingRatio = damping; +} + +float b3MotorJoint_GetLinearDampingRatio( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.linearDampingRatio; +} + +void b3MotorJoint_SetAngularHertz( b3JointId jointId, float hertz ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetAngularHertz, jointId, hertz ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.angularHertz = hertz; +} + +float b3MotorJoint_GetAngularHertz( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.angularHertz; +} + +void b3MotorJoint_SetAngularDampingRatio( b3JointId jointId, float damping ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetAngularDampingRatio, jointId, damping ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.angularDampingRatio = damping; +} + +float b3MotorJoint_GetAngularDampingRatio( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.angularDampingRatio; +} + +void b3MotorJoint_SetMaxSpringForce( b3JointId jointId, float maxForce ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetMaxSpringForce, jointId, maxForce ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.maxSpringForce = b3MaxFloat( 0.0f, maxForce ); +} + +float b3MotorJoint_GetMaxSpringForce( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.maxSpringForce; +} + +void b3MotorJoint_SetMaxSpringTorque( b3JointId jointId, float maxTorque ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, MotorJointSetMaxSpringTorque, jointId, maxTorque ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + joint->motorJoint.maxSpringTorque = b3MaxFloat( 0.0f, maxTorque ); +} + +float b3MotorJoint_GetMaxSpringTorque( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_motorJoint ); + return joint->motorJoint.maxSpringTorque; +} + +b3Vec3 b3GetMotorJointForce( b3World* world, b3JointSim* base ) +{ + b3Vec3 force = b3MulSV( world->inv_h, b3Add( base->motorJoint.linearVelocityImpulse, base->motorJoint.linearSpringImpulse ) ); + return force; +} + +b3Vec3 b3GetMotorJointTorque( b3World* world, b3JointSim* base ) +{ + return b3MulSV( world->inv_h, b3Add( base->motorJoint.angularVelocityImpulse, base->motorJoint.angularSpringImpulse ) ); +} + +// Point-to-point constraint +// C = p2 - p1 +// Cdot = v2 - v1 +// = v2 + cross(w2, r2) - v1 - cross(w1, r1) +// J = [-I -r1_skew I r2_skew ] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +// Angle constraint +// C = angle2 - angle1 - referenceAngle +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] +// K = invI1 + invI2 + +void b3PrepareMotorJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_motorJoint ); + + // chase body id to the solver set where the body lives + int idA = base->bodyIdA; + int idB = base->bodyIdB; + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ); + + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3MotorJoint* joint = &base->motorJoint; + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + // Compute the initial center delta. Incremental position updates are relative to this. + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + + joint->linearSpring = b3MakeSoft( joint->linearHertz, joint->linearDampingRatio, context->h ); + joint->angularSpring = b3MakeSoft( joint->angularHertz, joint->angularDampingRatio, context->h ); + + joint->angularMass = b3InvertMatrix( invInertiaSum ); + + if ( context->enableWarmStarting == false ) + { + joint->linearVelocityImpulse = b3Vec3_zero; + joint->angularVelocityImpulse = b3Vec3_zero; + joint->linearSpringImpulse = b3Vec3_zero; + joint->angularSpringImpulse = b3Vec3_zero; + } +} + +void b3WarmStartMotorJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_motorJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + b3MotorJoint* joint = &base->motorJoint; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 linearImpulse = b3Add( joint->linearVelocityImpulse, joint->linearSpringImpulse ); + b3Vec3 angularImpulse = b3Add( joint->angularVelocityImpulse, joint->angularSpringImpulse ); + + stateA->linearVelocity = b3MulSub( stateA->linearVelocity, mA, linearImpulse ); + stateA->angularVelocity = b3Sub( stateA->angularVelocity, b3MulMV( iA, b3Add( b3Cross( rA, linearImpulse ), angularImpulse ) ) ); + stateB->linearVelocity = b3MulAdd( stateB->linearVelocity, mB, linearImpulse ); + stateB->angularVelocity = b3Add( stateB->angularVelocity, b3MulMV( iB, b3Add( b3Cross( rB, linearImpulse ), angularImpulse ) ) ); +} + +void b3SolveMotorJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_motorJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3MotorJoint* joint = &base->motorJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the rotation angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + // angular spring + if ( joint->maxSpringTorque > 0.0f && joint->angularHertz > 0.0f ) + { + b3Quat targetQuat = b3Quat_identity; + b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, targetQuat ); + b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) ); + + b3Vec3 bias = b3MulSV( joint->angularSpring.biasRate, c ); + float massScale = joint->angularSpring.massScale; + float impulseScale = joint->angularSpring.impulseScale; + + b3Vec3 cdot = b3Sub( wB, wA ); + + float maxImpulse = context->h * joint->maxSpringTorque; + b3Vec3 oldImpulse = joint->angularSpringImpulse; + b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b3MulMV( joint->angularMass, b3Add( cdot, bias ) ) ), impulseScale, oldImpulse ); + joint->angularSpringImpulse = b3Add( oldImpulse, impulse ); + if ( b3LengthSquared( joint->angularSpringImpulse ) > maxImpulse * maxImpulse ) + { + joint->angularSpringImpulse = b3MulSV( maxImpulse, b3Normalize( joint->angularSpringImpulse ) ); + } + impulse = b3Sub( joint->angularSpringImpulse, oldImpulse ); + + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + + // angular velocity + if ( joint->maxVelocityTorque > 0.0 ) + { + b3Vec3 cdot = b3Sub( b3Sub( wB, wA ), joint->angularVelocity ); + b3Vec3 impulse = b3Neg( b3MulMV( joint->angularMass, cdot ) ); + + float maxImpulse = context->h * joint->maxVelocityTorque; + b3Vec3 oldImpulse = joint->angularVelocityImpulse; + joint->angularVelocityImpulse = b3Add( oldImpulse, impulse ); + if ( b3LengthSquared( joint->angularVelocityImpulse ) > maxImpulse * maxImpulse ) + { + joint->angularVelocityImpulse = b3MulSV( maxImpulse, b3Normalize( joint->angularVelocityImpulse ) ); + } + impulse = b3Sub( joint->angularVelocityImpulse, oldImpulse ); + + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + // linear spring + if ( joint->maxSpringForce > 0.0f && joint->linearHertz > 0.0f ) + { + b3Vec3 dcA = stateA->deltaPosition; + b3Vec3 dcB = stateB->deltaPosition; + b3Vec3 c = b3Add( b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ), joint->deltaCenter ); + + b3Vec3 bias = b3MulSV( joint->linearSpring.biasRate, c ); + float massScale = joint->linearSpring.massScale; + float impulseScale = joint->linearSpring.impulseScale; + + b3Vec3 cdot = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) ); + + //// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + b3Matrix3 sA = b3Skew( rA ); + b3Matrix3 sB = b3Skew( rB ); + b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) ); + b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) ); + b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) ); + k.cx.x += mA + mB; + k.cy.y += mA + mB; + k.cz.z += mA + mB; + + b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) ); + + b3Vec3 oldImpulse = joint->linearSpringImpulse; + b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b ), impulseScale, oldImpulse ); + float maxImpulse = context->h * joint->maxSpringForce; + joint->linearSpringImpulse = b3Add( joint->linearSpringImpulse, impulse ); + + if ( b3LengthSquared( joint->linearSpringImpulse ) > maxImpulse * maxImpulse ) + { + joint->linearSpringImpulse = b3MulSV( maxImpulse, b3Normalize( joint->linearSpringImpulse ) ); + } + + impulse = b3Sub( joint->linearSpringImpulse, oldImpulse ); + + vA = b3MulSub( vA, mA, impulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + } + + // linear velocity + if ( joint->maxVelocityForce > 0.0f ) + { + b3Vec3 cdot = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) ); + cdot = b3Sub( cdot, joint->linearVelocity ); + //// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + b3Matrix3 sA = b3Skew( rA ); + b3Matrix3 sB = b3Skew( rB ); + b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) ); + b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) ); + b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) ); + k.cx.x += mA + mB; + k.cy.y += mA + mB; + k.cz.z += mA + mB; + + b3Vec3 b = b3Solve3( k, cdot ); + b3Vec3 impulse = b3Neg( b ); + + b3Vec3 oldImpulse = joint->linearVelocityImpulse; + float maxImpulse = context->h * joint->maxVelocityForce; + joint->linearVelocityImpulse = b3Add( joint->linearVelocityImpulse, impulse ); + + if ( b3LengthSquared( joint->linearVelocityImpulse ) > maxImpulse * maxImpulse ) + { + joint->linearVelocityImpulse = b3MulSV( maxImpulse, b3Normalize( joint->linearVelocityImpulse ) ); + } + + impulse = b3Sub( joint->linearVelocityImpulse, oldImpulse ); + + vA = b3MulSub( vA, mA, impulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} diff --git a/vendor/box3d/src/src/mover.c b/vendor/box3d/src/src/mover.c new file mode 100644 index 000000000..209426e97 --- /dev/null +++ b/vendor/box3d/src/src/mover.c @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "math_internal.h" + +#include "box3d/collision.h" +#include "box3d/constants.h" + +b3PlaneSolverResult b3SolvePlanes( b3Vec3 targetDelta, b3CollisionPlane* planes, int count ) +{ + for ( int i = 0; i < count; ++i ) + { + planes[i].push = 0.0f; + } + + b3Vec3 delta = targetDelta; + float tolerance = B3_LINEAR_SLOP; + + int iteration; + for ( iteration = 0; iteration < 20; ++iteration ) + { + float totalPush = 0.0f; + for ( int planeIndex = 0; planeIndex < count; ++planeIndex ) + { + b3CollisionPlane* plane = planes + planeIndex; + + // Add slop to prevent jitter + float separation = b3PlaneSeparation( plane->plane, delta ) + B3_LINEAR_SLOP; + + float push = -separation; + + // Clamp accumulated push + float accumulatedPush = plane->push; + plane->push = b3ClampFloat( plane->push + push, 0.0f, plane->pushLimit ); + push = plane->push - accumulatedPush; + delta = b3MulAdd( delta, push, plane->plane.normal ); + + // Track maximum push for convergence + totalPush += b3AbsFloat( push ); + } + + if ( totalPush < tolerance ) + { + break; + } + } + + return (b3PlaneSolverResult){ + .delta = delta, + .iterationCount = iteration, + }; +} + +b3Vec3 b3ClipVector( b3Vec3 vector, const b3CollisionPlane* planes, int count ) +{ + b3Vec3 v = vector; + + for ( int planeIndex = 0; planeIndex < count; ++planeIndex ) + { + const b3CollisionPlane* plane = planes + planeIndex; + if ( plane->push == 0.0f || plane->clipVelocity == false ) + { + continue; + } + + v = b3MulSub( v, b3MinFloat( 0.0f, b3Dot( v, plane->plane.normal ) ), plane->plane.normal ); + } + + return v; +} diff --git a/vendor/box3d/src/src/name_cache.c b/vendor/box3d/src/src/name_cache.c new file mode 100644 index 000000000..a7833df5a --- /dev/null +++ b/vendor/box3d/src/src/name_cache.c @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#include "name_cache.h" + +#include "recording.h" + +#include + +#define NAME b3NameMap +#define KEY_TY uint32_t +#define VAL_TY int +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +#define FNV32_OFFSET_BASIS 0x811c9dc5u +#define FNV32_PRIME 0x01000193u + +// This is designed to hash small strings and while avoiding collisions +// for a 32-bit hash. +// Note: changing this will break recordings +uint32_t b3Hash32( const void* data, size_t length ) +{ + // Cast to unsigned to avoid sign extension + const unsigned char* p = data; + + // FNV-1a + uint32_t h = FNV32_OFFSET_BASIS; + for ( size_t i = 0; i < length; ++i ) + { + h ^= p[i]; + h *= FNV32_PRIME; + } + + // Murmur3 finalizer to help with short strings + h ^= h >> 16; + h *= 0x85ebca6bu; + h ^= h >> 13; + h *= 0xc2b2ae35u; + h ^= h >> 16; + return h; +} + +b3NameCache b3CreateNameCache( void ) +{ + b3NameCache cache = { 0 }; + b3Array_Create( cache.entries ); + + b3NameMap* map = b3Alloc( sizeof( b3NameMap ) ); + b3NameMap_init( map ); + cache.map = map; + return cache; +} + +void b3DestroyNameCache( b3NameCache* cache ) +{ + int count = cache->entries.count; + for ( int i = 0; i < count; ++i ) + { + b3NameEntry* entry = cache->entries.data + i; + b3Free( entry->name, entry->length + 1 ); + } + + b3Array_Destroy( cache->entries ); + b3NameMap_cleanup( (b3NameMap*)cache->map ); + b3Free( cache->map, sizeof( b3NameMap ) ); +} + +uint32_t b3AddName( b3NameCache* cache, const char* name ) +{ + if ( name == NULL || name[0] == 0 ) + { + return B3_NULL_NAME; + } + + size_t length = strlen( name ); + uint32_t id = b3Hash32( name, length ); + + id = id == 0 ? 1 : id; + + b3NameMap* map = cache->map; + b3NameMap_itr itr = b3NameMap_get( map, id ); + if ( b3NameMap_is_end( itr ) == false ) + { + if ( strcmp( cache->entries.data[itr.data->val].name, name ) != 0 ) + { + // Different name, same hash + b3Log( "Hash collision on %s", name ); + } + return id; + } + + char* clone = b3Alloc( length + 1 ); + memcpy( clone, name, length + 1 ); + int index = cache->entries.count; + b3NameEntry entry = { + .hash = id, + .name = clone, + .length = (int)length, + }; + + b3Array_Push( cache->entries, entry ); + + b3NameMap_insert( map, id, index ); + return id; +} + +const char* b3FindName( const b3NameCache* cache, uint32_t id ) +{ + b3NameMap* map = cache->map; + b3NameMap_itr itr = b3NameMap_get( map, id ); + if ( b3NameMap_is_end( itr ) == false ) + { + int index = itr.data->val; + const b3NameEntry* entry = b3Array_Get( cache->entries, index ); + return entry->name; + } + + return NULL; +} + +void b3LoadName( b3NameCache* cache, uint32_t id, char* name, int length ) +{ + if ( name == NULL ) + { + return; + } + + if ( name[0] == 0 || id == B3_NULL_NAME ) + { + b3Free( name, length + 1 ); + return; + } + + b3NameMap* map = cache->map; + b3NameMap_itr itr = b3NameMap_get( map, id ); + if ( b3NameMap_is_end( itr ) == false ) + { + b3Free( name, length + 1 ); + return; + } + + int index = cache->entries.count; + b3NameEntry entry = { + .hash = id, + .name = name, + .length = length, + }; + + b3Array_Push( cache->entries, entry ); + b3NameMap_insert( map, id, index ); +} diff --git a/vendor/box3d/src/src/name_cache.h b/vendor/box3d/src/src/name_cache.h new file mode 100644 index 000000000..d1535b37a --- /dev/null +++ b/vendor/box3d/src/src/name_cache.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "container.h" + +#define B3_NULL_NAME 0u + +typedef struct b3NameEntry +{ + // Names are used for debugging and I'm favoring simplicity and + // minimal storage. Collisions will be logged. + uint32_t hash; + int length; + char* name; +} b3NameEntry; + +b3DeclareArray( b3NameEntry ); + +typedef struct b3NameCache +{ + b3Array( b3NameEntry ) entries; + void* map; +} b3NameCache; + +b3NameCache b3CreateNameCache( void ); +void b3DestroyNameCache( b3NameCache* cache ); + +uint32_t b3AddName( b3NameCache* cache, const char* name ); +const char* b3FindName( const b3NameCache* cache, uint32_t id ); + +// Load a name from a recording. Name ownership is transferred. +void b3LoadName( b3NameCache* cache, uint32_t id, char* name, int length ); + +uint32_t b3Hash32( const void* data, size_t length ); + +static inline const char* b3FindNameWithDefault( const b3NameCache* cache, uint32_t id, const char* def ) +{ + const char* name = b3FindName( cache, id ); + return name == NULL ? def : name; +} diff --git a/vendor/box3d/src/src/parallel_for.c b/vendor/box3d/src/src/parallel_for.c new file mode 100644 index 000000000..07d1c3fdc --- /dev/null +++ b/vendor/box3d/src/src/parallel_for.c @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#include "parallel_for.h" + +#include "core.h" +#include "physics_world.h" +#include "platform.h" + +#include "box3d/base.h" +#include "box3d/constants.h" + +#include + +// Shared state for one b3ParallelFor invocation. Workers race on nextBlock to +// claim work, so a slow chunk can't strand the other threads. +typedef struct b3ParallelForShared +{ + b3AtomicInt nextBlock; + int blockCount; + int blockSize; + int itemCount; + b3ParallelForCallback* callback; + void* context; +} b3ParallelForShared; + +typedef struct b3ParallelForTask +{ + b3ParallelForShared* shared; + int workerIndex; +} b3ParallelForTask; + +static void b3ParallelForTrampoline( void* taskContext ) +{ + b3ParallelForTask* task = (b3ParallelForTask*)taskContext; + b3ParallelForShared* shared = task->shared; + int workerIndex = task->workerIndex; + void* context = shared->context; + b3ParallelForCallback* callback = shared->callback; + + int blockCount = shared->blockCount; + int blockSize = shared->blockSize; + int itemCount = shared->itemCount; + + for ( ;; ) + { + int blockIndex = b3AtomicFetchAddInt( &shared->nextBlock, 1 ); + if ( blockIndex >= blockCount ) + { + break; + } + + int start = blockIndex * blockSize; + int end = start + blockSize; + if ( end > itemCount ) + { + end = itemCount; + } + + callback( start, end, workerIndex, context ); + } +} + +void b3ParallelFor( b3World* world, b3ParallelForCallback* callback, int itemCount, int minRange, void* context, + const char* name ) +{ + if ( itemCount <= 0 ) + { + return; + } + + B3_ASSERT( minRange > 0 ); + + int workerCount = world->workerCount; + B3_ASSERT( 0 < workerCount && workerCount <= B3_MAX_WORKERS ); + + // Target multiple blocks per worker to reduce thread stalls. + // block size grows once items exceed maxBlockCount * minRange + // so the block count stays bounded and per-block sync overhead stays low. + int blocksPerWorker = 4; + int maxBlockCount = blocksPerWorker * workerCount; + + int blockSize; + int blockCount; + if ( itemCount <= minRange * maxBlockCount ) + { + blockSize = minRange; + blockCount = ( itemCount + blockSize - 1 ) / blockSize; + } + else + { + blockSize = ( itemCount + maxBlockCount - 1 ) / maxBlockCount; + blockCount = ( itemCount + blockSize - 1 ) / blockSize; + } + B3_ASSERT( blockCount >= 1 ); + B3_ASSERT( blockSize * blockCount >= itemCount ); + + // No point enqueueing more tasks than blocks. + int taskCount = workerCount < blockCount ? workerCount : blockCount; + + b3ParallelForShared shared; + shared.blockCount = blockCount; + shared.blockSize = blockSize; + shared.itemCount = itemCount; + shared.callback = callback; + shared.context = context; + b3AtomicStoreInt( &shared.nextBlock, 0 ); + + b3ParallelForTask tasks[B3_MAX_WORKERS]; + void* handles[B3_MAX_WORKERS]; + for ( int i = 0; i < taskCount; ++i ) + { + tasks[i].shared = &shared; + tasks[i].workerIndex = i; + + if (world->taskCount < B3_MAX_TASKS) + { + handles[i] = world->enqueueTaskFcn( &b3ParallelForTrampoline, tasks + i, world->userTaskContext, name ); + world->taskCount += 1; + } + else + { + handles[i] = NULL; + b3ParallelForTrampoline( tasks + i ); + } + } + + for ( int i = 0; i < taskCount; ++i ) + { + if ( handles[i] != NULL ) + { + world->finishTaskFcn( handles[i], world->userTaskContext ); + } + } +} diff --git a/vendor/box3d/src/src/parallel_for.h b/vendor/box3d/src/src/parallel_for.h new file mode 100644 index 000000000..4a6d2851c --- /dev/null +++ b/vendor/box3d/src/src/parallel_for.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +typedef struct b3World b3World; + +// Callback invoked by b3ParallelFor to process a range of items. May be called +// multiple times per worker: work is divided into blocks that workers claim +// atomically, so a worker that finishes early picks up the next unclaimed +// block instead of sitting idle. workerIndex is the worker identity and is +// stable across all invocations from the same worker, so it is safe to use as +// an index into per-worker state (e.g. world->taskContexts.data + workerIndex). +typedef void b3ParallelForCallback( int startIndex, int endIndex, int workerIndex, void* context ); + +// Divide [0, itemCount) into blocks and process them with cooperative claiming: +// up to world->workerCount tasks are enqueued, and each task loops, atomically +// claiming the next unclaimed block until the range is drained. Blocks the +// caller until all work is complete. minRange is the minimum block size; block +// size grows once itemCount exceeds 4 * workerCount * minRange so block count +// stays bounded. +void b3ParallelFor( b3World* world, b3ParallelForCallback* callback, int itemCount, int minRange, void* context, + const char* name ); diff --git a/vendor/box3d/src/src/parallel_joint.c b/vendor/box3d/src/src/parallel_joint.c new file mode 100644 index 000000000..7390a2131 --- /dev/null +++ b/vendor/box3d/src/src/parallel_joint.c @@ -0,0 +1,251 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "core.h" +#include "joint.h" +#include "math_internal.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +void b3ParallelJoint_SetSpringHertz( b3JointId jointId, float hertz ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, ParallelJointSetSpringHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint ); + base->parallelJoint.hertz = hertz; +} + +float b3ParallelJoint_GetSpringHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint ); + return base->parallelJoint.hertz; +} + +void b3ParallelJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, ParallelJointSetSpringDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint ); + base->parallelJoint.dampingRatio = dampingRatio; +} + +float b3ParallelJoint_GetSpringDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint ); + return base->parallelJoint.dampingRatio; +} + +void b3ParallelJoint_SetMaxTorque( b3JointId jointId, float maxForce ) +{ + B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, ParallelJointSetMaxTorque, jointId, maxForce ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint ); + base->parallelJoint.maxTorque = maxForce; +} + +float b3ParallelJoint_GetMaxTorque( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_parallelJoint ); + return base->parallelJoint.maxTorque; +} + +b3Vec3 b3GetParallelJointTorque( b3World* world, b3JointSim* base ) +{ + b3ParallelJoint* joint = &base->parallelJoint; + + b3Quat relQ = b3InvMulQuat( joint->quatA, joint->quatB ); + joint->perpAxisX = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + joint->perpAxisY = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + + b3Vec3 angularImpulse = b3Blend2( joint->perpImpulse.x, joint->perpAxisX, joint->perpImpulse.y, joint->perpAxisY ); + b3Vec3 torque = b3MulSV( world->inv_h, angularImpulse ); + return torque; +} + +void b3PrepareParallelJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_parallelJoint ); + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB ); + + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ); + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3ParallelJoint* joint = &base->parallelJoint; + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + joint->quatA = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->quatB = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + + b3Quat relQ = b3InvMulQuat( joint->quatA, joint->quatB ); + + { + // These are needed for warm starting + joint->perpAxisX = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + joint->perpAxisY = b3MulSV( 0.5f, b3RotateVector( joint->quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + } + + joint->softness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h ); + + if ( context->enableWarmStarting == false ) + { + joint->perpImpulse = (b3Vec2){ 0.0f, 0.0f }; + } +} + +void b3WarmStartParallelJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_parallelJoint ); + + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3ParallelJoint* joint = &base->parallelJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 wB = stateB->angularVelocity; + + b3Vec3 angularImpulse = b3Blend2( joint->perpImpulse.x, joint->perpAxisX, joint->perpImpulse.y, joint->perpAxisY ); + + wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) ); + wB = b3Add( wB, b3MulMV( iB, angularImpulse ) ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->angularVelocity = wB; + } +} + +void b3SolveParallelJoint( b3JointSim* base, b3StepContext* context ) +{ + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3ParallelJoint* joint = &base->parallelJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 wB = stateB->angularVelocity; + + bool fixedRotation = base->fixedRotation; + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->quatA ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->quatB ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the rotation angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + if ( fixedRotation == false && joint->maxTorque > 0.0f ) + { + b3Vec2 c = { relQ.v.x, relQ.v.y }; + b3Vec2 bias = { joint->softness.biasRate * c.x, joint->softness.biasRate * c.y }; + float massScale = joint->softness.massScale; + float impulseScale = joint->softness.impulseScale; + + // Collinearity constraint as 2-by-2 + b3Vec3 perpAxisX = b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + b3Vec3 perpAxisY = b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + joint->perpAxisX = perpAxisX; + joint->perpAxisY = perpAxisY; + + b3Matrix3 invInertiaSum = b3AddMM( iA, iB ); + float kxx = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisX ) ); + float kyy = b3Dot( perpAxisY, b3MulMV( invInertiaSum, perpAxisY ) ); + float kxy = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisY ) ); + + b3Matrix2 k = { { kxx, kxy }, { kxy, kyy } }; + + b3Vec3 wRel = b3Sub( wB, wA ); + b3Vec2 cdot = { b3Dot( wRel, perpAxisX ), b3Dot( wRel, perpAxisY ) }; + + float maxImpulse = context->h * joint->maxTorque; + b3Vec2 oldImpulse = joint->perpImpulse; + b3Vec2 cdotPlusBias = { cdot.x + bias.x, cdot.y + bias.y }; + b3Vec2 sol = b3Solve2( k, cdotPlusBias ); + b3Vec2 deltaImpulse = { + -massScale * sol.x - impulseScale * oldImpulse.x, + -massScale * sol.y - impulseScale * oldImpulse.y, + }; + joint->perpImpulse = (b3Vec2){ oldImpulse.x + deltaImpulse.x, oldImpulse.y + deltaImpulse.y }; + if ( b3LengthSquared2( joint->perpImpulse ) > maxImpulse * maxImpulse ) + { + float s = maxImpulse / b3Length2( joint->perpImpulse ); + joint->perpImpulse = (b3Vec2){ s * joint->perpImpulse.x, s * joint->perpImpulse.y }; + } + + deltaImpulse = b3Sub2( joint->perpImpulse, oldImpulse ); + + b3Vec3 angularImpulse = b3Blend2( deltaImpulse.x, perpAxisX, deltaImpulse.y, perpAxisY ); + wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) ); + wB = b3Add( wB, b3MulMV( iB, angularImpulse ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->angularVelocity = wB; + } +} + +void b3DrawParallelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ) +{ + float length = 0.1f * scale; + + b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA ); + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length, b3RotateVector( frameA.q, b3Vec3_axisZ ) ) ), b3_colorGreen, draw->context ); + + b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB ); + draw->DrawSegmentFcn( frameB.p, b3OffsetPos( frameB.p, b3MulSV( length, b3RotateVector( frameB.q, b3Vec3_axisZ ) ) ), b3_colorBlue, draw->context ); +} diff --git a/vendor/box3d/src/src/physics_world.c b/vendor/box3d/src/src/physics_world.c new file mode 100644 index 000000000..f0dc19e2d --- /dev/null +++ b/vendor/box3d/src/src/physics_world.c @@ -0,0 +1,4099 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "physics_world.h" + +#include "arena_allocator.h" +#include "bitset.h" +#include "body.h" +#include "broad_phase.h" +#include "constraint_graph.h" +#include "contact.h" +#include "core.h" +#include "ctz.h" +#include "hull_map.h" +#include "island.h" +#include "joint.h" +#include "parallel_for.h" +#include "platform.h" +#include "recording.h" +#include "scheduler.h" +#include "sensor.h" +#include "shape.h" +#include "solver.h" +#include "solver_set.h" + +#include "box3d/box3d.h" +#include "box3d/constants.h" + +#include +#include +#include +#include + +_Static_assert( B3_MAX_WORLDS > 0, "must be 1 or more" ); +_Static_assert( B3_MAX_WORLDS < UINT16_MAX, "B3_MAX_WORLDS limit exceeded" ); +b3World b3_worlds[B3_MAX_WORLDS]; +b3AtomicInt b3_worldCount; +int b3_maxWorldCount; + +const b3HullData* b3AddHullToDatabase( b3World* world, const b3HullData* src ) +{ + b3HullMap* database = world->hullDatabase; + + // Compare by content so an unowned query hull finds the shared copy. + b3HullMap_itr itr = b3HullMap_get( database, src ); + if ( b3HullMap_is_end( itr ) == false ) + { + itr.data->val += 1; + return itr.data->key; + } + + b3HullData* owned = b3CloneHull( src ); + B3_ASSERT( owned != NULL ); + b3HullMap_insert( database, owned, 1 ); + return owned; +} + +const b3HullData* b3AddOwnedHullToDatabase( b3World* world, b3HullData* owned ) +{ + b3HullMap* database = world->hullDatabase; + + b3HullMap_itr itr = b3HullMap_get( database, owned ); + if ( b3HullMap_is_end( itr ) == false ) + { + itr.data->val += 1; + b3DestroyHull( owned ); + return itr.data->key; + } + + // Take ownership of input hull. + b3HullMap_insert( database, owned, 1 ); + return owned; +} + +void b3RemoveHullFromDatabase( b3World* world, const b3HullData* data ) +{ + b3HullMap* database = world->hullDatabase; + + b3HullMap_itr itr = b3HullMap_get( database, data ); + B3_ASSERT( b3HullMap_is_end( itr ) == false ); + + if ( --itr.data->val == 0 ) + { + // Erase through the iterator we already have so the lookup runs once. + b3HullData* owned = (b3HullData*)itr.data->key; + b3HullMap_erase_itr( database, itr ); + b3DestroyHull( owned ); + } +} + +b3World* b3GetUnlockedWorldFromId( b3WorldId id ) +{ + B3_ASSERT( 1 <= id.index1 && id.index1 <= B3_MAX_WORLDS ); + b3World* world = b3_worlds + ( id.index1 - 1 ); + B3_ASSERT( id.index1 == world->worldId + 1 ); + B3_ASSERT( id.generation == world->generation ); + + // A world accessed from an id should not be locked + if ( world->locked ) + { + B3_ASSERT( false ); + return NULL; + } + return world; +} + +b3World* b3GetWorldFromId( b3WorldId id ) +{ + B3_ASSERT( 1 <= id.index1 && id.index1 <= B3_MAX_WORLDS ); + b3World* world = b3_worlds + ( id.index1 - 1 ); + B3_ASSERT( id.index1 == world->worldId + 1 ); + B3_ASSERT( id.generation == world->generation ); + return world; +} + +b3World* b3GetWorld( int index ) +{ + B3_ASSERT( 0 <= index && index < B3_MAX_WORLDS ); + b3World* world = b3_worlds + index; + B3_ASSERT( world->worldId == index ); + return world; +} + +b3World* b3GetUnlockedWorld( int index ) +{ + B3_ASSERT( 0 <= index && index < B3_MAX_WORLDS ); + b3World* world = b3_worlds + index; + B3_ASSERT( world->worldId == index ); + if ( world->locked ) + { + B3_ASSERT( false ); + return NULL; + } + + return world; +} + +static void* b3DefaultAddTaskFcn( b3TaskCallback* task, void* taskContext, void* userContext, const char* name ) +{ + B3_UNUSED( userContext, name ); + task( taskContext ); + return NULL; +} + +static void b3DefaultFinishTaskFcn( void* userTask, void* userContext ) +{ + B3_UNUSED( userTask ); + B3_UNUSED( userContext ); +} + +static float b3DefaultFrictionCallback( float frictionA, uint64_t materialA, float frictionB, uint64_t materialB ) +{ + B3_UNUSED( materialA, materialB ); + return sqrtf( frictionA * frictionB ); +} + +static float b3DefaultRestitutionCallback( float restitutionA, uint64_t materialA, float restitutionB, uint64_t materialB ) +{ + B3_UNUSED( materialA, materialB ); + return b3MaxFloat( restitutionA, restitutionB ); +} + +static void b3CreateWorkerContexts( b3World* world ) +{ + b3Array_Resize( world->taskContexts, world->workerCount ); + b3Array_MemZero( world->taskContexts ); + + b3Array_Resize( world->sensorTaskContexts, world->workerCount ); + b3Array_MemZero( world->sensorTaskContexts ); + + for ( int i = 0; i < world->workerCount; ++i ) + { + world->taskContexts.data[i].arena = b3CreateArena( 128 * 1024 ); + b3Array_Reserve( world->taskContexts.data[i].sensorHits, 8 ); + world->taskContexts.data[i].contactStateBitSet = b3CreateBitSet( 1024 ); + world->taskContexts.data[i].hitEventBitSet = b3CreateBitSet( 1024 ); + world->taskContexts.data[i].hasHitEvents = false; + world->taskContexts.data[i].jointStateBitSet = b3CreateBitSet( 1024 ); + world->taskContexts.data[i].enlargedSimBitSet = b3CreateBitSet( 256 ); + world->taskContexts.data[i].awakeIslandBitSet = b3CreateBitSet( 256 ); + world->taskContexts.data[i].splitIslandId = B3_NULL_INDEX; + + world->sensorTaskContexts.data[i].eventBits = b3CreateBitSet( 128 ); + } +} + +static void b3DestroyWorkerContexts( b3World* world ) +{ + for ( int i = 0; i < world->workerCount; ++i ) + { + b3DestroyArena( &world->taskContexts.data[i].arena ); + b3Array_Destroy( world->taskContexts.data[i].sensorHits ); + b3DestroyBitSet( &world->taskContexts.data[i].contactStateBitSet ); + b3DestroyBitSet( &world->taskContexts.data[i].hitEventBitSet ); + b3DestroyBitSet( &world->taskContexts.data[i].jointStateBitSet ); + b3DestroyBitSet( &world->taskContexts.data[i].enlargedSimBitSet ); + b3DestroyBitSet( &world->taskContexts.data[i].awakeIslandBitSet ); + + b3DestroyBitSet( &world->sensorTaskContexts.data[i].eventBits ); + } + + b3Array_Destroy( world->taskContexts ); + b3Array_Destroy( world->sensorTaskContexts ); +} + +b3WorldId b3CreateWorld( const b3WorldDef* def ) +{ + B3_CHECK_DEF( def ); + + B3_ASSERT( B3_LINEAR_SLOP <= B3_MESH_REST_OFFSET ); + B3_ASSERT( B3_MESH_REST_OFFSET < B3_SPECULATIVE_DISTANCE ); + + int worldId = B3_NULL_INDEX; + for ( int i = 0; i < B3_MAX_WORLDS; ++i ) + { + if ( b3_worlds[i].inUse == false ) + { + worldId = i; + break; + } + } + + if ( worldId == B3_NULL_INDEX ) + { + b3Log( "B3_MAX_WORLDS of %d exceeded!!!", B3_MAX_WORLDS ); + B3_ASSERT( worldId != B3_NULL_INDEX ); + return (b3WorldId){ 0 }; + } + + // b3Log( "b3_lengthUnitsPerMeter = %g", b3_lengthUnitsPerMeter ); + + int oldCount = b3AtomicFetchAddInt( &b3_worldCount, 1 ); + b3_maxWorldCount = b3MaxInt( b3_maxWorldCount, oldCount + 1 ); + + // b3Log( "Created world %d", worldId ); + + b3InitializeContactRegisters(); + + b3World* world = b3_worlds + worldId; + uint16_t revision = world->generation; + + memset( world, 0, sizeof( b3World ) ); + + world->worldId = (uint16_t)worldId; + world->generation = revision; + world->inUse = true; + + world->stack = b3CreateStack( 2048 ); + + b3Array_Reserve( world->manifoldAllocators, 16 ); + world->manifoldAllocatorMutex = b3CreateMutex(); + + b3CreateBroadPhase( &world->broadPhase, &def->capacity ); + b3CreateGraph( &world->constraintGraph, 16 ); + + // pools + world->bodyIdPool = b3CreateIdPool(); + + int bodyCapacity = b3MaxInt( 16, def->capacity.staticBodyCount + def->capacity.dynamicBodyCount ); + b3Array_Reserve( world->bodies, bodyCapacity ); + b3Array_Reserve( world->solverSets, 8 ); + + // add empty static, active, and disabled body sets + world->solverSetIdPool = b3CreateIdPool(); + b3SolverSet set = { 0 }; + + // static set + set.setIndex = b3AllocId( &world->solverSetIdPool ); + b3Array_Push( world->solverSets, set ); + b3Array_Reserve( world->solverSets.data[b3_staticSet].bodySims, b3MaxInt( 16, def->capacity.staticBodyCount ) ); + B3_ASSERT( world->solverSets.data[b3_staticSet].setIndex == b3_staticSet ); + + // disabled set + set.setIndex = b3AllocId( &world->solverSetIdPool ); + b3Array_Push( world->solverSets, set ); + B3_ASSERT( world->solverSets.data[b3_disabledSet].setIndex == b3_disabledSet ); + + // awake set + set.setIndex = b3AllocId( &world->solverSetIdPool ); + b3Array_Push( world->solverSets, set ); + b3Array_Reserve( world->solverSets.data[b3_awakeSet].bodySims, b3MaxInt( 16, def->capacity.dynamicBodyCount ) ); + b3Array_Reserve( world->solverSets.data[b3_awakeSet].bodyStates, b3MaxInt( 16, def->capacity.dynamicBodyCount ) ); + b3Array_Reserve( world->solverSets.data[b3_awakeSet].contactIndices, b3MaxInt( 16, def->capacity.contactCount ) ); + B3_ASSERT( world->solverSets.data[b3_awakeSet].setIndex == b3_awakeSet ); + + world->shapeIdPool = b3CreateIdPool(); + + int shapeCapacity = b3MaxInt( 16, def->capacity.staticShapeCount + def->capacity.dynamicShapeCount ); + b3Array_Reserve( world->shapes, shapeCapacity ); + + world->hullDatabase = b3Alloc( sizeof( b3HullMap ) ); + b3HullMap_init( world->hullDatabase ); + + world->names = b3CreateNameCache(); + + world->contactIdPool = b3CreateIdPool(); + b3Array_Reserve( world->contacts, b3MaxInt( 16, def->capacity.contactCount ) ); + + world->jointIdPool = b3CreateIdPool(); + b3Array_Reserve( world->joints, 16 ); + + world->islandIdPool = b3CreateIdPool(); + b3Array_Reserve( world->islands, b3MaxInt( 16, def->capacity.dynamicBodyCount ) ); + + b3Array_Reserve( world->sensors, 4 ); + + b3Array_Reserve( world->bodyMoveEvents, 4 ); + b3Array_Reserve( world->sensorBeginEvents, 4 ); + b3Array_Reserve( world->sensorEndEvents[0], 4 ); + b3Array_Reserve( world->sensorEndEvents[1], 4 ); + b3Array_Reserve( world->contactBeginEvents, 4 ); + b3Array_Reserve( world->contactEndEvents[0], 4 ); + b3Array_Reserve( world->contactEndEvents[1], 4 ); + b3Array_Reserve( world->contactHitEvents, 4 ); + b3Array_Reserve( world->jointEvents, 4 ); + world->endEventArrayIndex = 0; + + world->stepIndex = 0; + world->splitIslandId = B3_NULL_INDEX; + world->activeTaskCount = 0; + world->taskCount = 0; + world->gravity = def->gravity; + world->hitEventThreshold = def->hitEventThreshold; + world->restitutionThreshold = def->restitutionThreshold; + world->maxLinearSpeed = def->maximumLinearSpeed; + world->contactSpeed = def->contactSpeed; + world->contactHertz = def->contactHertz; + world->contactDampingRatio = def->contactDampingRatio; + world->contactRecycleDistance = B3_CONTACT_RECYCLE_DISTANCE; + + if ( def->frictionCallback == NULL ) + { + world->frictionCallback = b3DefaultFrictionCallback; + } + else + { + world->frictionCallback = def->frictionCallback; + } + + if ( def->restitutionCallback == NULL ) + { + world->restitutionCallback = b3DefaultRestitutionCallback; + } + else + { + world->restitutionCallback = def->restitutionCallback; + } + + world->enableSleep = def->enableSleep; + world->locked = false; + world->enableWarmStarting = true; + world->enableContinuous = def->enableContinuous; + world->enableSpeculative = true; + world->userTreeTask = NULL; + world->userData = def->userData; + + if ( def->workerCount > 0 && def->enqueueTask != NULL && def->finishTask != NULL ) + { + // External task system + world->workerCount = b3MinInt( def->workerCount, B3_MAX_WORKERS ); + world->enqueueTaskFcn = def->enqueueTask; + world->finishTaskFcn = def->finishTask; + world->userTaskContext = def->userTaskContext; + world->scheduler = NULL; + } + else if ( def->workerCount > 1 ) + { + // Built-in scheduler + world->workerCount = b3MinInt( def->workerCount, B3_MAX_WORKERS ); + world->scheduler = b3CreateScheduler( world->workerCount ); + world->enqueueTaskFcn = b3SchedulerEnqueueTask; + world->finishTaskFcn = b3SchedulerFinishTask; + world->userTaskContext = world->scheduler; + } + else + { + // Serial fallback + world->workerCount = 1; + world->enqueueTaskFcn = b3DefaultAddTaskFcn; + world->finishTaskFcn = b3DefaultFinishTaskFcn; + world->userTaskContext = NULL; + world->scheduler = NULL; + } + + b3CreateWorkerContexts( world ); + + world->debugBodySet = b3CreateBitSet( 256 ); + world->debugJointSet = b3CreateBitSet( 256 ); + world->debugContactSet = b3CreateBitSet( 256 ); + world->debugIslandSet = b3CreateBitSet( 256 ); + world->createDebugShape = def->createDebugShape; + world->destroyDebugShape = def->destroyDebugShape; + world->userDebugShapeContext = def->userDebugShapeContext; + + // add one to worldId so that 0 represents a null b3WorldId + return (b3WorldId){ (uint16_t)( worldId + 1 ), world->generation }; +} + +void b3DestroyWorld( b3WorldId worldId ) +{ + b3AtomicFetchAddInt( &b3_worldCount, -1 ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + world->locked = true; + + // Detach any recording before teardown. The user owns and frees the recording buffer. + b3StopRecordingInternal( world ); + + if ( world->scheduler != NULL ) + { + b3DestroyScheduler( world->scheduler ); + world->scheduler = NULL; + } + + b3DestroyBitSet( &world->debugBodySet ); + b3DestroyBitSet( &world->debugJointSet ); + b3DestroyBitSet( &world->debugContactSet ); + b3DestroyBitSet( &world->debugIslandSet ); + + b3DestroyWorkerContexts( world ); + + b3Array_Destroy( world->bodyMoveEvents ); + b3Array_Destroy( world->sensorBeginEvents ); + b3Array_Destroy( world->sensorEndEvents[0] ); + b3Array_Destroy( world->sensorEndEvents[1] ); + b3Array_Destroy( world->contactBeginEvents ); + b3Array_Destroy( world->contactEndEvents[0] ); + b3Array_Destroy( world->contactEndEvents[1] ); + b3Array_Destroy( world->contactHitEvents ); + b3Array_Destroy( world->jointEvents ); + + int sensorCount = world->sensors.count; + for ( int i = 0; i < sensorCount; ++i ) + { + b3Array_Destroy( world->sensors.data[i].hits ); + b3Array_Destroy( world->sensors.data[i].overlaps1 ); + b3Array_Destroy( world->sensors.data[i].overlaps2 ); + } + + b3Array_Destroy( world->sensors ); + b3Array_Destroy( world->bodies ); + + int shapeCapacity = world->shapes.count; + b3Shape* shapes = world->shapes.data; + for ( int i = 0; i < shapeCapacity; ++i ) + { + b3Shape* shape = shapes + i; + if ( shape->id != B3_NULL_INDEX ) + { + b3DestroyShapeAllocations( world, shapes + i ); + } + } + + int contactCapacity = world->contacts.count; + b3Contact* contacts = world->contacts.data; + for ( int i = 0; i < contactCapacity; ++i ) + { + b3Contact* contact = contacts + i; + if ( contact->contactId != B3_NULL_INDEX ) + { + if ( contact->flags & b3_simMeshContact ) + { + b3Array_Destroy( contact->meshContact.triangleCache ); + } + } + } + + // Destroying every shape above released all hull references, so the database is empty. + B3_ASSERT( b3HullMap_size( (b3HullMap*)world->hullDatabase ) == 0 ); + b3HullMap_cleanup( world->hullDatabase ); + b3Free( world->hullDatabase, sizeof( b3HullMap ) ); + world->hullDatabase = NULL; + + b3DestroyNameCache( &world->names ); + + b3Array_Destroy( world->shapes ); + b3Array_Destroy( world->contacts ); + b3Array_Destroy( world->joints ); + + for ( int i = 0; i < world->islands.count; ++i ) + { + b3Array_Destroy( world->islands.data[i].bodies ); + b3Array_Destroy( world->islands.data[i].contacts ); + b3Array_Destroy( world->islands.data[i].joints ); + } + b3Array_Destroy( world->islands ); + + // Destroy solver sets + int setCapacity = world->solverSets.count; + for ( int i = 0; i < setCapacity; ++i ) + { + b3SolverSet* set = world->solverSets.data + i; + if ( set->setIndex != B3_NULL_INDEX ) + { + b3DestroySolverSet( world, i ); + } + } + + b3Array_Destroy( world->solverSets ); + + b3DestroyGraph( &world->constraintGraph ); + b3DestroyBroadPhase( &world->broadPhase ); + + b3DestroyIdPool( &world->bodyIdPool ); + b3DestroyIdPool( &world->shapeIdPool ); + b3DestroyIdPool( &world->contactIdPool ); + b3DestroyIdPool( &world->jointIdPool ); + b3DestroyIdPool( &world->islandIdPool ); + b3DestroyIdPool( &world->solverSetIdPool ); + + for ( int i = 0; i < world->manifoldAllocators.count; ++i ) + { + b3DestroyBlockAllocator( world->manifoldAllocators.data + i ); + } + b3Array_Destroy( world->manifoldAllocators ); + b3DestroyMutex( world->manifoldAllocatorMutex ); + + b3DestroyStack( &world->stack ); + + // Wipe world but preserve generation + uint16_t generation = world->generation; + memset( world, 0, sizeof( b3World ) ); + world->generation = generation + 1; + + // b3Log( "Destroyed world %d", worldId.index1 - 1 ); +} + +int b3GetWorldCount( void ) +{ + return b3AtomicLoadInt( &b3_worldCount ); +} + +int b3GetMaxWorldCount( void ) +{ + return b3_maxWorldCount; +} + +// Issues T0 prefetches across the cache lines of a b3Contact (216 B / 4 lines). +// Used to hide the random-access latency of contact lookups while we work on an +// earlier index. +static inline void b3PrefetchContact( const b3Contact* contact ) +{ + const char* p = (const char*)contact; + b3Prefetch( p ); + b3Prefetch( p + 64 ); + b3Prefetch( p + 128 ); + b3Prefetch( p + 192 ); +} + +static void b3CollideTask( int startIndex, int endIndex, int workerIndex, void* context ) +{ + b3TracyCZoneNC( collide_task, "Collide Task", b3_colorDodgerBlue, true ); + + b3StepContext* stepContext = (b3StepContext*)context; + b3World* world = stepContext->world; + b3ConstraintGraph* graph = &world->constraintGraph; + b3TaskContext* taskContext = world->taskContexts.data + workerIndex; + int* contactIndices = stepContext->awakeContactIndices; + b3Contact* contacts = world->contacts.data; + b3Shape* shapes = world->shapes.data; + b3Body* bodies = world->bodies.data; + b3BodySim* awakeSims = world->solverSets.data[b3_awakeSet].bodySims.data; + b3BodySim* staticSims = world->solverSets.data[b3_staticSet].bodySims.data; + + B3_ASSERT( startIndex < endIndex ); + + float recycleDistance = world->contactRecycleDistance; + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + float recycleDistanceNonTouching = b3MinFloat( recycleDistance, speculativeDistance ); + + // Prefetch contact[i + contactPrefetchDistance] each iteration so the random + // 216 B contact load lands in L1 by the time we reach it. Distance picked to + // cover ~200 cycles of memory latency without overshooting the L1 working set. + const int contactPrefetchDistance = 4; + int prefetchEnd = endIndex - contactPrefetchDistance; + + for ( int i = startIndex; i < endIndex; ++i ) + { + if ( i < prefetchEnd ) + { + b3PrefetchContact( contacts + contactIndices[i + contactPrefetchDistance] ); + } + + int contactIndex = contactIndices[i]; + B3_ASSERT( contactIndex < world->contacts.count ); + + b3Contact* contact = contacts + contactIndex; + B3_VALIDATE( contact->contactId == contactIndex ); + + b3Shape* shapeA = shapes + contact->shapeIdA; + b3Shape* shapeB = shapes + contact->shapeIdB; + + // Do proxies still overlap? + bool overlap = b3AABB_Overlaps( shapeA->fatAABB, shapeB->fatAABB ); + if ( overlap == false ) + { + // This contact will be destroyed + contact->flags |= b3_simDisjoint; + contact->flags &= ~b3_simTouchingFlag; + b3SetBit( &taskContext->contactStateBitSet, contactIndex ); + continue; + } + + // Update contact respecting shape/body order (A,B). Bodies behind awake-set + // contacts are always either awake or static - inline b3GetBodySim with that + // invariant to skip the cross-TU call and per-call solverSets indirection. + b3Body* bodyA = bodies + shapeA->bodyId; + b3Body* bodyB = bodies + shapeB->bodyId; + bool isStaticA = bodyA->type == b3_staticBody; + bool isStaticB = bodyB->type == b3_staticBody; + bool wasTouching = ( contact->flags & b3_simTouchingFlag ); + bool isMeshContact = ( contact->flags & b3_simMeshContact ); + b3BodySim* bodySimA; + b3BodySim* bodySimB; + if ( wasTouching ) + { + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyA->setIndex == b3_staticSet ); + B3_ASSERT( bodyB->setIndex == b3_awakeSet || bodyB->setIndex == b3_staticSet ); + bodySimA = ( isStaticA ? staticSims : awakeSims ) + bodyA->localIndex; + bodySimB = ( isStaticB ? staticSims : awakeSims ) + bodyB->localIndex; + } + else + { + // There can be non-touching contacts between awake bodies and sleeping bodies. + { + b3SolverSet* set = b3Array_Get( world->solverSets, bodyA->setIndex ); + bodySimA = b3Array_Get( set->bodySims, bodyA->localIndex ); + } + { + b3SolverSet* set = b3Array_Get( world->solverSets, bodyB->setIndex ); + bodySimB = b3Array_Get( set->bodySims, bodyB->localIndex ); + } + } + + b3WorldTransform transformA = bodySimA->transform; + b3WorldTransform transformB = bodySimB->transform; + + bool isFast = ( bodySimA->flags & b3_isFast ) || ( bodySimB->flags & b3_isFast ); + + // These are used by the contact solver. If the contact is between an awake body + // and a sleeping body and the contact begins to touch, the these will be invalid + // but fixed when linked in the constraint graph. + contact->bodySimIndexA = isStaticA ? B3_NULL_INDEX : bodyA->localIndex; + contact->bodySimIndexB = isStaticB ? B3_NULL_INDEX : bodyB->localIndex; + float recycleTolerance = wasTouching ? recycleDistance : recycleDistanceNonTouching; + + // Contact recycling optimization. Please cite this library if you use this optimization. + // This is inspired by persistent contact manifolds used in some physics engines, such as PhysX. + // However, this allows larger relative motion and has fewer tuning parameters (just one). + if ( ( isFast == false || isMeshContact == false ) && recycleDistance > 0.0f && + ( contact->flags & b3_relativeTransformValid ) && ( contact->flags & b3_contactRecycleFlag ) ) + { + float angleA = b3DotQuat( transformA.q, contact->cachedRotationA ); + float angleB = b3DotQuat( transformB.q, contact->cachedRotationB ); + float angularDistance = b3MinFloat( angleA * angleA, angleB * angleB ); + + b3Transform xf = b3InvMulWorldTransforms( transformA, transformB ); + b3Transform xfc = contact->cachedRelativePose; + b3Vec3 maxExtentA = isStaticA ? b3Vec3_zero : bodySimA->maxExtent; + b3Vec3 maxExtentB = isStaticB ? b3Vec3_zero : bodySimB->maxExtent; + b3Vec3 maxExtent = b3Max( maxExtentA, maxExtentB ); + + // Variation of Conservative Advancement + // distance + 2 * length(modified_cross(|qr.v|, maxExtent)) < recycleTolerance. + // 2*|qr.v| == 2*|sin(theta/2)| ~= theta for small angles. + float distSquared = b3DistanceSquared( xf.p, xfc.p ); + + if ( angularDistance > B3_CONTACT_RECYCLE_ANGULAR_DISTANCE && distSquared < recycleTolerance * recycleTolerance ) + { + float distance = sqrtf( distSquared ); + float slack = recycleTolerance - distance; + + // qr = inv( inv(qA0) * qB0 ) * inv(qA) * qB + // = inv(qB0) * qA0 * inv(qA) * qB + // Suppose A is static + // qr = inv(qB0) * qA0 * inv(qA0) * qB + // = inv(qB0) * qB + // qB = qB0 * qr + // Therefore qr is associated with the local angular velocity of body B when A is static. + b3Quat qr = b3InvMulQuat( xfc.q, xf.q ); + b3Vec3 arc = b3ModifiedCross( b3Abs( qr.v ), maxExtent ); + + float arcSq = 4.0f * b3LengthSquared( arc ); + if ( arcSq < slack * slack ) + { + b3Quat dqA = b3MulQuat( transformA.q, b3Conjugate( contact->cachedRotationA ) ); + b3Quat dqB = b3MulQuat( transformB.q, b3Conjugate( contact->cachedRotationB ) ); + b3Matrix3 matrixA = b3MakeMatrixFromQuat( dqA ); + b3Matrix3 matrixB = b3MakeMatrixFromQuat( dqB ); + + // Minimize round-off + b3Vec3 dc = b3SubPos( bodySimB->center, bodySimA->center ); + + int manifoldCount = contact->manifoldCount; + for ( int manifoldIndex = 0; manifoldIndex < manifoldCount; ++manifoldIndex ) + { + b3Manifold* manifold = contact->manifolds + manifoldIndex; + b3Vec3 normal = manifold->normal; + + int pointCount = manifold->pointCount; + for ( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) + { + // Keep anchors but update separation, same as sub-stepping. This eliminates jitter. + b3ManifoldPoint* mp = manifold->points + pointIndex; + b3Vec3 rA = b3MulMV( matrixA, mp->anchorA ); + b3Vec3 rB = b3MulMV( matrixB, mp->anchorB ); + b3Vec3 dp = b3Add( dc, b3Sub( rB, rA ) ); + mp->separation = mp->baseSeparation + b3Dot( dp, normal ); + mp->persisted = true; + } + } + + // Diagnostics + taskContext->recycledContactCount += 1; + int bucketIndex = b3MinInt( manifoldCount, B3_CONTACT_MANIFOLD_COUNT_BUCKETS - 1 ); + if ( bucketIndex > 0 ) + { + taskContext->manifoldCounts[bucketIndex - 1] += 1; + } + + // Contact is recycled. This also skips updating other aspects of the contact + // such as material parameters. + continue; + } + } + } + + // Caching for contact recycling. + contact->cachedRotationA = transformA.q; + contact->cachedRotationB = transformB.q; + contact->cachedRelativePose = b3InvMulWorldTransforms( transformA, transformB ); + contact->flags |= b3_relativeTransformValid; + + // This updates solid contacts + bool touching = b3UpdateContact( world, workerIndex, contact, shapeA, bodySimA->localCenter, transformA, shapeB, + bodySimB->localCenter, transformB, isFast, taskContext->arena ); + + int bucketIndex = b3MinInt( contact->manifoldCount, B3_CONTACT_MANIFOLD_COUNT_BUCKETS - 1 ); + if ( bucketIndex > 0 ) + { + taskContext->manifoldCounts[bucketIndex - 1] += 1; + } + + // Update the mesh contact spec + if ( touching == true && wasTouching == true && ( contact->flags & b3_simMeshContact ) ) + { + B3_ASSERT( contact->colorIndex != B3_NULL_INDEX ); + B3_ASSERT( 0 <= contact->colorIndex && contact->colorIndex < B3_GRAPH_COLOR_COUNT ); + b3GraphColor* color = graph->colors + contact->colorIndex; + b3ContactSpec* spec = b3Array_Get( color->contacts, contact->localIndex ); + spec->manifoldCount = (uint16_t)contact->manifoldCount; + } + + // State changes that affect island connectivity. Also affects contact events. + if ( touching == true && wasTouching == false ) + { + contact->flags |= b3_simStartedTouching; + b3SetBit( &taskContext->contactStateBitSet, contactIndex ); + } + else if ( touching == false && wasTouching == true ) + { + contact->flags |= b3_simStoppedTouching; + b3SetBit( &taskContext->contactStateBitSet, contactIndex ); + } + + for ( int manifoldIndex = 0; manifoldIndex < contact->manifoldCount; ++manifoldIndex ) + { + b3Manifold* manifold = contact->manifolds + manifoldIndex; + for ( int pointIndex = 0; pointIndex < manifold->pointCount; ++pointIndex ) + { + // Cache separation + b3ManifoldPoint* mp = manifold->points + pointIndex; + mp->baseSeparation = mp->separation; + } + } + } + + b3TracyCZoneEnd( collide_task ); +} + +static void b3AddNonTouchingContact( b3World* world, b3Contact* contact ) +{ + B3_ASSERT( contact->setIndex == b3_awakeSet ); + b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet ); + contact->colorIndex = B3_NULL_INDEX; + contact->localIndex = set->contactIndices.count; + contact->bodySimIndexA = B3_NULL_INDEX; + contact->bodySimIndexB = B3_NULL_INDEX; + b3Array_Push( set->contactIndices, contact->contactId ); +} + +static void b3RemoveNonTouchingContact( b3World* world, int setIndex, int localIndex ) +{ + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + int movedIndex = b3Array_RemoveSwap( set->contactIndices, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + int movedContactIndex = set->contactIndices.data[localIndex]; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex ); + B3_ASSERT( movedContact->setIndex == setIndex ); + B3_ASSERT( movedContact->colorIndex == B3_NULL_INDEX ); + B3_ASSERT( movedContact->localIndex == movedIndex ); + movedContact->localIndex = localIndex; + } +} + +// Narrow-phase collision +static void b3Collide( b3StepContext* context ) +{ + b3World* world = context->world; + + B3_ASSERT( world->workerCount > 0 ); + + b3TracyCZoneNC( collide, "Collide", b3_colorDarkOrchid, true ); + + // Gather contacts from all the graph colors into a single array for easier parallel-for + int touchingCount = 0; + + b3GraphColor* graphColors = world->constraintGraph.colors; + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT; ++i ) + { + touchingCount += graphColors[i].convexContacts.count + graphColors[i].contacts.count; + } + + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + int nonTouchingCount = awakeSet->contactIndices.count; + + int contactCount = touchingCount + nonTouchingCount; + + if ( contactCount == 0 ) + { + b3TracyCZoneEnd( collide ); + return; + } + + int* contactIndices = (int*)b3StackAlloc( &world->stack, contactCount * sizeof( int ), "contact indices" ); + + int contactIndex = 0; + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT; ++i ) + { + b3GraphColor* color = graphColors + i; + int count = color->convexContacts.count; + for ( int j = 0; j < count; ++j ) + { + contactIndices[contactIndex] = color->convexContacts.data[j]; + contactIndex += 1; + } + + count = color->contacts.count; + for ( int j = 0; j < count; ++j ) + { + contactIndices[contactIndex] = color->contacts.data[j].contactId; + contactIndex += 1; + } + } + + B3_ASSERT( contactIndex == touchingCount ); + + if ( nonTouchingCount > 0 ) + { + int* nonTouchingIndices = awakeSet->contactIndices.data; + memcpy( contactIndices + touchingCount, nonTouchingIndices, nonTouchingCount * sizeof( int ) ); + } + + context->awakeContactIndices = contactIndices; + + // Contact bit set on ids because contact pointers are unstable as they move between touching and not touching. + int contactIdCapacity = b3GetIdCapacity( &world->contactIdPool ); + for ( int i = 0; i < world->workerCount; ++i ) + { + b3TaskContext* taskContext = world->taskContexts.data + i; + b3SetBitCountAndClear( &taskContext->contactStateBitSet, contactIdCapacity ); + taskContext->satCallCount = 0; + taskContext->satCacheHitCount = 0; + taskContext->recycledContactCount = 0; + memset( taskContext->manifoldCounts, 0, sizeof( taskContext->manifoldCounts ) ); + } + + // Task should take at least 40us on a 4GHz CPU (10K cycles) + int minRange = 20; + b3ParallelFor( world, b3CollideTask, contactCount, minRange, context, "collide" ); + + b3StackFree( &world->stack, contactIndices ); + context->awakeContactIndices = NULL; + contactIndices = NULL; + + // Serially update contact state + // todo bring this zone together with island merge + b3TracyCZoneNC( contact_state, "Contact State", b3_colorLightSlateGray, true ); + + int satMultiplier = context->dt > 0.0f ? 1 : 0; + + // Bitwise OR all contact bits + b3BitSet* bitSet = &world->taskContexts.data[0].contactStateBitSet; + world->satCallCount = satMultiplier * world->taskContexts.data[0].satCallCount; + world->satCacheHitCount = satMultiplier * world->taskContexts.data[0].satCacheHitCount; + memcpy( world->manifoldCounts, world->taskContexts.data[0].manifoldCounts, + B3_CONTACT_MANIFOLD_COUNT_BUCKETS * sizeof( int ) ); + + for ( int i = 1; i < world->workerCount; ++i ) + { + b3InPlaceUnion( bitSet, &world->taskContexts.data[i].contactStateBitSet ); + world->satCallCount += world->taskContexts.data[i].satCallCount; + world->satCacheHitCount += world->taskContexts.data[i].satCacheHitCount; + for ( int j = 0; j < B3_CONTACT_MANIFOLD_COUNT_BUCKETS; ++j ) + { + world->manifoldCounts[j] += world->taskContexts.data[i].manifoldCounts[j]; + } + } + + // Release per-step overflow blocks and grow the backing capacity if last + // step's demand exceeded it. Contact processing is the only consumer of + // these arenas and is finished by this point. + for ( int i = 0; i < world->workerCount; ++i ) + { + b3ArenaSync( &world->taskContexts.data[i].arena ); + } + + int endEventArrayIndex = world->endEventArrayIndex; + + const b3Shape* shapes = world->shapes.data; + uint16_t worldId = world->worldId; + + // Process contact state changes. Iterate over set bits + for ( uint32_t k = 0; k < bitSet->blockCount; ++k ) + { + uint64_t bits = bitSet->bits[k]; + while ( bits != 0 ) + { + uint32_t ctz = b3CTZ64( bits ); + int contactId = (int)( 64 * k + ctz ); + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + B3_ASSERT( contact->setIndex == b3_awakeSet ); + + const b3Shape* shapeA = shapes + contact->shapeIdA; + const b3Shape* shapeB = shapes + contact->shapeIdB; + b3ShapeId shapeIdA = { shapeA->id + 1, worldId, shapeA->generation }; + b3ShapeId shapeIdB = { shapeB->id + 1, worldId, shapeB->generation }; + b3ContactId contactFullId = { + .index1 = contactId + 1, + .world0 = worldId, + .padding = 0, + .generation = contact->generation, + }; + uint32_t flags = contact->flags; + + if ( flags & b3_simDisjoint ) + { + // Bounding boxes no longer overlap + b3DestroyContact( world, contact, false ); + contact = NULL; + } + else if ( flags & b3_simStartedTouching ) + { + B3_ASSERT( contact->islandId == B3_NULL_INDEX ); + + if ( flags & b3_contactEnableContactEvents ) + { + b3ContactBeginTouchEvent event = { shapeIdA, shapeIdB, contactFullId }; + b3Array_Push( world->contactBeginEvents, event ); + } + + B3_ASSERT( contact->manifoldCount > 0 ); + B3_ASSERT( contact->setIndex == b3_awakeSet ); + + // Link first because this wakes colliding bodies and ensures the body sims + // are in the correct place. + contact->flags &= ~b3_simStartedTouching; + contact->flags |= b3_contactTouchingFlag; + b3LinkContact( world, contact ); + + // Make sure these didn't change + B3_ASSERT( contact->colorIndex == B3_NULL_INDEX ); + + // Contact sim pointer may have become orphaned due to awake set growth, + // so I just need to refresh it. + + int oldLocalIndex = contact->localIndex; + + b3AddContactToGraph( world, contact ); + b3RemoveNonTouchingContact( world, b3_awakeSet, oldLocalIndex ); + } + else if ( flags & b3_simStoppedTouching ) + { + contact->flags &= ~b3_simStoppedTouching; + contact->flags &= ~b3_contactTouchingFlag; + + if ( contact->flags & b3_contactEnableContactEvents ) + { + b3ContactEndTouchEvent event = { shapeIdA, shapeIdB, contactFullId }; + b3Array_Push( world->contactEndEvents[endEventArrayIndex], event ); + } + + B3_ASSERT( contact->manifoldCount == 0 ); + + // Cache these here for the remove below + int colorIndex = contact->colorIndex; + int localIndex = contact->localIndex; + + b3UnlinkContact( world, contact ); + int bodyIdA = contact->edges[0].bodyId; + int bodyIdB = contact->edges[1].bodyId; + + b3AddNonTouchingContact( world, contact ); + + bool isMeshContact = contact->flags & b3_simMeshContact; + b3RemoveContactFromGraph( world, bodyIdA, bodyIdB, colorIndex, localIndex, isMeshContact ); + contact = NULL; + } + + // Clear the smallest set bit + bits = bits & ( bits - 1 ); + } + } + + b3ValidateSolverSets( world ); + b3ValidateContacts( world ); + + b3TracyCZoneEnd( contact_state ); + b3TracyCZoneEnd( collide ); +} + +void b3World_Step( b3WorldId worldId, float timeStep, int subStepCount ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, Step, worldId, timeStep, subStepCount ); + + world->locked = true; + + b3TracyCZoneNC( world_step, "Step", b3_colorBox2DGreen, true ); + + // Clear debug buffers + for ( int i = 0; i < world->workerCount; ++i ) + { + world->taskContexts.data[i].pointCount = 0; + world->taskContexts.data[i].lineCount = 0; + } + + // Prepare to capture events + // Ensure user does not access stale data if there is an early return + b3Array_Clear( world->bodyMoveEvents ); + b3Array_Clear( world->sensorBeginEvents ); + b3Array_Clear( world->contactBeginEvents ); + b3Array_Clear( world->contactHitEvents ); + b3Array_Clear( world->jointEvents ); + + world->profile = (b3Profile){ 0 }; + + world->activeTaskCount = 0; + world->taskCount = 0; + + if ( world->scheduler != NULL ) + { + b3ResetScheduler( world->scheduler ); + } + + uint64_t stepTicks = b3GetTicks(); + + { + b3Capacity* c = &world->maxCapacity; + c->staticShapeCount = b3MaxInt( c->staticShapeCount, world->broadPhase.trees[b3_staticBody].proxyCount ); + c->dynamicShapeCount = b3MaxInt( c->dynamicShapeCount, world->broadPhase.trees[b3_dynamicBody].proxyCount ); + + int staticBodyCount = world->solverSets.data[b3_staticSet].bodySims.count; + c->staticBodyCount = b3MaxInt( c->staticBodyCount, staticBodyCount ); + + // this includes kinematic bodies + int totalBodyCount = b3GetIdCount( &world->bodyIdPool ); + c->dynamicBodyCount = b3MaxInt( c->dynamicBodyCount, totalBodyCount - staticBodyCount ); + + int totalContactCount = b3GetIdCount( &world->contactIdPool ); + c->contactCount = b3MaxInt( c->contactCount, totalContactCount ); + } + + // Update collision pairs and create contacts + { + uint64_t pairTicks = b3GetTicks(); + b3UpdateBroadPhasePairs( world ); + world->profile.pairs = b3GetMilliseconds( pairTicks ); + } + + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + + b3StepContext context = { 0 }; + context.world = world; + context.states = awakeSet->bodyStates.data; + context.dt = timeStep; + context.subStepCount = b3MaxInt( 1, subStepCount ); + + if ( timeStep > 0.0f ) + { + context.inv_dt = 1.0f / timeStep; + context.h = timeStep / context.subStepCount; + context.inv_h = context.subStepCount * context.inv_dt; + } + else + { + context.inv_dt = 0.0f; + context.h = 0.0f; + context.inv_h = 0.0f; + } + + world->inv_h = context.inv_h; + world->inv_dt = context.inv_dt; + + // Hertz values get reduced for large time steps + float contactHertz = b3MinFloat( world->contactHertz, 0.125f * context.inv_h ); + context.contactSoftness = b3MakeSoft( contactHertz, world->contactDampingRatio, context.h ); + context.staticSoftness = b3MakeSoft( 2.0f * contactHertz, 0.5f * world->contactDampingRatio, context.h ); + + context.restitutionThreshold = world->restitutionThreshold; + context.maxLinearVelocity = world->maxLinearSpeed; + context.enableWarmStarting = world->enableWarmStarting; + + // Narrow phase : update contacts + { + uint64_t collideTicks = b3GetTicks(); + b3Collide( &context ); + world->profile.collide = b3GetMilliseconds( collideTicks ); + } + + // Integrate velocities, solve velocity constraints, and integrate positions. + if ( timeStep > 0.0f ) + { + uint64_t solveTicks = b3GetTicks(); + b3Solve( world, &context ); + world->profile.solve = b3GetMilliseconds( solveTicks ); + } + + // Finish the tree task in case b3Solve didn't finish it + if ( world->userTreeTask ) + { + world->finishTaskFcn( world->userTreeTask, world->userTaskContext ); + world->userTreeTask = NULL; + world->activeTaskCount -= 1; + } + + // Update sensors + { + uint64_t sensorTicks = b3GetTicks(); + b3OverlapSensors( world ); + world->profile.sensors = b3GetMilliseconds( sensorTicks ); + } + + world->profile.step = b3GetMilliseconds( stepTicks ); + + B3_ASSERT( world->stack.allocation == 0 ); + + // Ensure stack is large enough + b3GrowStack( &world->stack ); + + // Make sure all tasks that were started were also finished + B3_ASSERT( world->activeTaskCount == 0 ); + + // Swap end event array buffers + world->endEventArrayIndex = 1 - world->endEventArrayIndex; + b3Array_Clear( world->sensorEndEvents[world->endEventArrayIndex] ); + b3Array_Clear( world->contactEndEvents[world->endEventArrayIndex] ); + world->locked = false; + + if ( world->recording != NULL ) + { + uint64_t hash = b3HashWorldState( world ); + b3RecArgs_StateHash stateHash = { worldId, hash }; + b3RecWrite_StateHash( world->recording, &stateHash ); + + // Fold this step's world bounds into the recording so a viewer can frame the whole motion. + b3AABB worldBounds = { 0 }; + bool haveBounds = false; + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DynamicTree* tree = world->broadPhase.trees + i; + if ( b3DynamicTree_GetProxyCount( tree ) == 0 ) + { + continue; + } + b3AABB bounds = b3DynamicTree_GetRootBounds( tree ); + worldBounds = haveBounds ? b3AABB_Union( worldBounds, bounds ) : bounds; + haveBounds = true; + } + if ( haveBounds ) + { + b3RecAccumulateBounds( world->recording, worldBounds ); + } + } + + b3TracyCZoneEnd( world_step ); + b3TracyCFrame; +} + +typedef struct DrawContext +{ + b3World* world; + b3DebugDraw* draw; +} DrawContext; + +static bool DrawQueryCallback( int proxyId, uint64_t userData, void* context ) +{ + int shapeId = (int)userData; + + B3_UNUSED( proxyId ); + + struct DrawContext* drawContext = (DrawContext*)context; + b3World* world = drawContext->world; + b3DebugDraw* draw = drawContext->draw; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + B3_ASSERT( shape->id == shapeId ); + + b3SetBit( &world->debugBodySet, shape->bodyId ); + + if ( draw->drawShapes ) + { + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + + b3HexColor color; + + const b3SurfaceMaterial* surfaceMaterial = b3GetShapeMaterials( shape ); + if ( surfaceMaterial[0].customColor != 0 ) + { + // May already carry a packed material preset, pass through unchanged + color = (b3HexColor)surfaceMaterial[0].customColor; + } + else + { + // Hue carries the state, material carries its energy. Calm matte for the + // resting masses, glossy for fast bodies, metallic for the driven kinematic. + // Diagnostic states keep a saturated hue and the default material so they pop. + b3HexColor rgb; + b3DebugMaterial material = b3_debugMaterialDefault; + + if ( body->type == b3_dynamicBody && body->mass == 0.0f ) + { + // Bad body + rgb = b3_colorRed; + } + else if ( body->setIndex == b3_disabledSet ) + { + rgb = b3_colorSlateGray; + } + else if ( shape->sensorIndex != B3_NULL_INDEX ) + { + rgb = b3_colorWheat; + } + else if ( body->flags & b3_hadTimeOfImpact ) + { + rgb = b3_colorLime; + } + else if ( ( bodySim->flags & b3_isBullet ) && body->setIndex == b3_awakeSet ) + { + rgb = b3_colorTurquoise; + } + else if ( body->flags & b3_isSpeedCapped ) + { + rgb = b3_colorYellow; + } + else if ( bodySim->flags & b3_isFast ) + { + rgb = b3_colorOrange; + material = b3_debugMaterialGlossy; + } + else if ( body->type == b3_staticBody ) + { + rgb = b3_colorDarkGray; + material = b3_debugMaterialMatte; + } + else if ( body->type == b3_kinematicBody ) + { + if ( body->setIndex == b3_awakeSet ) + { + rgb = b3_colorSteelBlue; + material = b3_debugMaterialMetallic; + } + else + { + rgb = b3_colorLightSteelBlue; + material = b3_debugMaterialMatte; + } + } + else if ( body->setIndex == b3_awakeSet ) + { + rgb = b3_colorTan; + material = b3_debugMaterialSoft; + } + else + { + rgb = b3_colorLightSlateGray; + material = b3_debugMaterialDead; + } + + color = (b3HexColor)b3MakeDebugColor( rgb, material ); + } + + if ( shape->userShape == NULL && world->createDebugShape != NULL ) + { + b3DebugShape debugShape = { 0 }; + debugShape.shapeId = (b3ShapeId){ + .world0 = world->worldId, + .index1 = shapeId + 1, + .generation = shape->generation, + }; + debugShape.type = shape->type; + + switch ( shape->type ) + { + case b3_capsuleShape: + debugShape.capsule = &shape->capsule; + shape->userShape = world->createDebugShape( &debugShape, world->userDebugShapeContext ); + break; + case b3_compoundShape: + debugShape.compound = shape->compound; + shape->userShape = world->createDebugShape( &debugShape, world->userDebugShapeContext ); + break; + case b3_heightShape: + debugShape.heightField = shape->heightField; + shape->userShape = world->createDebugShape( &debugShape, world->userDebugShapeContext ); + break; + case b3_hullShape: + debugShape.hull = shape->hull; + shape->userShape = world->createDebugShape( &debugShape, world->userDebugShapeContext ); + break; + case b3_meshShape: + debugShape.mesh = &shape->mesh; + shape->userShape = world->createDebugShape( &debugShape, world->userDebugShapeContext ); + break; + case b3_sphereShape: + debugShape.sphere = &shape->sphere; + shape->userShape = world->createDebugShape( &debugShape, world->userDebugShapeContext ); + break; + default: + B3_ASSERT( false ); + break; + } + } + + if ( shape->userShape != NULL ) + { + draw->DrawShapeFcn( shape->userShape, bodySim->transform, color, draw->context ); + } + } + + if ( draw->drawBounds ) + { + draw->DrawBoundsFcn( shape->fatAABB, b3_colorGold, draw->context ); + } + + return true; +} + +void b3World_Draw( b3WorldId worldId, b3DebugDraw* draw, uint64_t maskBits ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_ASSERT( b3IsValidAABB( draw->drawingBounds ) ); + + float lengthScale = b3GetLengthUnitsPerMeter(); + float axisScale = 0.3f * lengthScale; + b3HexColor farColor = b3_colorDarkBlue; + b3HexColor speculativeColor = b3_colorBlue; + b3HexColor addColor = b3_colorLimeGreen; + b3HexColor persistColor = b3_colorLightBlue; + // b3HexColor normalColors[3] = { b3_colorLightGray, b3_colorLightSalmon, b3_colorLightSeaGreen }; + b3HexColor impulseColor = b3_colorMagenta; + b3HexColor frictionColor = b3_colorYellow; + + b3HexColor graphColors[B3_GRAPH_COLOR_COUNT] = { + b3_colorRed, b3_colorOrange, b3_colorYellow, b3_colorGreen, b3_colorCyan, b3_colorBlue, + b3_colorViolet, b3_colorPink, b3_colorChocolate, b3_colorGoldenRod, b3_colorCoral, b3_colorRosyBrown, + b3_colorAqua, b3_colorPeru, b3_colorLime, b3_colorGold, b3_colorPlum, b3_colorSnow, + b3_colorTeal, b3_colorKhaki, b3_colorSalmon, b3_colorPeachPuff, b3_colorHoneyDew, b3_colorBlack, + }; + + int bodyCapacity = b3GetIdCapacity( &world->bodyIdPool ); + b3SetBitCountAndClear( &world->debugBodySet, bodyCapacity ); + + int jointCapacity = b3GetIdCapacity( &world->jointIdPool ); + b3SetBitCountAndClear( &world->debugJointSet, jointCapacity ); + + int contactCapacity = b3GetIdCapacity( &world->contactIdPool ); + b3SetBitCountAndClear( &world->debugContactSet, contactCapacity ); + + int islandCapacity = b3GetIdCapacity( &world->islandIdPool ); + b3SetBitCountAndClear( &world->debugIslandSet, islandCapacity ); + + struct DrawContext drawContext = { world, draw }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DynamicTree_Query( world->broadPhase.trees + i, draw->drawingBounds, maskBits, false, DrawQueryCallback, &drawContext ); + } + + uint32_t wordCount = world->debugBodySet.blockCount; + uint64_t* bits = world->debugBodySet.bits; + for ( uint32_t wordIndex = 0; wordIndex < wordCount; ++wordIndex ) + { + uint64_t word = bits[wordIndex]; + while ( word != 0 ) + { + uint32_t ctz = b3CTZ64( word ); + int bodyId = (int)( 64 * wordIndex + ctz ); + + b3Body* body = b3Array_Get( world->bodies, bodyId ); + b3BodySim* bodySim = b3GetBodySim( world, body ); + + if ( draw->drawBodyNames && body->nameId != B3_NULL_NAME ) + { + b3Vec3 offset = { 0.05f, 0.05f, 0.05f }; + b3WorldTransform transform = { bodySim->center, bodySim->transform.q }; + b3Pos p = b3TransformWorldPoint( transform, offset ); + const char* name = b3FindName( &world->names, body->nameId ); + if ( name != NULL ) + { + draw->DrawStringFcn( p, name, b3_colorOrange, draw->context ); + } + } + + if ( draw->drawMass && body->type == b3_dynamicBody ) + { + b3Vec3 offset = { 0.1f, 0.1f, 0.1f }; + + b3WorldTransform transform = { bodySim->center, bodySim->transform.q }; + draw->DrawTransformFcn( transform, draw->context ); + b3Pos p = b3TransformWorldPoint( transform, offset ); + + char buffer[32]; + snprintf( buffer, 32, " %.2f", body->mass ); + draw->DrawStringFcn( p, buffer, b3_colorWhite, draw->context ); + } + + if ( draw->drawSleep ) + { + b3BodyState* bodyState = b3GetBodyState( world, body ); + + if ( bodyState != NULL ) + { + b3HexColor colors[4] = { b3_colorBlue, b3_colorSkyBlue, b3_colorOrange, b3_colorRed }; + + b3HexColor color = b3_colorBlack; + if ( body->sleepThreshold > 0.0f ) + { + float ratio = body->sleepVelocity / body->sleepThreshold; + int index = b3ClampInt( (int)ratio, 0, 3 ); + color = colors[index]; + } + + b3Pos center = bodySim->center; + draw->DrawPointFcn( center, 10.0f, color, draw->context ); + + b3Vec3 offset = { 0.1f, 0.1f, 0.1f }; + b3Pos p = b3OffsetPos( center, offset ); + + char buffer[32]; + snprintf( buffer, 32, " %.3f", body->sleepVelocity ); + draw->DrawStringFcn( p, buffer, color, draw->context ); + } + } + + if ( draw->drawJoints ) + { + int jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + b3Joint* joint = b3Array_Get( world->joints, jointId ); + + // avoid double draw + if ( b3GetBit( &world->debugJointSet, jointId ) == false ) + { + b3DrawJoint( draw, world, joint ); + b3SetBit( &world->debugJointSet, jointId ); + } + + jointKey = joint->edges[edgeIndex].nextKey; + } + } + + const float speculativeDistance = B3_SPECULATIVE_DISTANCE; + if ( draw->drawContacts && body->type == b3_dynamicBody && body->setIndex == b3_awakeSet ) + { + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + contactKey = contact->edges[edgeIndex].nextKey; + + if ( contact->setIndex != b3_awakeSet || contact->colorIndex == B3_NULL_INDEX ) + { + continue; + } + + // avoid double draw + if ( b3GetBit( &world->debugContactSet, contactId ) == false ) + { + b3Body* bodyA = b3Array_Get( world->bodies, contact->edges[0].bodyId ); + b3BodySim* bodySimA = b3GetBodySim( world, bodyA ); + b3Body* bodyB = b3Array_Get( world->bodies, contact->edges[1].bodyId ); + b3BodySim* bodySimB = b3GetBodySim( world, bodyB ); + + for ( int manifoldIndex = 0; manifoldIndex < contact->manifoldCount; ++manifoldIndex ) + { + const b3Manifold* manifold = contact->manifolds + manifoldIndex; + B3_ASSERT( manifold->pointCount > 0 ); + + b3Vec3 normal = manifold->normal; + + // Average the anchors not the world points so the friction center stays exact far from the origin + b3Pos contactCenter = draw->drawAnchorA == 1 ? bodySimA->center : bodySimB->center; + b3Vec3 anchorSum = b3Vec3_zero; + + const b3ManifoldPoint* points = manifold->points; + for ( int pointIndex = 0; pointIndex < manifold->pointCount; ++pointIndex ) + { + const b3ManifoldPoint* mp = points + pointIndex; + + char buffer[32]; + + b3Vec3 anchor = draw->drawAnchorA == 1 ? mp->anchorA : mp->anchorB; + b3Pos p = b3OffsetPos( contactCenter, anchor ); + + anchorSum = b3Add( anchorSum, anchor ); + + if ( draw->drawContactNormals ) + { + b3Pos p1 = p; + b3Pos p2 = b3OffsetPos( p1, b3MulSV( axisScale, normal ) ); + draw->DrawSegmentFcn( p1, p2, b3_colorLightGray, draw->context ); + + snprintf( buffer, B3_ARRAY_COUNT( buffer ), " %.2f", 100.0f * mp->separation ); + draw->DrawStringFcn( p, buffer, b3_colorWhite, draw->context ); + } + else if ( draw->drawContactForces ) + { + // Hack inv_dt for single step debugging + float inv_dt = world->inv_dt > 0.0f ? world->inv_dt : 60.0f; + // todo validate + // multiply by one-half due to relax iteration + float force = 0.5f * mp->totalNormalImpulse * inv_dt; + b3Pos p1 = p; + b3Pos p2 = b3OffsetPos( p1, b3MulSV( draw->forceScale * force, normal ) ); + draw->DrawSegmentFcn( p1, p2, impulseColor, draw->context ); + snprintf( buffer, B3_ARRAY_COUNT( buffer ), " %.1f", force ); + draw->DrawStringFcn( p1, buffer, b3_colorWhite, draw->context ); + } + else if ( draw->drawContactFeatures ) + { + snprintf( buffer, B3_ARRAY_COUNT( buffer ), " %#X", mp->featureId ); + draw->DrawStringFcn( p, buffer, b3_colorWhite, draw->context ); + } + + if ( draw->drawGraphColors ) + { + // graph color + float pointSize = contact->colorIndex == B3_OVERFLOW_INDEX ? 15.0f : 10.0f; + draw->DrawPointFcn( p, pointSize, graphColors[contact->colorIndex], draw->context ); + // g_draw.DrawString(point->position, "%d", point->color); + } + else if ( mp->persisted == false ) + { + // Add + draw->DrawPointFcn( p, 20.0f, addColor, draw->context ); + } + else if ( mp->separation > speculativeDistance ) + { + // Speculative + draw->DrawPointFcn( p, 10.0f, farColor, draw->context ); + } + else if ( mp->separation > 0.0f ) + { + // Speculative + draw->DrawPointFcn( p, 10.0f, speculativeColor, draw->context ); + } + else + { + // Persist + draw->DrawPointFcn( p, 10.0f, persistColor, draw->context ); + } + } + + if ( draw->drawContactForces ) + { + // Hack inv_dt for single step debugging + float inv_dt = world->inv_dt > 0.0f ? world->inv_dt : 60.0f; + + b3Vec3 avgAnchor = b3MulSV( 1.0f / manifold->pointCount, anchorSum ); + b3Pos p1 = b3OffsetPos( contactCenter, avgAnchor ); + b3Vec3 frictionForce = b3MulSV( 0.5f * inv_dt, manifold->frictionImpulse ); + b3Pos p2 = b3OffsetPos( p1, b3MulSV( draw->forceScale, frictionForce ) ); + draw->DrawSegmentFcn( p1, p2, frictionColor, draw->context ); + draw->DrawPointFcn( p1, 5.0f, frictionColor, draw->context ); + + p1 = b3OffsetPos( p1, b3MulSV( 0.05f * lengthScale, normal ) ); + char buffer[32]; + snprintf( buffer, B3_ARRAY_COUNT( buffer ), "%.2f", b3Length( frictionForce ) ); + draw->DrawStringFcn( p1, buffer, b3_colorWhite, draw->context ); + } + } + + b3SetBit( &world->debugContactSet, contactId ); + } + + contactKey = contact->edges[edgeIndex].nextKey; + } + } + + if ( draw->drawIslands ) + { + int islandId = body->islandId; + if ( islandId != B3_NULL_INDEX && b3GetBit( &world->debugIslandSet, islandId ) == false ) + { + b3Island* island = world->islands.data + islandId; + if ( island->setIndex == B3_NULL_INDEX ) + { + continue; + } + + int shapeCount = 0; + b3AABB aabb = { + .lowerBound = { FLT_MAX, FLT_MAX, FLT_MAX }, + .upperBound = { -FLT_MAX, -FLT_MAX, -FLT_MAX }, + }; + + for ( int b = 0; b < island->bodies.count; ++b ) + { + int islandBodyId = island->bodies.data[b]; + b3Body* islandBody = b3Array_Get( world->bodies, islandBodyId ); + int shapeId = islandBody->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + aabb = b3AABB_Union( aabb, shape->fatAABB ); + shapeCount += 1; + shapeId = shape->nextShapeId; + } + } + + if ( shapeCount > 0 ) + { + draw->DrawBoundsFcn( aabb, b3_colorOrangeRed, draw->context ); + } + + b3SetBit( &world->debugIslandSet, islandId ); + } + } + + // Clear the smallest set bit + word = word & ( word - 1 ); + } + } + + char buffer[32] = { 0 }; + float lengthUnits = b3GetLengthUnitsPerMeter(); + b3Vec3 offset = { 0.002f * lengthUnits, 0.002f * lengthUnits, 0.002f * lengthUnits }; + for ( int i = 0; i < world->workerCount; ++i ) + { + int pointCount = world->taskContexts.data[i].pointCount; + b3DebugPoint* points = world->taskContexts.data[i].points; + for ( int j = 0; j < pointCount; ++j ) + { + b3DebugPoint* point = points + j; + b3Pos p = point->p; + draw->DrawPointFcn( p, 5.0f, point->color, draw->context ); + snprintf( buffer, 32, " %d, %.2f", point->label, point->value ); + b3Pos ps = b3OffsetPos( p, offset ); + draw->DrawStringFcn( ps, buffer, point->color, draw->context ); + } + + int lineCount = world->taskContexts.data[i].lineCount; + b3DebugLine* lines = world->taskContexts.data[i].lines; + for ( int j = 0; j < lineCount; ++j ) + { + b3DebugLine* line = lines + j; + b3Pos p1 = line->p1; + b3Pos p2 = line->p2; + draw->DrawSegmentFcn( p1, p2, line->color, draw->context ); + draw->DrawPointFcn( p1, 10.0f, line->color, draw->context ); + snprintf( buffer, 32, "%d", line->label ); + b3Pos ps = b3OffsetPos( p1, offset ); + draw->DrawStringFcn( ps, buffer, line->color, draw->context ); + } + } +} + +b3AABB b3World_GetBounds( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3AABB){ 0 }; + } + + b3AABB worldBounds = { 0 }; + bool haveBounds = false; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DynamicTree* tree = world->broadPhase.trees + i; + if ( b3DynamicTree_GetProxyCount( tree ) == 0 ) + { + continue; + } + + b3AABB bounds = b3DynamicTree_GetRootBounds( world->broadPhase.trees + i ); + + if ( haveBounds ) + { + worldBounds = b3AABB_Union( worldBounds, bounds ); + } + else + { + worldBounds = bounds; + haveBounds = true; + } + } + + return worldBounds; +} + +b3BodyEvents b3World_GetBodyEvents( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3BodyEvents){ 0 }; + } + + int count = world->bodyMoveEvents.count; + b3BodyEvents events = { world->bodyMoveEvents.data, count }; + return events; +} + +b3SensorEvents b3World_GetSensorEvents( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3SensorEvents){ 0 }; + } + + // Careful to use previous buffer + int endEventArrayIndex = 1 - world->endEventArrayIndex; + + int beginCount = world->sensorBeginEvents.count; + int endCount = world->sensorEndEvents[endEventArrayIndex].count; + + b3SensorEvents events = { + .beginEvents = world->sensorBeginEvents.data, + .endEvents = world->sensorEndEvents[endEventArrayIndex].data, + .beginCount = beginCount, + .endCount = endCount, + }; + return events; +} + +b3ContactEvents b3World_GetContactEvents( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3ContactEvents){ 0 }; + } + + // Careful to use previous buffer + int endEventArrayIndex = 1 - world->endEventArrayIndex; + + int beginCount = world->contactBeginEvents.count; + int endCount = world->contactEndEvents[endEventArrayIndex].count; + int hitCount = world->contactHitEvents.count; + + b3ContactEvents events = { + .beginEvents = world->contactBeginEvents.data, + .endEvents = world->contactEndEvents[endEventArrayIndex].data, + .hitEvents = world->contactHitEvents.data, + .beginCount = beginCount, + .endCount = endCount, + .hitCount = hitCount, + }; + + return events; +} + +b3JointEvents b3World_GetJointEvents( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3JointEvents){ 0 }; + } + + int count = world->jointEvents.count; + b3JointEvents events = { world->jointEvents.data, count }; + return events; +} + +bool b3World_IsValid( b3WorldId id ) +{ + if ( id.index1 < 1 || B3_MAX_WORLDS < id.index1 ) + { + return false; + } + + b3World* world = b3_worlds + ( id.index1 - 1 ); + + if ( world->worldId != id.index1 - 1 ) + { + // world is not allocated + return false; + } + + return id.generation == world->generation; +} + +bool b3Body_IsValid( b3BodyId id ) +{ + if ( B3_MAX_WORLDS <= id.world0 ) + { + // invalid world + return false; + } + + b3World* world = b3_worlds + id.world0; + if ( world->worldId != id.world0 ) + { + // world is free + return false; + } + + if ( id.index1 < 1 || world->bodies.count < id.index1 ) + { + // invalid index + return false; + } + + b3Body* body = world->bodies.data + ( id.index1 - 1 ); + if ( body->setIndex == B3_NULL_INDEX ) + { + // this was freed + return false; + } + + B3_ASSERT( body->localIndex != B3_NULL_INDEX ); + + if ( body->generation != id.generation ) + { + // this id is orphaned + return false; + } + + return true; +} + +bool b3Shape_IsValid( b3ShapeId id ) +{ + if ( B3_MAX_WORLDS <= id.world0 ) + { + return false; + } + + b3World* world = b3_worlds + id.world0; + if ( world->worldId != id.world0 ) + { + // world is free + return false; + } + + int shapeId = id.index1 - 1; + if ( shapeId < 0 || world->shapes.count <= shapeId ) + { + return false; + } + + b3Shape* shape = world->shapes.data + shapeId; + if ( shape->id == B3_NULL_INDEX ) + { + // shape is free + return false; + } + + B3_ASSERT( shape->id == shapeId ); + + return id.generation == shape->generation; +} + +bool b3Joint_IsValid( b3JointId id ) +{ + if ( B3_MAX_WORLDS <= id.world0 ) + { + return false; + } + + b3World* world = b3_worlds + id.world0; + if ( world->worldId != id.world0 ) + { + // world is free + return false; + } + + int jointId = id.index1 - 1; + if ( jointId < 0 || world->joints.count <= jointId ) + { + return false; + } + + b3Joint* joint = world->joints.data + jointId; + if ( joint->jointId == B3_NULL_INDEX ) + { + // joint is free + return false; + } + + B3_ASSERT( joint->jointId == jointId ); + + return id.generation == joint->generation; +} + +bool b3Contact_IsValid( b3ContactId id ) +{ + if ( B3_MAX_WORLDS <= id.world0 ) + { + return false; + } + + b3World* world = b3_worlds + id.world0; + if ( world->worldId != id.world0 ) + { + // world is free + return false; + } + + int contactId = id.index1 - 1; + if ( contactId < 0 || world->contacts.count <= contactId ) + { + return false; + } + + b3Contact* contact = world->contacts.data + contactId; + if ( contact->contactId == B3_NULL_INDEX ) + { + // contact is free + return false; + } + + B3_ASSERT( contact->contactId == contactId ); + + return id.generation == contact->generation; +} + +void b3World_EnableSleeping( b3WorldId worldId, bool flag ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + if ( flag == world->enableSleep ) + { + return; + } + + B3_REC( world, WorldEnableSleeping, worldId, flag ); + + world->enableSleep = flag; + + if ( flag == false ) + { + int setCount = world->solverSets.count; + for ( int i = b3_firstSleepingSet; i < setCount; ++i ) + { + b3SolverSet* set = b3Array_Get( world->solverSets, i ); + if ( set->bodySims.count > 0 ) + { + b3WakeSolverSet( world, i ); + } + } + } +} + +bool b3World_IsSleepingEnabled( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->enableSleep; +} + +void b3World_EnableWarmStarting( b3WorldId worldId, bool flag ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldEnableWarmStarting, worldId, flag ); + + world->enableWarmStarting = flag; +} + +bool b3World_IsWarmStartingEnabled( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->enableWarmStarting; +} + +int b3World_GetAwakeBodyCount( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return 0; + } + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + return awakeSet->bodySims.count; +} + +void b3World_EnableContinuous( b3WorldId worldId, bool flag ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldEnableContinuous, worldId, flag ); + + world->enableContinuous = flag; +} + +bool b3World_IsContinuousEnabled( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->enableContinuous; +} + +void b3World_SetRestitutionThreshold( b3WorldId worldId, float value ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldSetRestitutionThreshold, worldId, value ); + + world->restitutionThreshold = b3ClampFloat( value, 0.0f, FLT_MAX ); +} + +float b3World_GetRestitutionThreshold( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->restitutionThreshold; +} + +void b3World_SetHitEventThreshold( b3WorldId worldId, float value ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldSetHitEventThreshold, worldId, value ); + + world->hitEventThreshold = b3ClampFloat( value, 0.0f, FLT_MAX ); +} + +float b3World_GetHitEventThreshold( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->hitEventThreshold; +} + +void b3World_SetContactTuning( b3WorldId worldId, float hertz, float dampingRatio, float contactSpeed ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldSetContactTuning, worldId, hertz, dampingRatio, contactSpeed ); + + world->contactHertz = b3ClampFloat( hertz, 0.0f, FLT_MAX ); + world->contactDampingRatio = b3ClampFloat( dampingRatio, 0.0f, FLT_MAX ); + world->contactSpeed = b3ClampFloat( contactSpeed, 0.0f, FLT_MAX ); +} + +void b3World_SetContactRecycleDistance( b3WorldId worldId, float recycleDistance ) +{ + b3World* world = b3GetWorldFromId( worldId ); + B3_ASSERT( world->locked == false ); + if ( world->locked ) + { + return; + } + + B3_REC( world, WorldSetContactRecycleDistance, worldId, recycleDistance ); + + world->contactRecycleDistance = b3ClampFloat( recycleDistance, 0.0f, FLT_MAX ); +} + +float b3World_GetContactRecycleDistance( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->contactRecycleDistance; +} + +void b3World_SetMaximumLinearSpeed( b3WorldId worldId, float maximumLinearSpeed ) +{ + B3_ASSERT( b3IsValidFloat( maximumLinearSpeed ) && maximumLinearSpeed > 0.0f ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldSetMaximumLinearSpeed, worldId, maximumLinearSpeed ); + + world->maxLinearSpeed = maximumLinearSpeed; +} + +float b3World_GetMaximumLinearSpeed( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->maxLinearSpeed; +} + +b3Profile b3World_GetProfile( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3Profile){ 0 }; + } + return world->profile; +} + +b3Counters b3World_GetCounters( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3Counters){ 0 }; + } + + b3Counters s = { 0 }; + s.bodyCount = b3GetIdCount( &world->bodyIdPool ); + s.shapeCount = b3GetIdCount( &world->shapeIdPool ); + s.contactCount = b3GetIdCount( &world->contactIdPool ); + s.jointCount = b3GetIdCount( &world->jointIdPool ); + s.islandCount = b3GetIdCount( &world->islandIdPool ); + + b3DynamicTree* staticTree = world->broadPhase.trees + b3_staticBody; + s.staticTreeHeight = b3DynamicTree_GetHeight( staticTree ); + + b3DynamicTree* dynamicTree = world->broadPhase.trees + b3_dynamicBody; + b3DynamicTree* kinematicTree = world->broadPhase.trees + b3_kinematicBody; + s.treeHeight = b3MaxInt( b3DynamicTree_GetHeight( dynamicTree ), b3DynamicTree_GetHeight( kinematicTree ) ); + + s.satCallCount = world->satCallCount; + s.satCacheHitCount = world->satCacheHitCount; + memcpy( s.manifoldCounts, world->manifoldCounts, B3_CONTACT_MANIFOLD_COUNT_BUCKETS * sizeof( int ) ); + s.stackUsed = world->stack.maxAllocation; + s.byteCount = b3GetByteCount(); + s.taskCount = world->taskCount; + + _Static_assert( B3_GRAPH_COLOR_COUNT == sizeof( s.colorCounts ) / sizeof( s.colorCounts[0] ), "colorCounts size mismatch" ); + + s.awakeContactCount = 0; + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT; ++i ) + { + b3GraphColor* color = world->constraintGraph.colors + i; + int colorContactCount = color->convexContacts.count + color->contacts.count; + s.colorCounts[i] = colorContactCount + color->jointSims.count; + s.awakeContactCount += colorContactCount; + } + s.awakeContactCount += world->solverSets.data[b3_awakeSet].contactIndices.count; + + s.recycledContactCount = 0; + s.arenaCapacity = 0; + s.distanceIterations = 0; + s.pushBackIterations = 0; + s.rootIterations = 0; + for ( int i = 0; i < world->workerCount; ++i ) + { + s.recycledContactCount += world->taskContexts.data[i].recycledContactCount; + + s.distanceIterations = b3MaxInt( s.distanceIterations, world->taskContexts.data[i].distanceIterations ); + s.pushBackIterations = b3MaxInt( s.pushBackIterations, world->taskContexts.data[i].pushBackIterations ); + s.rootIterations = b3MaxInt( s.rootIterations, world->taskContexts.data[i].rootIterations ); + + int peak = world->taskContexts.data[i].arena.shared->peakDemand; + if ( peak > s.arenaCapacity ) + { + s.arenaCapacity = peak; + } + } + + return s; +} + +b3Capacity b3World_GetMaxCapacity( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return (b3Capacity){ 0 }; + } + return world->maxCapacity; +} + +void b3World_SetUserData( b3WorldId worldId, void* userData ) +{ + b3World* world = b3GetWorldFromId( worldId ); + world->userData = userData; +} + +void* b3World_GetUserData( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->userData; +} + +void b3World_SetFrictionCallback( b3WorldId worldId, b3FrictionCallback* callback ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + world->frictionCallback = callback != NULL ? callback : b3DefaultFrictionCallback; +} + +void b3World_SetRestitutionCallback( b3WorldId worldId, b3RestitutionCallback* callback ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + world->restitutionCallback = callback != NULL ? callback : b3DefaultRestitutionCallback; +} + +void b3World_SetWorkerCount( b3WorldId worldId, int count ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + if ( count == world->workerCount ) + { + return; + } + + b3DestroyWorkerContexts( world ); + world->workerCount = b3ClampInt( count, 1, B3_MAX_WORKERS ); + b3CreateWorkerContexts( world ); +} + +int b3World_GetWorkerCount( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return 0; + } + + return world->workerCount; +} + +void b3World_StartRecording( b3WorldId worldId, b3Recording* recording ) +{ + // Must be a step boundary, so refuse a locked world + b3World* world = b3GetUnlockedWorldFromId( worldId ); + + if ( world == NULL || recording == NULL || world->recording != NULL ) + { + return; + } + + b3StartRecordingIntoBuffer( world, recording ); +} + +void b3World_StopRecording( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + b3StopRecordingInternal( world ); +} + +void b3World_DumpMemoryStats( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + // Large worlds can exceed 2GB, sum in 64 bits + uint64_t total = 0; + + // id pools + int bodyIdBytes = b3GetIdBytes( &world->bodyIdPool ); + int solverSetIdBytes = b3GetIdBytes( &world->solverSetIdPool ); + int jointIdBytes = b3GetIdBytes( &world->jointIdPool ); + int contactIdBytes = b3GetIdBytes( &world->contactIdPool ); + int islandIdBytes = b3GetIdBytes( &world->islandIdPool ); + int shapeIdBytes = b3GetIdBytes( &world->shapeIdPool ); + total += (uint64_t)bodyIdBytes + solverSetIdBytes + jointIdBytes + contactIdBytes + islandIdBytes + shapeIdBytes; + + b3Log( "id pools" ); + b3Log( "body ids: %d", bodyIdBytes ); + b3Log( "solver set ids: %d", solverSetIdBytes ); + b3Log( "joint ids: %d", jointIdBytes ); + b3Log( "contact ids: %d", contactIdBytes ); + b3Log( "island ids: %d", islandIdBytes ); + b3Log( "shape ids: %d", shapeIdBytes ); + + // Islands own per-island body/contact/joint link arrays + int islandLinkBytes = 0; + for ( int i = 0; i < world->islands.count; ++i ) + { + b3Island* island = world->islands.data + i; + islandLinkBytes += b3Array_ByteCount( island->bodies ); + islandLinkBytes += b3Array_ByteCount( island->contacts ); + islandLinkBytes += b3Array_ByteCount( island->joints ); + } + + // world arrays + int bodyArrayBytes = b3Array_ByteCount( world->bodies ); + int solverSetArrayBytes = b3Array_ByteCount( world->solverSets ); + int jointArrayBytes = b3Array_ByteCount( world->joints ); + int contactArrayBytes = b3Array_ByteCount( world->contacts ); + int islandArrayBytes = b3Array_ByteCount( world->islands ); + int shapeArrayBytes = b3Array_ByteCount( world->shapes ); + int sensorArrayBytes = b3Array_ByteCount( world->sensors ); + total += (uint64_t)bodyArrayBytes + solverSetArrayBytes + jointArrayBytes + contactArrayBytes + islandArrayBytes + + islandLinkBytes + shapeArrayBytes + sensorArrayBytes; + + b3Log( "world arrays" ); + b3Log( "bodies: %d", bodyArrayBytes ); + b3Log( "solver sets: %d", solverSetArrayBytes ); + b3Log( "joints: %d", jointArrayBytes ); + b3Log( "contacts: %d", contactArrayBytes ); + b3Log( "islands: %d", islandArrayBytes ); + b3Log( "island links: %d", islandLinkBytes ); + b3Log( "shapes: %d", shapeArrayBytes ); + b3Log( "sensors: %d", sensorArrayBytes ); + + // Sensors own overlap tracking arrays. The sensor array is dense. + int sensorOverlapBytes = 0; + for ( int i = 0; i < world->sensors.count; ++i ) + { + b3Sensor* sensor = world->sensors.data + i; + sensorOverlapBytes += b3Array_ByteCount( sensor->hits ); + sensorOverlapBytes += b3Array_ByteCount( sensor->overlaps1 ); + sensorOverlapBytes += b3Array_ByteCount( sensor->overlaps2 ); + } + total += sensorOverlapBytes; + + b3Log( "owned arrays" ); + b3Log( "sensor overlaps: %d", sensorOverlapBytes ); + + // Shared hull database. The map owns a combined bucket and metadata allocation + // plus the small map struct. Each stored key is an owned clone sized by byteCount. + b3HullMap* hullDatabase = world->hullDatabase; + int hullCount = (int)b3HullMap_size( hullDatabase ); + int hullBucketCount = (int)b3HullMap_bucket_count( hullDatabase ); + uint64_t hullMapBytes = b3HullMapByteCount( hullDatabase ); + uint64_t hullDataBytes = 0; + for ( b3HullMap_itr itr = b3HullMap_first( hullDatabase ); b3HullMap_is_end( itr ) == false; itr = b3HullMap_next( itr ) ) + { + hullDataBytes += itr.data->key->byteCount; + } + total += hullMapBytes + hullDataBytes; + + b3Log( "hulls" ); + b3Log( "database: %d (%d, %d)", (int)hullMapBytes, hullCount, hullBucketCount ); + b3Log( "hull data: %d", (int)hullDataBytes ); + + // broad-phase + int staticTreeBytes = b3DynamicTree_GetByteCount( world->broadPhase.trees + b3_staticBody ); + int kinematicTreeBytes = b3DynamicTree_GetByteCount( world->broadPhase.trees + b3_kinematicBody ); + int dynamicTreeBytes = b3DynamicTree_GetByteCount( world->broadPhase.trees + b3_dynamicBody ); + int movedBytes = 0; + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + movedBytes += b3GetBitSetBytes( &world->broadPhase.movedProxies[i] ); + } + int moveArrayBytes = b3Array_ByteCount( world->broadPhase.moveArray ); + b3HashSet* pairSet = &world->broadPhase.pairSet; + int pairSetBytes = b3GetHashSetBytes( pairSet ); + total += (uint64_t)staticTreeBytes + kinematicTreeBytes + dynamicTreeBytes + movedBytes + moveArrayBytes + pairSetBytes; + + b3Log( "broad-phase" ); + b3Log( "static tree: %d", staticTreeBytes ); + b3Log( "kinematic tree: %d", kinematicTreeBytes ); + b3Log( "dynamic tree: %d", dynamicTreeBytes ); + b3Log( "movedProxies: %d", movedBytes ); + b3Log( "moveArray: %d", moveArrayBytes ); + b3Log( "pairSet: %d (%d, %d)", pairSetBytes, pairSet->count, pairSet->capacity ); + + // Manifold block allocators, one per manifold point count + int manifoldArrayBytes = b3Array_ByteCount( world->manifoldAllocators ); + int manifoldBlockBytes = 0; + for ( int i = 0; i < world->manifoldAllocators.count; ++i ) + { + b3BlockAllocator* allocator = world->manifoldAllocators.data + i; + manifoldBlockBytes += b3Array_ByteCount( allocator->blocks ); + manifoldBlockBytes += allocator->blocks.count * B3_BLOCK_SIZE * allocator->elementSize; + } + total += (uint64_t)manifoldArrayBytes + manifoldBlockBytes; + + b3Log( "manifold allocators" ); + b3Log( "allocator array: %d", manifoldArrayBytes ); + b3Log( "blocks: %d", manifoldBlockBytes ); + + // solver sets + int bodySimCapacity = 0; + int bodyStateCapacity = 0; + int jointSimCapacity = 0; + int contactIndexCapacity = 0; + int islandSimCapacity = 0; + int solverSetCapacity = world->solverSets.count; + for ( int i = 0; i < solverSetCapacity; ++i ) + { + b3SolverSet* set = world->solverSets.data + i; + if ( set->setIndex == B3_NULL_INDEX ) + { + continue; + } + + bodySimCapacity += set->bodySims.capacity; + bodyStateCapacity += set->bodyStates.capacity; + jointSimCapacity += set->jointSims.capacity; + contactIndexCapacity += set->contactIndices.capacity; + islandSimCapacity += set->islandSims.capacity; + } + + int setBodySimBytes = bodySimCapacity * (int)sizeof( b3BodySim ); + int setBodyStateBytes = bodyStateCapacity * (int)sizeof( b3BodyState ); + int setJointSimBytes = jointSimCapacity * (int)sizeof( b3JointSim ); + int setContactSimBytes = contactIndexCapacity * (int)sizeof( int ); + int setIslandSimBytes = islandSimCapacity * (int)sizeof( b3IslandSim ); + total += (uint64_t)setBodySimBytes + setBodyStateBytes + setJointSimBytes + setContactSimBytes + setIslandSimBytes; + + b3Log( "solver sets" ); + b3Log( "body sim: %d", setBodySimBytes ); + b3Log( "body state: %d", setBodyStateBytes ); + b3Log( "joint sim: %d", setJointSimBytes ); + b3Log( "contact sim: %d", setContactSimBytes ); + b3Log( "island sim: %d", setIslandSimBytes ); + + // constraint graph + int bodyBitSetBytes = 0; + int graphContactBytes = 0; + int graphJointSimBytes = 0; + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT; ++i ) + { + b3GraphColor* c = world->constraintGraph.colors + i; + bodyBitSetBytes += b3GetBitSetBytes( &c->bodySet ); + graphContactBytes += b3Array_ByteCount( c->convexContacts ) + b3Array_ByteCount( c->contacts ); + graphJointSimBytes += b3Array_ByteCount( c->jointSims ); + } + total += (uint64_t)bodyBitSetBytes + graphJointSimBytes + graphContactBytes; + + b3Log( "constraint graph" ); + b3Log( "body bit sets: %d", bodyBitSetBytes ); + b3Log( "joint sim: %d", graphJointSimBytes ); + b3Log( "contact sim: %d", graphContactBytes ); + + // Per worker task storage and its bit sets + int taskContextBytes = b3Array_ByteCount( world->taskContexts ); + for ( int i = 0; i < world->taskContexts.count; ++i ) + { + b3TaskContext* taskContext = world->taskContexts.data + i; + taskContextBytes += b3Array_ByteCount( taskContext->sensorHits ); + taskContextBytes += b3GetBitSetBytes( &taskContext->contactStateBitSet ); + taskContextBytes += b3GetBitSetBytes( &taskContext->jointStateBitSet ); + taskContextBytes += b3GetBitSetBytes( &taskContext->hitEventBitSet ); + taskContextBytes += b3GetBitSetBytes( &taskContext->enlargedSimBitSet ); + taskContextBytes += b3GetBitSetBytes( &taskContext->awakeIslandBitSet ); + } + + int sensorTaskContextBytes = b3Array_ByteCount( world->sensorTaskContexts ); + for ( int i = 0; i < world->sensorTaskContexts.count; ++i ) + { + b3SensorTaskContext* taskContext = world->sensorTaskContexts.data + i; + sensorTaskContextBytes += b3GetBitSetBytes( &taskContext->eventBits ); + } + total += (uint64_t)taskContextBytes + sensorTaskContextBytes; + + b3Log( "task contexts" ); + b3Log( "worker: %d", taskContextBytes ); + b3Log( "sensor: %d", sensorTaskContextBytes ); + + // Double buffered event arrays + int eventBytes = 0; + eventBytes += b3Array_ByteCount( world->bodyMoveEvents ); + eventBytes += b3Array_ByteCount( world->sensorBeginEvents ); + eventBytes += b3Array_ByteCount( world->contactBeginEvents ); + eventBytes += b3Array_ByteCount( world->sensorEndEvents[0] ); + eventBytes += b3Array_ByteCount( world->sensorEndEvents[1] ); + eventBytes += b3Array_ByteCount( world->contactEndEvents[0] ); + eventBytes += b3Array_ByteCount( world->contactEndEvents[1] ); + eventBytes += b3Array_ByteCount( world->contactHitEvents ); + eventBytes += b3Array_ByteCount( world->jointEvents ); + total += eventBytes; + + b3Log( "events: %d", eventBytes ); + + // Debug draw bit sets + int debugBytes = 0; + debugBytes += b3GetBitSetBytes( &world->debugBodySet ); + debugBytes += b3GetBitSetBytes( &world->debugJointSet ); + debugBytes += b3GetBitSetBytes( &world->debugContactSet ); + debugBytes += b3GetBitSetBytes( &world->debugIslandSet ); + total += debugBytes; + + b3Log( "debug draw: %d", debugBytes ); + + // stack allocator + total += world->stack.capacity; + b3Log( "stack allocator: %d", world->stack.capacity ); + + b3Log( "total: %u KB", (uint32_t)( total / 1024 ) ); +} + +typedef struct WorldQueryContext +{ + b3World* world; + b3OverlapResultFcn* fcn; + b3QueryFilter filter; + void* userContext; +} WorldQueryContext; + +static bool TreeQueryCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + WorldQueryContext* worldContext = (WorldQueryContext*)context; + b3World* world = worldContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + b3Filter shapeFilter = shape->filter; + b3QueryFilter queryFilter = worldContext->filter; + + if ( b3ShouldQueryCollide( &shapeFilter, &queryFilter ) == false ) + { + return true; + } + + b3ShapeId id = { shapeId + 1, world->worldId, shape->generation }; + bool result = worldContext->fcn( id, worldContext->userContext ); + return result; +} + +b3TreeStats b3World_OverlapAABB( b3WorldId worldId, b3AABB aabb, b3QueryFilter filter, b3OverlapResultFcn* fcn, void* context ) +{ + b3TreeStats treeStats = { 0 }; + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return treeStats; + } + + B3_ASSERT( b3IsValidAABB( aabb ) ); + + b3RecQueryWriter recWriter = { 0 }; + if ( world->recording != NULL ) + { + b3RecQueryBegin( &recWriter, context, filter.id, filter.name ); + recWriter.userFcn.overlapFcn = fcn; + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_AABB( &recWriter.buf, aabb ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + recWriter.countOffset = b3RecReserveU32( &recWriter.buf ); + fcn = b3RecOverlapTrampoline; + context = &recWriter; + } + + WorldQueryContext worldContext = { world, fcn, filter, context }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3TreeStats treeResult = + b3DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, false, TreeQueryCallback, &worldContext ); + + treeStats.nodeVisits += treeResult.nodeVisits; + treeStats.leafVisits += treeResult.leafVisits; + } + + if ( world->recording != NULL ) + { + b3RecPatchU32( &recWriter.buf, recWriter.countOffset, recWriter.hitCount ); + b3RecW_TREESTATS( &recWriter.buf, treeStats ); + b3RecQueryCommit( world->recording, b3_recOpQueryOverlapAABB, &recWriter ); + } + + return treeStats; +} + +typedef struct WorldOverlapContext +{ + b3World* world; + b3OverlapResultFcn* fcn; + b3QueryFilter filter; + b3ShapeProxy proxy; + b3Pos origin; + void* userContext; +} WorldOverlapContext; + +static bool b3TreeOverlapCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + WorldOverlapContext* worldContext = (WorldOverlapContext*)context; + b3World* world = worldContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + b3Filter shapeFilter = shape->filter; + b3QueryFilter queryFilter = worldContext->filter; + + if ( b3ShouldQueryCollide( &shapeFilter, &queryFilter ) == false ) + { + return true; + } + + // Re-center on the query origin so the overlap test stays in float precision far from the origin + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), worldContext->origin ); + + bool overlapping = b3OverlapShape( shape, transform, &worldContext->proxy ); + if ( overlapping == false ) + { + return true; + } + + b3ShapeId id = { shape->id + 1, world->worldId, shape->generation }; + bool result = worldContext->fcn( id, worldContext->userContext ); + return result; +} + +b3TreeStats b3World_OverlapShape( b3WorldId worldId, b3Pos origin, const b3ShapeProxy* proxy, b3QueryFilter filter, + b3OverlapResultFcn* fcn, void* context ) +{ + b3TreeStats treeStats = { 0 }; + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return treeStats; + } + + B3_ASSERT( b3IsValidPosition( origin ) ); + + b3RecQueryWriter recWriter = { 0 }; + if ( world->recording != NULL ) + { + b3RecQueryBegin( &recWriter, context, filter.id, filter.name ); + recWriter.userFcn.overlapFcn = fcn; + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_POSITION( &recWriter.buf, origin ); + b3RecW_SHAPEPROXY( &recWriter.buf, *proxy ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + recWriter.countOffset = b3RecReserveU32( &recWriter.buf ); + fcn = b3RecOverlapTrampoline; + context = &recWriter; + } + + // Bound the proxy in origin relative space then lift to a conservative world float box + b3AABB aabb = b3OffsetAABB( b3MakeAABB( proxy->points, proxy->count, proxy->radius ), origin ); + WorldOverlapContext worldContext = { + world, fcn, filter, *proxy, origin, context, + }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3TreeStats treeResult = b3DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, false, + b3TreeOverlapCallback, &worldContext ); + + treeStats.nodeVisits += treeResult.nodeVisits; + treeStats.leafVisits += treeResult.leafVisits; + } + + if ( world->recording != NULL ) + { + b3RecPatchU32( &recWriter.buf, recWriter.countOffset, recWriter.hitCount ); + b3RecW_TREESTATS( &recWriter.buf, treeStats ); + b3RecQueryCommit( world->recording, b3_recOpQueryOverlapShape, &recWriter ); + } + + return treeStats; +} + +typedef struct WorldMoverContext +{ + b3World* world; + b3PlaneResultFcn* fcn; + b3QueryFilter filter; + b3Capsule mover; + b3Pos origin; + void* userContext; +} WorldMoverContext; + +static bool TreeCollideCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + WorldMoverContext* worldContext = (WorldMoverContext*)context; + b3World* world = worldContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + b3Filter shapeFilter = shape->filter; + b3QueryFilter queryFilter = worldContext->filter; + + if ( b3ShouldQueryCollide( &shapeFilter, &queryFilter ) == false ) + { + return true; + } + + // Re-center on the query origin, the mover and the resulting planes are origin relative + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3WorldTransform bodyTransform = b3GetBodyTransformQuick( world, body ); + b3Transform transform = b3ToRelativeTransform( bodyTransform, worldContext->origin ); + + b3PlaneResult buffer[64]; + int count = b3CollideMover( buffer, 64, shape, transform, &worldContext->mover ); + + if ( count > 0 ) + { + b3ShapeId id = { shape->id + 1, world->worldId, shape->generation }; + return worldContext->fcn( id, buffer, count, worldContext->userContext ); + } + + return true; +} + +// It is tempting to use a shape proxy for the mover, but this makes handling deep overlap difficult and the generality may +// not be worth it. +void b3World_CollideMover( b3WorldId worldId, b3Pos origin, const b3Capsule* mover, b3QueryFilter filter, b3PlaneResultFcn* fcn, + void* context ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_ASSERT( b3IsValidPosition( origin ) ); + + b3RecQueryWriter recWriter = { 0 }; + if ( world->recording != NULL ) + { + b3RecQueryBegin( &recWriter, context, filter.id, filter.name ); + recWriter.userFcn.planeFcn = fcn; + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_POSITION( &recWriter.buf, origin ); + b3RecW_CAPSULE( &recWriter.buf, *mover ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + recWriter.countOffset = b3RecReserveU32( &recWriter.buf ); + fcn = b3RecPlaneTrampoline; + context = &recWriter; + } + + b3Vec3 r = { mover->radius, mover->radius, mover->radius }; + + // Relative box lifted to world float with outward rounding, conservative for the tree + b3AABB relBox; + relBox.lowerBound = b3Sub( b3Min( mover->center1, mover->center2 ), r ); + relBox.upperBound = b3Add( b3Max( mover->center1, mover->center2 ), r ); + b3AABB aabb = b3OffsetAABB( relBox, origin ); + + WorldMoverContext worldContext = { + world, fcn, filter, *mover, origin, context, + }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DynamicTree_Query( world->broadPhase.trees + i, aabb, filter.maskBits, false, TreeCollideCallback, &worldContext ); + } + + if ( world->recording != NULL ) + { + // CollideMover returns void: no treestats tail, just the per-shape plane batches. + b3RecPatchU32( &recWriter.buf, recWriter.countOffset, recWriter.hitCount ); + b3RecQueryCommit( world->recording, b3_recOpQueryCollideMover, &recWriter ); + } +} + +typedef struct WorldRayCastContext +{ + b3World* world; + b3CastResultFcn* fcn; + b3QueryFilter filter; + float fraction; + b3Pos origin; + void* userContext; +} WorldRayCastContext; + +static float RayCastCallback( const b3RayCastInput* input, int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + WorldRayCastContext* worldContext = (WorldRayCastContext*)context; + b3World* world = worldContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + b3Filter shapeFilter = shape->filter; + b3QueryFilter queryFilter = worldContext->filter; + + if ( b3ShouldQueryCollide( &shapeFilter, &queryFilter ) == false ) + { + return input->maxFraction; + } + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3WorldTransform bodyTransform = b3GetBodyTransformQuick( world, body ); + b3Transform transform = b3ToRelativeTransform( bodyTransform, worldContext->origin ); + + b3RayCastInput localInput = *input; + localInput.origin = b3Vec3_zero; + b3CastOutput output = b3RayCastShape( shape, transform, &localInput ); + + if ( output.hit ) + { + B3_ASSERT( output.fraction <= input->maxFraction ); + + b3ShapeId id = { shapeId + 1, world->worldId, shape->generation }; + b3Pos point = b3OffsetPos( worldContext->origin, output.point ); + int materialIndex = b3ClampInt( output.materialIndex, 0, shape->materialCount - 1 ); + uint64_t userMaterialId = b3GetShapeMaterials( shape )[materialIndex].userMaterialId; + + int triangleIndex = output.triangleIndex; + int childIndex = output.childIndex; + float fraction = worldContext->fcn( id, point, output.normal, output.fraction, userMaterialId, triangleIndex, childIndex, + worldContext->userContext ); + + // The user may return -1 to skip this shape + if ( 0.0f <= fraction && fraction <= 1.0f ) + { + worldContext->fraction = fraction; + } + + return fraction; + } + + return input->maxFraction; +} + +b3TreeStats b3World_CastRay( b3WorldId worldId, b3Pos origin, b3Vec3 translation, b3QueryFilter filter, b3CastResultFcn* fcn, + void* context ) +{ + b3TreeStats treeStats = { 0 }; + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return treeStats; + } + + B3_ASSERT( b3IsValidPosition( origin ) ); + B3_ASSERT( b3IsValidVec3( translation ) ); + + b3RecQueryWriter recWriter = { 0 }; + if ( world->recording != NULL ) + { + b3RecQueryBegin( &recWriter, context, filter.id, filter.name ); + recWriter.userFcn.castFcn = fcn; + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_POSITION( &recWriter.buf, origin ); + b3RecW_VEC3( &recWriter.buf, translation ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + recWriter.countOffset = b3RecReserveU32( &recWriter.buf ); + fcn = b3RecCastTrampoline; + context = &recWriter; + } + + // The tree traverses in float relative to the world origin. Each shape is then re-differenced at + // full precision against the origin, so a hit stays accurate far from the origin. + b3RayCastInput input = { b3ToVec3( origin ), translation, 1.0f }; + + WorldRayCastContext worldContext = { world, fcn, filter, 1.0f, origin, context }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3TreeStats treeResult = + b3DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, false, RayCastCallback, &worldContext ); + treeStats.nodeVisits += treeResult.nodeVisits; + treeStats.leafVisits += treeResult.leafVisits; + + if ( worldContext.fraction == 0.0f ) + { + break; + } + + input.maxFraction = worldContext.fraction; + } + + if ( world->recording != NULL ) + { + b3RecPatchU32( &recWriter.buf, recWriter.countOffset, recWriter.hitCount ); + b3RecW_TREESTATS( &recWriter.buf, treeStats ); + b3RecQueryCommit( world->recording, b3_recOpQueryCastRay, &recWriter ); + } + + return treeStats; +} + +// This callback finds the closest hit. This is the most common callback used in games. +static float b3RayCastClosestFcn( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, + int triangleIndex, int childIndex, void* context ) +{ + // Ignore initial overlap + if ( fraction == 0.0f ) + { + return -1.0f; + } + + b3RayResult* rayResult = (b3RayResult*)context; + rayResult->shapeId = shapeId; + rayResult->point = point; + rayResult->normal = normal; + rayResult->fraction = fraction; + rayResult->userMaterialId = userMaterialId; + rayResult->triangleIndex = triangleIndex; + rayResult->childIndex = childIndex; + rayResult->hit = true; + return fraction; +} + +b3RayResult b3World_CastRayClosest( b3WorldId worldId, b3Pos origin, b3Vec3 translation, b3QueryFilter filter ) +{ + b3RayResult result = { 0 }; + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return result; + } + + B3_ASSERT( b3IsValidPosition( origin ) ); + B3_ASSERT( b3IsValidVec3( translation ) ); + + // The tree traverses in float relative to the world origin. Each shape is then re-differenced at + // full precision against its body, so a hit stays accurate far from the origin. + b3RayCastInput input = { b3ToVec3( origin ), translation, 1.0f }; + WorldRayCastContext worldContext = { + .world = world, + .fcn = b3RayCastClosestFcn, + .filter = filter, + .fraction = 1.0f, + .origin = origin, + .userContext = &result, + }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3TreeStats treeResult = + b3DynamicTree_RayCast( world->broadPhase.trees + i, &input, filter.maskBits, false, RayCastCallback, &worldContext ); + result.nodeVisits += treeResult.nodeVisits; + result.leafVisits += treeResult.leafVisits; + + if ( worldContext.fraction == 0.0f ) + { + break; + } + + input.maxFraction = worldContext.fraction; + } + + // Closed query, no user callback: record the inputs and the single result for the replay compare. + if ( world->recording != NULL ) + { + b3RecQueryWriter recWriter = { 0 }; + b3RecQueryBegin( &recWriter, NULL, filter.id, filter.name ); + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_POSITION( &recWriter.buf, origin ); + b3RecW_VEC3( &recWriter.buf, translation ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + b3RecW_RAYRESULT( &recWriter.buf, result ); + b3RecQueryCommit( world->recording, b3_recOpQueryCastRayClosest, &recWriter ); + } + + return result; +} + +typedef struct WorldShapeCastContext +{ + b3World* world; + b3CastResultFcn* fcn; + b3QueryFilter filter; + float fraction; + b3Pos origin; + // origin relative input + b3ShapeCastInput input; + void* userContext; +} WorldShapeCastContext; + +static float b3ShapeCastCallback( const b3BoxCastInput* input, int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + WorldShapeCastContext* worldContext = (WorldShapeCastContext*)context; + b3World* world = worldContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + b3Filter shapeFilter = shape->filter; + b3QueryFilter queryFilter = worldContext->filter; + + if ( b3ShouldQueryCollide( &shapeFilter, &queryFilter ) == false ) + { + return input->maxFraction; + } + + // Rebuild from the origin relative input, taking only the advancing fraction from the tree. + // The tree box is world float and would lose the cast far from the origin. + b3ShapeCastInput localInput = worldContext->input; + localInput.maxFraction = input->maxFraction; + + // Re-center on the query origin so the per-shape cast stays in float precision far from the origin + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), worldContext->origin ); + + b3CastOutput output = b3ShapeCastShape( shape, transform, &localInput ); + + if ( output.hit ) + { + b3ShapeId id = { shapeId + 1, world->worldId, shape->generation }; + int materialIndex = b3ClampInt( output.materialIndex, 0, shape->materialCount - 1 ); + uint64_t userMaterialId = b3GetShapeMaterials( shape )[materialIndex].userMaterialId; + + int triangleIndex = output.triangleIndex; + int childIndex = output.childIndex; + float fraction = worldContext->fcn( id, b3OffsetPos( worldContext->origin, output.point ), output.normal, output.fraction, + userMaterialId, triangleIndex, childIndex, worldContext->userContext ); + + // The user may return -1 to skip this shape + if ( 0.0f <= fraction && fraction <= 1.0f ) + { + worldContext->fraction = fraction; + } + + return fraction; + } + + return input->maxFraction; +} + +b3TreeStats b3World_CastShape( b3WorldId worldId, b3Pos origin, const b3ShapeProxy* proxy, b3Vec3 translation, + b3QueryFilter filter, b3CastResultFcn* fcn, void* context ) +{ + b3TreeStats treeStats = { 0 }; + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return treeStats; + } + + B3_ASSERT( b3IsValidPosition( origin ) ); + B3_ASSERT( b3IsValidVec3( translation ) ); + + b3RecQueryWriter recWriter = { 0 }; + if ( world->recording != NULL ) + { + b3RecQueryBegin( &recWriter, context, filter.id, filter.name ); + recWriter.userFcn.castFcn = fcn; + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_POSITION( &recWriter.buf, origin ); + b3RecW_SHAPEPROXY( &recWriter.buf, *proxy ); + b3RecW_VEC3( &recWriter.buf, translation ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + recWriter.countOffset = b3RecReserveU32( &recWriter.buf ); + fcn = b3RecCastTrampoline; + context = &recWriter; + } + + WorldShapeCastContext worldContext = { 0 }; + worldContext.world = world; + worldContext.fcn = fcn; + worldContext.filter = filter; + worldContext.fraction = 1.0f; + worldContext.origin = origin; + worldContext.input.proxy = *proxy; + worldContext.input.translation = translation; + worldContext.input.maxFraction = 1.0f; + worldContext.input.canEncroach = false; + worldContext.userContext = context; + + // Bound the proxy in origin relative space then lift to a conservative world float box. The tree + // node boxes use the same directed rounding, so the swept box never clips a shape far from the + // origin. Per shape casts re-difference at full precision against the carried origin. + b3AABB localBox = b3MakeAABB( proxy->points, proxy->count, proxy->radius ); + b3BoxCastInput treeInput = { b3OffsetAABB( localBox, origin ), translation, 1.0f }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3TreeStats treeResult = b3DynamicTree_BoxCast( world->broadPhase.trees + i, &treeInput, filter.maskBits, false, + b3ShapeCastCallback, &worldContext ); + treeStats.nodeVisits += treeResult.nodeVisits; + treeStats.leafVisits += treeResult.leafVisits; + + if ( worldContext.fraction == 0.0f ) + { + break; + } + + treeInput.maxFraction = worldContext.fraction; + } + + if ( world->recording != NULL ) + { + b3RecPatchU32( &recWriter.buf, recWriter.countOffset, recWriter.hitCount ); + b3RecW_TREESTATS( &recWriter.buf, treeStats ); + b3RecQueryCommit( world->recording, b3_recOpQueryCastShape, &recWriter ); + } + + return treeStats; +} + +typedef struct WorldMoverCastContext +{ + b3World* world; + b3MoverFilterFcn* fcn; + b3QueryFilter filter; + float fraction; + b3Pos origin; + // origin relative input + b3ShapeCastInput input; + void* userContext; +} WorldMoverCastContext; + +static float MoverCastCallback( const b3BoxCastInput* input, int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + WorldMoverCastContext* worldContext = (WorldMoverCastContext*)context; + b3World* world = worldContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + b3Filter shapeFilter = shape->filter; + b3QueryFilter queryFilter = worldContext->filter; + + if ( b3ShouldQueryCollide( &shapeFilter, &queryFilter ) == false ) + { + return worldContext->fraction; + } + + if ( worldContext->fcn != NULL ) + { + b3ShapeId id = { shapeId + 1, world->worldId, shape->generation }; + bool shouldCollide = worldContext->fcn( id, worldContext->userContext ); + if ( shouldCollide == false ) + { + return worldContext->fraction; + } + } + + // Rebuild from the origin relative input, taking only the advancing fraction from the tree + b3ShapeCastInput localInput = worldContext->input; + localInput.maxFraction = input->maxFraction; + + // Re-center on the query origin so the per-shape cast stays in float precision far from the origin + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), worldContext->origin ); + + b3CastOutput output = b3ShapeCastShape( shape, transform, &localInput ); + if ( output.fraction == 0.0f ) + { + // Ignore overlapping shapes + return worldContext->fraction; + } + + worldContext->fraction = output.fraction; + return output.fraction; +} + +float b3World_CastMover( b3WorldId worldId, b3Pos origin, const b3Capsule* mover, b3Vec3 translation, b3QueryFilter filter, + b3MoverFilterFcn* fcn, void* context ) +{ + B3_ASSERT( b3IsValidPosition( origin ) ); + B3_ASSERT( b3IsValidVec3( translation ) ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return 1.0f; + } + + // The mover filter is a per-shape bool(shapeId, ctx) decision, the same shape as an overlap + // callback, so the overlap trampoline captures it. Installing it even when the user passed no + // filter records an accept-all stream that replays identically. + b3RecQueryWriter recWriter = { 0 }; + if ( world->recording != NULL ) + { + b3RecQueryBegin( &recWriter, context, filter.id, filter.name ); + recWriter.userFcn.moverFilterFcn = fcn; + b3RecW_WORLDID( &recWriter.buf, worldId ); + b3RecW_POSITION( &recWriter.buf, origin ); + b3RecW_CAPSULE( &recWriter.buf, *mover ); + b3RecW_VEC3( &recWriter.buf, translation ); + b3RecW_QUERYFILTER( &recWriter.buf, filter ); + recWriter.countOffset = b3RecReserveU32( &recWriter.buf ); + fcn = b3RecOverlapTrampoline; + context = &recWriter; + } + + WorldMoverCastContext worldContext = { + .world = world, + .fcn = fcn, + .filter = filter, + .fraction = 1.0f, + .origin = origin, + .userContext = context, + }; + worldContext.input.proxy = (b3ShapeProxy){ &mover->center1, 2, mover->radius }; + worldContext.input.translation = translation; + worldContext.input.maxFraction = 1.0f; + worldContext.input.canEncroach = mover->radius > 0.0f; + + // Bound the capsule in origin relative space then lift to a conservative world float box + b3Vec3 centers[2] = { mover->center1, mover->center2 }; + b3BoxCastInput treeInput = { b3OffsetAABB( b3MakeAABB( centers, 2, mover->radius ), origin ), translation, 1.0f }; + + for ( int i = 0; i < b3_bodyTypeCount; ++i ) + { + b3DynamicTree_BoxCast( world->broadPhase.trees + i, &treeInput, filter.maskBits, false, MoverCastCallback, + &worldContext ); + + if ( worldContext.fraction == 0.0f ) + { + break; + } + + treeInput.maxFraction = worldContext.fraction; + } + + if ( world->recording != NULL ) + { + // The mover filter type aliases the overlap trampoline, so the user fcn lands in the same + // union slot. Backpatch the accept count, then record the returned fraction as the tail. + b3RecPatchU32( &recWriter.buf, recWriter.countOffset, recWriter.hitCount ); + b3RecW_F32( &recWriter.buf, worldContext.fraction ); + b3RecQueryCommit( world->recording, b3_recOpQueryCastMover, &recWriter ); + } + + return worldContext.fraction; +} + +void b3World_SetCustomFilterCallback( b3WorldId worldId, b3CustomFilterFcn* fcn, void* context ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + world->customFilterFcn = fcn; + world->customFilterContext = context; +} + +void b3World_SetPreSolveCallback( b3WorldId worldId, b3PreSolveFcn* fcn, void* context ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + world->preSolveFcn = fcn; + world->preSolveContext = context; +} + +void b3World_SetGravity( b3WorldId worldId, b3Vec3 gravity ) +{ + b3World* world = b3GetWorldFromId( worldId ); + + B3_REC( world, WorldSetGravity, worldId, gravity ); + + world->gravity = gravity; +} + +b3Vec3 b3World_GetGravity( b3WorldId worldId ) +{ + b3World* world = b3GetWorldFromId( worldId ); + return world->gravity; +} + +typedef struct ExplosionContext +{ + b3World* world; + b3Pos position; + float radius; + float falloff; + float impulsePerArea; +} ExplosionContext; + +static bool ExplosionCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + ExplosionContext* explosionContext = (ExplosionContext*)context; + b3World* world = explosionContext->world; + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + if ( shape->explosionScale == 0.0f ) + { + return true; + } + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + B3_ASSERT( body->type == b3_dynamicBody ); + + b3WorldTransform xf = b3GetBodyTransformQuick( world, body ); + + // Re-center the explosion into the shape local frame so distance and direction stay precise + // far from the origin. Everything below runs in that near-origin frame. + b3Vec3 localPosition = b3InvTransformWorldPoint( xf, explosionContext->position ); + + b3DistanceInput input; + input.proxyA = b3MakeShapeProxy( shape ); + input.proxyB = (b3ShapeProxy){ &localPosition, 1, 0.0f }; + input.transform = b3Transform_identity; + input.useRadii = true; + + b3SimplexCache cache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + + float radius = explosionContext->radius; + float falloff = explosionContext->falloff; + if ( output.distance > radius + falloff ) + { + return true; + } + + b3WakeBody( world, body ); + + if ( body->setIndex != b3_awakeSet ) + { + return true; + } + + // Witness point is already in the body local query frame + b3Vec3 closestPoint = output.pointA; + if ( output.distance == 0.0f ) + { + closestPoint = b3GetShapeCentroid( shape ); + } + + b3Vec3 direction = b3Sub( closestPoint, localPosition ); + if ( b3LengthSquared( direction ) > 100.0f * FLT_EPSILON * FLT_EPSILON ) + { + direction = b3Normalize( direction ); + } + else + { + direction = (b3Vec3){ 1.0f, 0.0f, 0.0f }; + } + + float area = b3GetShapeProjectedArea( shape, direction ); + float scale = 1.0f; + if ( output.distance > radius && falloff > 0.0f ) + { + scale = b3ClampFloat( ( radius + falloff - output.distance ) / falloff, 0.0f, 1.0f ); + } + + float magnitude = explosionContext->impulsePerArea * area * scale * shape->explosionScale; + b3Vec3 impulse = b3MulSV( magnitude, b3RotateVector( xf.q, direction ) ); + + int localIndex = body->localIndex; + b3SolverSet* set = b3Array_Get( world->solverSets, b3_awakeSet ); + b3BodyState* state = b3Array_Get( set->bodyStates, localIndex ); + b3BodySim* bodySim = b3Array_Get( set->bodySims, localIndex ); + state->linearVelocity = b3MulAdd( state->linearVelocity, bodySim->invMass, impulse ); + + // Lever arm from the center of mass to the closest point, rotated to world + b3Vec3 r = b3RotateVector( xf.q, b3Sub( closestPoint, bodySim->localCenter ) ); + state->angularVelocity = b3Add( state->angularVelocity, b3MulMV( bodySim->invInertiaWorld, b3Cross( r, impulse ) ) ); + + return true; +} + +void b3World_Explode( b3WorldId worldId, const b3ExplosionDef* explosionDef ) +{ + uint64_t maskBits = explosionDef->maskBits; + b3Pos position = explosionDef->position; + float radius = explosionDef->radius; + float falloff = explosionDef->falloff; + float impulsePerArea = explosionDef->impulsePerArea; + + B3_ASSERT( b3IsValidPosition( position ) ); + B3_ASSERT( b3IsValidFloat( radius ) && radius >= 0.0f ); + B3_ASSERT( b3IsValidFloat( falloff ) && falloff >= 0.0f ); + B3_ASSERT( b3IsValidFloat( impulsePerArea ) ); + + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldExplode, worldId, *explosionDef ); + + // Locked due to waking + world->locked = true; + + struct ExplosionContext explosionContext = { world, position, radius, falloff, impulsePerArea }; + + // The broad-phase tree is float, so translate a local query box out to world with outward rounding + float extent = radius + falloff; + b3AABB localBox = { { -extent, -extent, -extent }, { extent, extent, extent } }; + b3AABB aabb = b3OffsetAABB( localBox, position ); + + b3DynamicTree_Query( world->broadPhase.trees + b3_dynamicBody, aabb, maskBits, false, ExplosionCallback, &explosionContext ); + + world->locked = false; +} + +void b3World_RebuildStaticTree( b3WorldId worldId ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldRebuildStaticTree, worldId ); + + b3DynamicTree* staticTree = world->broadPhase.trees + b3_staticBody; + b3DynamicTree_Rebuild( staticTree, true ); +} + +void b3World_EnableSpeculative( b3WorldId worldId, bool flag ) +{ + b3World* world = b3GetUnlockedWorldFromId( worldId ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, WorldEnableSpeculative, worldId, flag ); + + world->enableSpeculative = flag; +} + +#if B3_ENABLE_VALIDATION +// This validates island graph connectivity for each body +void b3ValidateConnectivity( b3World* world ) +{ + b3Body* bodies = world->bodies.data; + int bodyCapacity = world->bodies.count; + + for ( int bodyIndex = 0; bodyIndex < bodyCapacity; ++bodyIndex ) + { + b3Body* body = bodies + bodyIndex; + if ( body->id == B3_NULL_INDEX ) + { + b3ValidateFreeId( &world->bodyIdPool, bodyIndex ); + continue; + } + + B3_ASSERT( bodyIndex == body->id ); + + // Need to get the root island because islands are not merged until the next time step + int bodyIslandId = body->islandId; + int bodySetIndex = body->setIndex; + + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + + bool touching = ( contact->flags & b3_contactTouchingFlag ) != 0; + if ( touching ) + { + if ( bodySetIndex != b3_staticSet ) + { + int contactIslandId = contact->islandId; + B3_ASSERT( contactIslandId == bodyIslandId ); + } + } + else + { + B3_ASSERT( contact->islandId == B3_NULL_INDEX ); + } + + contactKey = contact->edges[edgeIndex].nextKey; + } + + int jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + + int otherEdgeIndex = edgeIndex ^ 1; + + b3Body* otherBody = b3Array_Get( world->bodies, joint->edges[otherEdgeIndex].bodyId ); + + if ( bodySetIndex == b3_disabledSet || otherBody->setIndex == b3_disabledSet ) + { + B3_ASSERT( joint->islandId == B3_NULL_INDEX ); + } + else if ( bodySetIndex == b3_staticSet ) + { + // Intentional nesting + if ( otherBody->setIndex == b3_staticSet ) + { + B3_ASSERT( joint->islandId == B3_NULL_INDEX ); + } + } + else if ( body->type != b3_dynamicBody && otherBody->type != b3_dynamicBody ) + { + B3_ASSERT( joint->islandId == B3_NULL_INDEX ); + } + else + { + int jointIslandId = joint->islandId; + B3_ASSERT( jointIslandId == bodyIslandId ); + } + + jointKey = joint->edges[edgeIndex].nextKey; + } + } +} + +// Validates solver sets, but not island connectivity +void b3ValidateSolverSets( b3World* world ) +{ + B3_ASSERT( b3GetIdCapacity( &world->bodyIdPool ) == world->bodies.count ); + B3_ASSERT( b3GetIdCapacity( &world->contactIdPool ) == world->contacts.count ); + B3_ASSERT( b3GetIdCapacity( &world->jointIdPool ) == world->joints.count ); + B3_ASSERT( b3GetIdCapacity( &world->islandIdPool ) == world->islands.count ); + B3_ASSERT( b3GetIdCapacity( &world->solverSetIdPool ) == world->solverSets.count ); + + int activeSetCount = 0; + int totalBodyCount = 0; + int totalJointCount = 0; + int totalContactCount = 0; + int totalIslandCount = 0; + + // Validate all solver sets + int setCount = world->solverSets.count; + for ( int setIndex = 0; setIndex < setCount; ++setIndex ) + { + b3SolverSet* set = world->solverSets.data + setIndex; + if ( set->setIndex != B3_NULL_INDEX ) + { + activeSetCount += 1; + + if ( setIndex == b3_staticSet ) + { + B3_ASSERT( set->contactIndices.count == 0 ); + B3_ASSERT( set->islandSims.count == 0 ); + B3_ASSERT( set->bodyStates.count == 0 ); + } + else if ( setIndex == b3_disabledSet ) + { + B3_ASSERT( set->islandSims.count == 0 ); + B3_ASSERT( set->bodyStates.count == 0 ); + } + else if ( setIndex == b3_awakeSet ) + { + B3_ASSERT( set->bodySims.count == set->bodyStates.count ); + B3_ASSERT( set->jointSims.count == 0 ); + } + else + { + B3_ASSERT( set->bodyStates.count == 0 ); + } + + // Validate bodies + { + b3Body* bodies = world->bodies.data; + B3_ASSERT( set->bodySims.count >= 0 ); + totalBodyCount += set->bodySims.count; + for ( int i = 0; i < set->bodySims.count; ++i ) + { + b3BodySim* bodySim = set->bodySims.data + i; + + int bodyId = bodySim->bodyId; + B3_ASSERT( 0 <= bodyId && bodyId < world->bodies.count ); + b3Body* body = bodies + bodyId; + B3_ASSERT( body->setIndex == setIndex ); + B3_ASSERT( body->localIndex == i ); + + uint32_t syncedFlags = body->flags & ~b3_bodyTransientFlags; + B3_ASSERT( ( bodySim->flags & syncedFlags ) == syncedFlags ); + + b3BodyState* bodyState = b3GetBodyState( world, body ); + if ( bodyState != NULL ) + { + B3_ASSERT( ( bodyState->flags & syncedFlags ) == syncedFlags ); + } + + if ( body->type == b3_dynamicBody ) + { + B3_ASSERT( body->flags & b3_dynamicFlag ); + } + + if ( setIndex == b3_disabledSet ) + { + B3_ASSERT( body->headContactKey == B3_NULL_INDEX ); + } + + // Validate body shapes + int prevShapeId = B3_NULL_INDEX; + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + B3_ASSERT( shape->id == shapeId ); + B3_ASSERT( shape->prevShapeId == prevShapeId ); + + if ( setIndex == b3_disabledSet ) + { + B3_ASSERT( shape->proxyKey == B3_NULL_INDEX ); + } + else if ( setIndex == b3_staticSet ) + { + B3_ASSERT( B3_PROXY_TYPE( shape->proxyKey ) == b3_staticBody ); + } + else + { + b3BodyType proxyType = B3_PROXY_TYPE( shape->proxyKey ); + B3_ASSERT( proxyType == b3_kinematicBody || proxyType == b3_dynamicBody ); + } + + prevShapeId = shapeId; + shapeId = shape->nextShapeId; + } + + // Validate body contacts + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + B3_ASSERT( contact->setIndex != b3_staticSet ); + B3_ASSERT( contact->edges[0].bodyId == bodyId || contact->edges[1].bodyId == bodyId ); + contactKey = contact->edges[edgeIndex].nextKey; + } + + // Validate body joints + int jointKey = body->headJointKey; + while ( jointKey != B3_NULL_INDEX ) + { + int jointId = jointKey >> 1; + int edgeIndex = jointKey & 1; + + b3Joint* joint = b3Array_Get( world->joints, jointId ); + + int otherEdgeIndex = edgeIndex ^ 1; + + b3Body* otherBody = b3Array_Get( world->bodies, joint->edges[otherEdgeIndex].bodyId ); + + if ( setIndex == b3_disabledSet || otherBody->setIndex == b3_disabledSet ) + { + B3_ASSERT( joint->setIndex == b3_disabledSet ); + } + else if ( setIndex == b3_staticSet && otherBody->setIndex == b3_staticSet ) + { + B3_ASSERT( joint->setIndex == b3_staticSet ); + } + else if ( body->type != b3_dynamicBody && otherBody->type != b3_dynamicBody ) + { + B3_ASSERT( joint->setIndex == b3_staticSet ); + } + else if ( setIndex == b3_awakeSet ) + { + B3_ASSERT( joint->setIndex == b3_awakeSet ); + } + else if ( setIndex >= b3_firstSleepingSet ) + { + B3_ASSERT( joint->setIndex == setIndex ); + } + + b3JointSim* jointSim = b3GetJointSim( world, joint ); + B3_ASSERT( jointSim->jointId == jointId ); + B3_ASSERT( jointSim->bodyIdA == joint->edges[0].bodyId ); + B3_ASSERT( jointSim->bodyIdB == joint->edges[1].bodyId ); + + jointKey = joint->edges[edgeIndex].nextKey; + } + } + } + + // Validate contacts + { + B3_ASSERT( set->contactIndices.count >= 0 ); + totalContactCount += set->contactIndices.count; + for ( int i = 0; i < set->contactIndices.count; ++i ) + { + int contactIndex = set->contactIndices.data[i]; + b3Contact* contact = b3Array_Get( world->contacts, contactIndex ); + if ( setIndex == b3_awakeSet ) + { + // contact should be non-touching if awake + // or it could be this contact hasn't been transferred yet + B3_ASSERT( contact->manifoldCount == 0 || ( contact->flags & b3_simStartedTouching ) != 0 ); + } + B3_ASSERT( contact->setIndex == setIndex ); + B3_ASSERT( contact->colorIndex == B3_NULL_INDEX ); + B3_ASSERT( contact->localIndex == i ); + } + } + + // Validate joints + { + B3_ASSERT( set->jointSims.count >= 0 ); + totalJointCount += set->jointSims.count; + for ( int i = 0; i < set->jointSims.count; ++i ) + { + b3JointSim* jointSim = set->jointSims.data + i; + b3Joint* joint = b3Array_Get( world->joints, jointSim->jointId ); + B3_ASSERT( joint->setIndex == setIndex ); + B3_ASSERT( joint->colorIndex == B3_NULL_INDEX ); + B3_ASSERT( joint->localIndex == i ); + } + } + + // Validate islands + { + B3_ASSERT( set->islandSims.count >= 0 ); + totalIslandCount += set->islandSims.count; + for ( int i = 0; i < set->islandSims.count; ++i ) + { + b3IslandSim* islandSim = set->islandSims.data + i; + b3Island* island = b3Array_Get( world->islands, islandSim->islandId ); + B3_ASSERT( island->setIndex == setIndex ); + B3_ASSERT( island->localIndex == i ); + } + } + } + else + { + B3_ASSERT( set->bodySims.count == 0 ); + B3_ASSERT( set->contactIndices.count == 0 ); + B3_ASSERT( set->jointSims.count == 0 ); + B3_ASSERT( set->islandSims.count == 0 ); + B3_ASSERT( set->bodyStates.count == 0 ); + } + } + + int setIdCount = b3GetIdCount( &world->solverSetIdPool ); + B3_ASSERT( activeSetCount == setIdCount ); + + int bodyIdCount = b3GetIdCount( &world->bodyIdPool ); + B3_ASSERT( totalBodyCount == bodyIdCount ); + + int islandIdCount = b3GetIdCount( &world->islandIdPool ); + B3_ASSERT( totalIslandCount == islandIdCount ); + + // Validate constraint graph + for ( int colorIndex = 0; colorIndex < B3_GRAPH_COLOR_COUNT; ++colorIndex ) + { + b3GraphColor* color = world->constraintGraph.colors + colorIndex; + int bitCount = 0; + + B3_ASSERT( color->convexContacts.count >= 0 ); + totalContactCount += color->convexContacts.count; + for ( int i = 0; i < color->convexContacts.count; ++i ) + { + int contactId = color->convexContacts.data[i]; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + // contact should be touching in the constraint graph or awaiting transfer to non-touching + B3_ASSERT( contact->manifoldCount > 0 || ( contact->flags & ( b3_simStoppedTouching | b3_simDisjoint ) ) != 0 ); + B3_ASSERT( contact->setIndex == b3_awakeSet ); + B3_ASSERT( contact->colorIndex == colorIndex ); + B3_ASSERT( contact->localIndex == i ); + + int bodyIdA = contact->edges[0].bodyId; + int bodyIdB = contact->edges[1].bodyId; + + if ( colorIndex < B3_OVERFLOW_INDEX ) + { + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + B3_ASSERT( b3GetBit( &color->bodySet, bodyIdA ) == ( bodyA->type == b3_dynamicBody ) ); + B3_ASSERT( b3GetBit( &color->bodySet, bodyIdB ) == ( bodyB->type == b3_dynamicBody ) ); + + bitCount += bodyA->type == b3_dynamicBody ? 1 : 0; + bitCount += bodyB->type == b3_dynamicBody ? 1 : 0; + } + } + + totalContactCount += color->contacts.count; + for ( int i = 0; i < color->contacts.count; ++i ) + { + int contactId = color->contacts.data[i].contactId; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + // contact should be touching in the constraint graph or awaiting transfer to non-touching + B3_ASSERT( contact->manifoldCount > 0 || ( contact->flags & ( b3_simStoppedTouching | b3_simDisjoint ) ) != 0 ); + B3_ASSERT( contact->setIndex == b3_awakeSet ); + B3_ASSERT( contact->colorIndex == colorIndex ); + B3_ASSERT( contact->localIndex == i ); + + int bodyIdA = contact->edges[0].bodyId; + int bodyIdB = contact->edges[1].bodyId; + + if ( colorIndex < B3_OVERFLOW_INDEX ) + { + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + B3_ASSERT( b3GetBit( &color->bodySet, bodyIdA ) == ( bodyA->type == b3_dynamicBody ) ); + B3_ASSERT( b3GetBit( &color->bodySet, bodyIdB ) == ( bodyB->type == b3_dynamicBody ) ); + + bitCount += bodyA->type == b3_dynamicBody ? 1 : 0; + bitCount += bodyB->type == b3_dynamicBody ? 1 : 0; + } + } + + B3_ASSERT( color->jointSims.count >= 0 ); + totalJointCount += color->jointSims.count; + for ( int i = 0; i < color->jointSims.count; ++i ) + { + b3JointSim* jointSim = color->jointSims.data + i; + b3Joint* joint = b3Array_Get( world->joints, jointSim->jointId ); + B3_ASSERT( joint->setIndex == b3_awakeSet ); + B3_ASSERT( joint->colorIndex == colorIndex ); + B3_ASSERT( joint->localIndex == i ); + + int bodyIdA = joint->edges[0].bodyId; + int bodyIdB = joint->edges[1].bodyId; + + if ( colorIndex < B3_OVERFLOW_INDEX ) + { + b3Body* bodyA = b3Array_Get( world->bodies, bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, bodyIdB ); + B3_ASSERT( b3GetBit( &color->bodySet, bodyIdA ) == ( bodyA->type == b3_dynamicBody ) ); + B3_ASSERT( b3GetBit( &color->bodySet, bodyIdB ) == ( bodyB->type == b3_dynamicBody ) ); + + bitCount += bodyA->type == b3_dynamicBody ? 1 : 0; + bitCount += bodyB->type == b3_dynamicBody ? 1 : 0; + } + } + + // Validate the bit population for this graph color + B3_ASSERT( bitCount == b3CountSetBits( &color->bodySet ) ); + } + + int contactIdCount = b3GetIdCount( &world->contactIdPool ); + B3_ASSERT( totalContactCount == contactIdCount ); + B3_ASSERT( totalContactCount == (int)world->broadPhase.pairSet.count ); + + int jointIdCount = b3GetIdCount( &world->jointIdPool ); + B3_ASSERT( totalJointCount == jointIdCount ); + +// Validate shapes +// This is very slow on compounds +#if 0 + int shapeCapacity = b3Array(world->shapeArray).count; + for (int shapeIndex = 0; shapeIndex < shapeCapacity; shapeIndex += 1) + { + b3Shape* shape = world->shapeArray + shapeIndex; + if (shape->id != shapeIndex) + { + continue; + } + + B3_ASSERT(0 <= shape->bodyId && shape->bodyId < b3Array(world->bodyArray).count); + + b3Body* body = world->bodyArray + shape->bodyId; + B3_ASSERT(0 <= body->setIndex && body->setIndex < b3Array(world->solverSetArray).count); + + b3SolverSet* set = world->solverSetArray + body->setIndex; + B3_ASSERT(0 <= body->localIndex && body->localIndex < set->sims.count); + + b3BodySim* bodySim = set->sims.mData + body->localIndex; + B3_ASSERT(bodySim->bodyId == shape->bodyId); + + bool found = false; + int shapeCount = 0; + int index = body->headShapeId; + while (index != B3_NULL_INDEX) + { + b3CheckId(world->shapeArray, index); + b3Shape* s = world->shapeArray + index; + if (index == shapeIndex) + { + found = true; + } + + index = s->nextShapeId; + shapeCount += 1; + } + + B3_ASSERT(found); + B3_ASSERT(shapeCount == body->shapeCount); + } +#endif +} + +// Validate contact touching status. +void b3ValidateContacts( b3World* world ) +{ + b3ConstraintGraph* graph = &world->constraintGraph; + int contactCount = world->contacts.count; + B3_ASSERT( contactCount == b3GetIdCapacity( &world->contactIdPool ) ); + int allocatedContactCount = 0; + + for ( int contactIndex = 0; contactIndex < contactCount; ++contactIndex ) + { + b3Contact* contact = b3Array_Get( world->contacts, contactIndex ); + if ( contact->contactId == B3_NULL_INDEX ) + { + continue; + } + + B3_ASSERT( contact->contactId == contactIndex ); + + allocatedContactCount += 1; + + bool touching = ( contact->flags & b3_contactTouchingFlag ) != 0; + + int setId = contact->setIndex; + b3SolverSet* set = b3Array_Get( world->solverSets, setId ); + + if ( setId == b3_awakeSet ) + { + if ( touching ) + { + B3_ASSERT( 0 <= contact->colorIndex && contact->colorIndex < B3_GRAPH_COLOR_COUNT ); + // Validate body sim indices + b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA ); + b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB ); + + b3Body* bodyA = b3Array_Get( world->bodies, shapeA->bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, shapeB->bodyId ); + + if ( bodyA->type == b3_staticBody ) + { + B3_ASSERT( contact->bodySimIndexA == B3_NULL_INDEX ); + } + else + { + B3_ASSERT( contact->bodySimIndexA == bodyA->localIndex ); + } + + if ( bodyB->type == b3_staticBody ) + { + B3_ASSERT( contact->bodySimIndexB == B3_NULL_INDEX ); + } + else + { + B3_ASSERT( contact->bodySimIndexB == bodyB->localIndex ); + } + + if ( ( contact->flags & b3_simMeshContact ) != 0 || contact->colorIndex == B3_OVERFLOW_INDEX ) + { + b3GraphColor* color = graph->colors + contact->colorIndex; + int contactId = b3Array_Get( color->contacts, contact->localIndex )->contactId; + B3_ASSERT( contactId == contactIndex ); + } + else + { + b3GraphColor* color = graph->colors + contact->colorIndex; + int contactId = *b3Array_Get( color->convexContacts, contact->localIndex ); + B3_ASSERT( contactId == contactIndex ); + } + } + else + { + B3_ASSERT( contact->colorIndex == B3_NULL_INDEX ); + B3_ASSERT( contact->manifolds == NULL ); + B3_ASSERT( contact->manifoldCount == 0 ); + + int* index = b3Array_Get( set->contactIndices, contact->localIndex ); + B3_ASSERT( *index == contactIndex ); + } + } + else if ( setId >= b3_firstSleepingSet ) + { + // Only touching contacts allowed in a sleeping set + B3_ASSERT( touching == true ); + B3_ASSERT( contact->manifolds != NULL ); + B3_ASSERT( contact->manifoldCount > 0 ); + int* index = b3Array_Get( set->contactIndices, contact->localIndex ); + B3_ASSERT( *index == contactIndex ); + } + else + { + // Sleeping and non-touching contacts belong in the disabled set + B3_ASSERT( touching == false && setId == b3_disabledSet ); + B3_ASSERT( contact->manifolds == NULL ); + B3_ASSERT( contact->manifoldCount == 0 ); + int* index = b3Array_Get( set->contactIndices, contact->localIndex ); + B3_ASSERT( *index == contactIndex ); + } + + if ( contact->flags & b3_simMeshContact ) + { + int cacheCount = contact->meshContact.triangleCache.count; + if ( cacheCount > 0 ) + { + B3_ASSERT( contact->meshContact.triangleCache.data != NULL ); + B3_ASSERT( contact->meshContact.triangleCache.capacity >= cacheCount ); + + b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA ); + if ( shapeA->type == b3_meshShape ) + { + int triangleCount = shapeA->mesh.data->triangleCount; + for ( int i = 0; i < cacheCount; ++i ) + { + int triangleIndex = contact->meshContact.triangleCache.data[i].triangleIndex; + B3_ASSERT( 0 <= triangleIndex && triangleIndex < triangleCount ); + } + } + else if ( shapeA->type == b3_heightShape ) + { + int triangleCount = b3GetHeightFieldTriangleCount( shapeA->heightField ); + for ( int i = 0; i < cacheCount; ++i ) + { + int triangleIndex = contact->meshContact.triangleCache.data[i].triangleIndex; + B3_ASSERT( 0 <= triangleIndex && triangleIndex < triangleCount ); + } + } + else + { + B3_ASSERT( shapeA->type == b3_compoundShape ); + b3ChildShape child = b3GetCompoundChild( shapeA->compound, contact->childIndex ); + B3_ASSERT( child.type == b3_meshShape ); + + int triangleCount = child.mesh.data->triangleCount; + for ( int i = 0; i < cacheCount; ++i ) + { + int triangleIndex = contact->meshContact.triangleCache.data[i].triangleIndex; + B3_ASSERT( 0 <= triangleIndex && triangleIndex < triangleCount ); + } + } + } + } + } + + int contactIdCount = b3GetIdCount( &world->contactIdPool ); + B3_ASSERT( allocatedContactCount == contactIdCount ); +} + +#else + +void b3ValidateConnectivity( b3World* world ) +{ + B3_UNUSED( world ); +} + +void b3ValidateSolverSets( b3World* world ) +{ + B3_UNUSED( world ); +} + +void b3ValidateContacts( b3World* world ) +{ + B3_UNUSED( world ); +} + +#endif diff --git a/vendor/box3d/src/src/physics_world.h b/vendor/box3d/src/src/physics_world.h new file mode 100644 index 000000000..12f169561 --- /dev/null +++ b/vendor/box3d/src/src/physics_world.h @@ -0,0 +1,350 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "arena_allocator.h" +#include "bitset.h" +#include "block_allocator.h" +#include "broad_phase.h" +#include "constraint_graph.h" +#include "id_pool.h" +#include "name_cache.h" + +#include "box3d/types.h" + +#define B3_DEBUG_POINT_CAPACITY 64 +#define B3_DEBUG_LINE_CAPACITY 64 + +typedef struct b3Body b3Body; +typedef struct b3Recording b3Recording; +typedef struct b3Contact b3Contact; +typedef struct b3Island b3Island; +typedef struct b3Joint b3Joint; +typedef struct b3Sensor b3Sensor; +typedef struct b3SensorTaskContext b3SensorTaskContext; +typedef struct b3SensorHit b3SensorHit; +typedef struct b3Shape b3Shape; +typedef struct b3SolverSet b3SolverSet; + +b3DeclareArray( b3BlockAllocator ); +b3DeclareArray( b3Body ); +b3DeclareArray( b3SolverSet ); +b3DeclareArray( b3Joint ); +b3DeclareArray( b3Contact ); +b3DeclareArray( b3Island ); +b3DeclareArray( b3Shape ); +b3DeclareArray( b3Sensor ); +b3DeclareArray( b3SensorTaskContext ); +b3DeclareArray( b3SensorHit ); +b3DeclareArray( b3BodyMoveEvent ); +b3DeclareArray( b3SensorBeginTouchEvent ); +b3DeclareArray( b3ContactBeginTouchEvent ); +b3DeclareArray( b3SensorEndTouchEvent ); +b3DeclareArray( b3ContactEndTouchEvent ); +b3DeclareArray( b3ContactHitEvent ); +b3DeclareArray( b3JointEvent ); + +enum b3SetType +{ + b3_staticSet = 0, + b3_disabledSet = 1, + b3_awakeSet = 2, + b3_firstSleepingSet = 3, +}; + +typedef struct b3DebugPoint +{ + b3Pos p; + int label; + float value; + b3HexColor color; +} b3DebugPoint; + +typedef struct b3DebugLine +{ + b3Pos p1, p2; + int label; + b3HexColor color; +} b3DebugLine; + +// Per thread task storage +typedef struct b3TaskContext +{ + b3Arena arena; + + // Collect per thread sensor continuous hit events. + b3Array( b3SensorHit ) sensorHits; + + // These bits align with the b3ConstraintGraph::contactBlocks and signal a change in contact status + b3BitSet contactStateBitSet; + + // These bits align with the joint id capacity and signal a change in contact status + b3BitSet jointStateBitSet; + + // These bits align with the contact id capacity and signal a hit event. + b3BitSet hitEventBitSet; + + // Fast-path flag: true when this worker set at least one bit in hitEventBitSet this step. + bool hasHitEvents; + + // Used to track bodies with shapes that have enlarged AABBs. This avoids having a bit array + // that is very large when there are many static shapes. + b3BitSet enlargedSimBitSet; + + // Used to put islands to sleep + b3BitSet awakeIslandBitSet; + + // Per worker split island candidate + float splitSleepTime; + int splitIslandId; + + // Profiling + int satCallCount; + int satCacheHitCount; + int distanceIterations; + int pushBackIterations; + int rootIterations; + + // Number of contacts recycled this step (collide pass). + int recycledContactCount; + + b3DebugPoint points[B3_DEBUG_POINT_CAPACITY]; + int pointCount; + + b3DebugLine lines[B3_DEBUG_LINE_CAPACITY]; + int lineCount; + + int manifoldCounts[B3_CONTACT_MANIFOLD_COUNT_BUCKETS]; + // Prevent false sharing + char cacheLine[64]; +} b3TaskContext; + +b3DeclareArray( b3TaskContext ); + +// The world struct manages all physics entities, dynamic simulation, and asynchronous queries. +// The world also contains efficient memory management facilities. +typedef struct b3World +{ + b3Stack stack; + b3BroadPhase broadPhase; + b3ConstraintGraph constraintGraph; + + // Manifold allocators have one allocator for each manifold count. + b3Array( b3BlockAllocator ) manifoldAllocators; + b3Mutex* manifoldAllocatorMutex; + + // The body id pool is used to allocate and recycle body ids. Body ids + // provide a stable identifier for users, but incur caches misses when used + // to access body data. Aligns with b3Body. + b3IdPool bodyIdPool; + + // This is a sparse array that maps body ids to the body data + // stored in solver sets. As sims move within a set or across set. + // Indices come from id pool. + b3Array( b3Body ) bodies; + + // Provides free list for solver sets. + b3IdPool solverSetIdPool; + + // Solvers sets allow sims to be stored in contiguous arrays. The first + // set is all static sims. The second set is active sims. The third set is disabled + // sims. The remaining sets are sleeping islands. + b3Array( b3SolverSet ) solverSets; + + // Used to create stable ids for joints + b3IdPool jointIdPool; + + // This is a sparse array that maps joint ids to the joint data stored in the constraint graph + // or in the solver sets. + b3Array( b3Joint ) joints; + + // Used to create stable ids for contacts + b3IdPool contactIdPool; + + // This is a sparse array that maps contact ids to the contact data stored in the constraint graph + // or in the solver sets. + b3Array( b3Contact ) contacts; + + // Used to create stable ids for islands + b3IdPool islandIdPool; + + // This is a sparse array that maps island ids to the island data stored in the solver sets. + b3Array( b3Island ) islands; + + b3IdPool shapeIdPool; + + // These are sparse arrays that point into the pools above + b3Array( b3Shape ) shapes; + + // Reference counted store of shared hull data keyed by content. Shapes hold a + // pointer to the owned copy here. Opaque to avoid leaking the verstable map + // type into this header. + void* hullDatabase; + + // Name cache for shape and body names. This works with recording. + b3NameCache names; + + // This is a dense array of sensor data. + b3Array( b3Sensor ) sensors; + + // Per thread storage + b3Array( b3TaskContext ) taskContexts; + b3Array( b3SensorTaskContext ) sensorTaskContexts; + + b3Array( b3BodyMoveEvent ) bodyMoveEvents; + b3Array( b3SensorBeginTouchEvent ) sensorBeginEvents; + b3Array( b3ContactBeginTouchEvent ) contactBeginEvents; + + // End events are double buffered so that the user doesn't need to flush events + b3Array( b3SensorEndTouchEvent ) sensorEndEvents[2]; + b3Array( b3ContactEndTouchEvent ) contactEndEvents[2]; + int endEventArrayIndex; + + b3Array( b3ContactHitEvent ) contactHitEvents; + b3Array( b3JointEvent ) jointEvents; + + // Used to track debug draw + b3BitSet debugBodySet; + b3BitSet debugJointSet; + b3BitSet debugContactSet; + b3BitSet debugIslandSet; + b3CreateDebugShapeCallback* createDebugShape; + b3DestroyDebugShapeCallback* destroyDebugShape; + void* userDebugShapeContext; + + // Id that is incremented every time step + uint64_t stepIndex; + + // Identify islands for splitting as follows: + // - I want to split islands so smaller islands can sleep + // - when a body comes to rest and its sleep timer trips, I can look at the island and flag it for splitting + // if it has removed constraints + // - islands that have removed constraints must be put split first because I don't want to wake bodies incorrectly + // - otherwise I can use the awake islands that have bodies wanting to sleep as the splitting candidates + // - if no bodies want to sleep then there is no reason to perform island splitting + int splitIslandId; + + b3Vec3 gravity; + float hitEventThreshold; + float restitutionThreshold; + float maxLinearSpeed; + float contactSpeed; + float contactHertz; + float contactDampingRatio; + float contactRecycleDistance; + + b3FrictionCallback* frictionCallback; + b3RestitutionCallback* restitutionCallback; + + uint16_t generation; + + b3Profile profile; + int satCallCount; + int satCacheHitCount; + int manifoldCounts[B3_CONTACT_MANIFOLD_COUNT_BUCKETS]; + + b3Capacity maxCapacity; + + b3PreSolveFcn* preSolveFcn; + void* preSolveContext; + + b3CustomFilterFcn* customFilterFcn; + void* customFilterContext; + + int workerCount; + b3EnqueueTaskCallback* enqueueTaskFcn; + b3FinishTaskCallback* finishTaskFcn; + void* userTaskContext; + void* userTreeTask; + + struct b3Scheduler* scheduler; + + void* userData; + + // Non-NULL while a recording session is active. Set by b3World_StartRecording, + // cleared by b3World_StopRecording. Hooks in mutators check this before writing. + struct b3Recording* recording; + + // latest inverse sub-step + float inv_h; + + // latest inverse full-step + float inv_dt; + + int activeTaskCount; + int taskCount; + + uint16_t worldId; + + bool enableSleep; + + // This indicates there is a world write operation in progress. This is for debugging and + // not a real mutex. This should have minimal performance impact. + bool locked; + bool enableWarmStarting; + bool enableContinuous; + bool enableSpeculative; + bool inUse; +} b3World; + +b3World* b3GetUnlockedWorldFromId( b3WorldId id ); +b3World* b3GetWorldFromId( b3WorldId id ); + +b3World* b3GetUnlockedWorld( int index ); +b3World* b3GetWorld( int index ); + +void b3ValidateConnectivity( b3World* world ); +void b3ValidateSolverSets( b3World* world ); +void b3ValidateContacts( b3World* world ); + +// Register a hull in the world database, returning the owned shared copy. Identical hulls +// share one copy with a reference count. The input may be freed after this call. +const b3HullData* b3AddHullToDatabase( b3World* world, const b3HullData* src ); + +// Like b3AddHullToDatabase but takes ownership of a heap hull: inserted directly on a miss, +// freed on a hit. Avoids cloning data the caller already allocated. +const b3HullData* b3AddOwnedHullToDatabase( b3World* world, b3HullData* owned ); + +// Release a reference to a shared hull. The owned copy is freed when the count reaches zero. +void b3RemoveHullFromDatabase( b3World* world, const b3HullData* data ); + +static inline b3Manifold* b3AllocateManifolds( b3World* world, int count ) +{ + if ( count == 0 ) + { + return NULL; + } + + int index = count - 1; + + // Need lock because this is called from the parallel narrow phase + b3LockMutex( world->manifoldAllocatorMutex ); + int currentCount = world->manifoldAllocators.count; + for ( int i = currentCount; i < count; ++i ) + { + b3BlockAllocator allocator = b3CreateBlockAllocator( ( i + 1 ) * sizeof( b3Manifold ), 2 * B3_BLOCK_SIZE ); + b3Array_Push( world->manifoldAllocators, allocator ); + } + + b3BlockAllocator* allocator = b3Array_Get( world->manifoldAllocators, index ); + b3Manifold* manifolds = (b3Manifold*)b3AllocateElement( allocator ); + b3UnlockMutex( world->manifoldAllocatorMutex ); + memset( manifolds, 0, count * sizeof( b3Manifold ) ); + return manifolds; +} + +static inline void b3FreeManifolds( b3World* world, b3Manifold* manifolds, int count ) +{ + if ( count == 0 ) + { + return; + } + + int index = count - 1; + b3LockMutex( world->manifoldAllocatorMutex ); + b3BlockAllocator* allocator = b3Array_Get( world->manifoldAllocators, index ); + b3FreeElement( allocator, manifolds ); + b3UnlockMutex( world->manifoldAllocatorMutex ); +} + diff --git a/vendor/box3d/src/src/platform.h b/vendor/box3d/src/src/platform.h new file mode 100644 index 000000000..2bc37a591 --- /dev/null +++ b/vendor/box3d/src/src/platform.h @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core.h" + +#include +#include + +// Software prefetch hint. T0 brings the line into all cache levels. +// On x86 MSVC exposes _mm_prefetch, ARM MSVC uses __prefetch instead. +// clang/gcc provide __builtin_prefetch on every target. +#if defined( B3_COMPILER_MSVC ) +#include +#if defined( B3_CPU_X86_X64 ) +#define b3Prefetch( addr ) _mm_prefetch( (const char*)( addr ), _MM_HINT_T0 ) +#else +#define b3Prefetch( addr ) __prefetch( (const void*)( addr ) ) +#endif +#elif defined( B3_COMPILER_CLANG ) || defined( B3_COMPILER_GCC ) +#define b3Prefetch( addr ) __builtin_prefetch( (const void*)( addr ), 0, 3 ) +#else +#define b3Prefetch( addr ) ( (void)( addr ) ) +#endif + +static inline void b3AtomicStoreInt( b3AtomicInt* a, int value ) +{ +#if defined( _MSC_VER ) + (void)_InterlockedExchange( (long*)&a->value, value ); +#elif defined( __GNUC__ ) || defined( __clang__ ) + __atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST ); +#else +#error "Unsupported platform" +#endif +} + +static inline int b3AtomicLoadInt( b3AtomicInt* a ) +{ +#if defined( _MSC_VER ) && !defined( __clang__ ) + int value = __iso_volatile_load32( (volatile __int32*)&a->value ); +#if defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC ) + __dmb( 0xB ); +#else + _ReadWriteBarrier(); +#endif + return value; +#elif defined( __GNUC__ ) || defined( __clang__ ) + return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST ); +#else +#error "Unsupported platform" +#endif +} + +static inline int b3AtomicFetchAddInt( b3AtomicInt* a, int increment ) +{ +#if defined( _MSC_VER ) + return _InterlockedExchangeAdd( (long*)&a->value, (long)increment ); +#elif defined( __GNUC__ ) || defined( __clang__ ) + return __atomic_fetch_add( &a->value, increment, __ATOMIC_SEQ_CST ); +#else +#error "Unsupported platform" +#endif +} + +static inline bool b3AtomicCompareExchangeInt( b3AtomicInt* a, int expected, int desired ) +{ +#if defined( _MSC_VER ) + return _InterlockedCompareExchange( (long*)&a->value, (long)desired, (long)expected ) == expected; +#elif defined( __GNUC__ ) || defined( __clang__ ) + // The value written to expected is ignored + return __atomic_compare_exchange_n( &a->value, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); +#else +#error "Unsupported platform" +#endif +} + +static inline void b3AtomicStoreU32( b3AtomicU32* a, uint32_t value ) +{ +#if defined( _MSC_VER ) + (void)_InterlockedExchange( (long*)&a->value, value ); +#elif defined( __GNUC__ ) || defined( __clang__ ) + __atomic_store_n( &a->value, value, __ATOMIC_SEQ_CST ); +#else +#error "Unsupported platform" +#endif +} + +static inline uint32_t b3AtomicLoadU32( b3AtomicU32* a ) +{ +#if defined( _MSC_VER ) && !defined( __clang__ ) + uint32_t value = (uint32_t)__iso_volatile_load32( (volatile __int32*)&a->value ); +#if defined( _M_ARM ) || defined( _M_ARM64 ) || defined( _M_ARM64EC ) + __dmb( 0xB ); +#else + _ReadWriteBarrier(); +#endif + return value; +#elif defined( __GNUC__ ) || defined( __clang__ ) + return __atomic_load_n( &a->value, __ATOMIC_SEQ_CST ); +#else +#error "Unsupported platform" +#endif +} diff --git a/vendor/box3d/src/src/prismatic_joint.c b/vendor/box3d/src/src/prismatic_joint.c new file mode 100644 index 000000000..c96e6d643 --- /dev/null +++ b/vendor/box3d/src/src/prismatic_joint.c @@ -0,0 +1,725 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "core.h" +#include "joint.h" +#include "math_internal.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +// Linear constraint (point-to-line) +// joint axis is along joint frame A local z-axis +// perpX and perpY are world vectors fixed in A +// +// d = pB - pA = xB + rB - xA - rA +// Cx = dot(perpX, d) +// Cy = dot(perpY, d) + +// CdotX = dot(d, cross(wA, perpX)) + dot(perpX, vB + cross(wB, rB) - vA - cross(wA, rA)) +// = -dot(perpX, vA) - dot(cross(d + rA, perpX), wA) + dot(perpX, vB) + dot(cross(rB, perpX), vB) +// Jx = [-perpX, -cross(d + rA, perpX), perpX, cross(rB, perpX)] +// similar for perpY +// +// Simplification dropping dot(d, cross(wA, perpX)) (todo needs testing) +// CdotXs = dot(perpX, vB + cross(wB, rB) - vA - cross(wA, rA)) +// Jxs = [-perpX, -cross(rA, perpX), perpX, cross(rB, perpX)] + +// Motor/limit/spring linear constraint +// axis is the world joint axis fixed in A + +// C = dot(axis, d) +// Cdot = dot(d, cross(wA, axis)) + dot(axis, vB + cross(wB, rB) - vA - cross(wA, rA)) +// Cdot = -dot(axis, vA) - dot(cross(d + rA, axis), wA) + dot(axis, vB) + dot(cross(rB, axis), vB) +// J = [-axis -cross(d + rA, axis) axis cross(rB, axis)] +// +// Simplified (todo needs testing) +// Cdot = -dot(axis, vA) - dot(cross(rA, axis), wA) + dot(axis, vB) + dot(cross(rB, axis), vB) +// J = [-axis -cross(rA, axis) axis cross(rB, axis)] + +// Predictive limit is applied even when the limit is not active. +// Prevents a constraint speed that can lead to a constraint error in one time step. +// Want C2 = C1 + h * Cdot >= 0 +// Or: +// Cdot + C1/h >= 0 +// I do not apply a negative constraint error because that is handled in position correction. +// So: +// Cdot + max(C1, 0)/h >= 0 + +void b3PrismaticJoint_EnableLimit( b3JointId jointId, bool enableLimit ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointEnableLimit, jointId, enableLimit ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + if ( enableLimit != base->prismaticJoint.enableLimit ) + { + base->prismaticJoint.lowerImpulse = 0.0f; + base->prismaticJoint.upperImpulse = 0.0f; + } + base->prismaticJoint.enableLimit = enableLimit; +} + +bool b3PrismaticJoint_IsLimitEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.enableLimit; +} + +float b3PrismaticJoint_GetLowerLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.lowerTranslation; +} + +float b3PrismaticJoint_GetUpperLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.upperTranslation; +} + +void b3PrismaticJoint_SetLimits( b3JointId jointId, float lower, float upper ) +{ + B3_ASSERT( b3IsValidFloat( lower ) && b3IsValidFloat( upper ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointSetLimits, jointId, lower, upper ); + float lowerAngle = b3MinFloat( lower, upper ); + float upperAngle = b3MaxFloat( lower, upper ); + + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + base->prismaticJoint.lowerTranslation = lowerAngle; + base->prismaticJoint.upperTranslation = upperAngle; +} + +float b3PrismaticJoint_GetTranslation( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB ); + + b3Vec3 jointAxis = b3RotateVector( base->localFrameA.q, b3Vec3_axisX ); + jointAxis = b3RotateVector( transformA.q, jointAxis ); + + b3Vec3 anchorA = b3RotateVector( transformA.q, base->localFrameA.p ); + b3Vec3 anchorB = b3RotateVector( transformB.q, base->localFrameB.p ); + b3Vec3 d = b3Add( b3SubPos( transformB.p, transformA.p ), b3Sub( anchorB, anchorA ) ); + float translation = b3Dot( d, jointAxis ); + return translation; +} + +void b3PrismaticJoint_EnableSpring( b3JointId jointId, bool enableSpring ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointEnableSpring, jointId, enableSpring ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + if ( enableSpring != base->prismaticJoint.enableSpring ) + { + base->prismaticJoint.springImpulse = 0.0f; + } + base->prismaticJoint.enableSpring = enableSpring; +} + +bool b3PrismaticJoint_IsSpringEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.enableSpring; +} + +void b3PrismaticJoint_SetTargetTranslation( b3JointId jointId, float targetTranslation ) +{ + B3_ASSERT( b3IsValidFloat( targetTranslation ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointSetTargetTranslation, jointId, targetTranslation ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + base->prismaticJoint.targetTranslation = targetTranslation; +} + +float b3PrismaticJoint_GetTargetTranslation( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.targetTranslation; +} + +void b3PrismaticJoint_SetSpringHertz( b3JointId jointId, float hertz ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointSetSpringHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + base->prismaticJoint.hertz = hertz; +} + +float b3PrismaticJoint_GetSpringHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.hertz; +} + +void b3PrismaticJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointSetSpringDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + base->prismaticJoint.dampingRatio = dampingRatio; +} + +float b3PrismaticJoint_GetSpringDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.dampingRatio; +} + +void b3PrismaticJoint_EnableMotor( b3JointId jointId, bool enableMotor ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointEnableMotor, jointId, enableMotor ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + if ( enableMotor != base->prismaticJoint.enableMotor ) + { + base->prismaticJoint.motorImpulse = 0.0f; + } + base->prismaticJoint.enableMotor = enableMotor; +} + +bool b3PrismaticJoint_IsMotorEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.enableMotor; +} + +void b3PrismaticJoint_SetMotorSpeed( b3JointId jointId, float motorSpeed ) +{ + B3_ASSERT( b3IsValidFloat( motorSpeed ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointSetMotorSpeed, jointId, motorSpeed ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + base->prismaticJoint.motorSpeed = motorSpeed; +} + +float b3PrismaticJoint_GetMotorSpeed( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.motorSpeed; +} + +void b3PrismaticJoint_SetMaxMotorForce( b3JointId jointId, float maxForce ) +{ + B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, PrismaticJointSetMaxMotorForce, jointId, maxForce ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + base->prismaticJoint.maxMotorForce = maxForce; +} + +float b3PrismaticJoint_GetMaxMotorForce( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return base->prismaticJoint.maxMotorForce; +} + +float b3PrismaticJoint_GetMotorForce( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + return world->inv_h * base->prismaticJoint.motorImpulse; +} + +float b3PrismaticJoint_GetSpeed( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_prismaticJoint ); + + b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB ); + b3BodySim* bodySimA = b3GetBodySim( world, bodyA ); + b3BodySim* bodySimB = b3GetBodySim( world, bodyB ); + b3BodyState* stateA = b3GetBodyState( world, bodyA ); + b3BodyState* stateB = b3GetBodyState( world, bodyB ); + + b3Quat qA = bodySimA->transform.q; + b3Quat qB = bodySimB->transform.q; + + b3Vec3 axisA = b3RotateVector( qA, b3RotateVector( base->localFrameA.q, b3Vec3_axisX ) ); + b3Vec3 rA = b3RotateVector( qA, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + b3Vec3 rB = b3RotateVector( qB, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + // Difference the centers in double so the speed stays exact far from the origin. + b3Vec3 d = b3Add( b3SubPos( bodySimB->center, bodySimA->center ), b3Sub( rB, rA ) ); + + b3Vec3 vA = stateA ? stateA->linearVelocity : b3Vec3_zero; + b3Vec3 vB = stateB ? stateB->linearVelocity : b3Vec3_zero; + b3Vec3 wA = stateA ? stateA->angularVelocity : b3Vec3_zero; + b3Vec3 wB = stateB ? stateB->angularVelocity : b3Vec3_zero; + + b3Vec3 vRel = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) ); + + // The axis moves with body A, so account for its rotation. + float speed = b3Dot( d, b3Cross( wA, axisA ) ) + b3Dot( axisA, vRel ); + return speed; +} + +b3Vec3 b3GetPrismaticJointForce( b3World* world, b3JointSim* base ) +{ + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3PrismaticJoint* joint = &base->prismaticJoint; + + // impulse in joint space + b3Vec3 impulse = { + joint->perpImpulse.x, + joint->perpImpulse.y, + joint->motorImpulse + joint->lowerImpulse + joint->upperImpulse + joint->springImpulse, + }; + + // convert impulse to force + b3Vec3 force = b3MulSV( world->inv_h, impulse ); + + // convert to body space + force = b3RotateVector( base->localFrameA.q, force ); + + // convert to world space + force = b3RotateVector( transformA.q, force ); + return force; +} + +b3Vec3 b3GetPrismaticJointTorque( b3World* world, b3JointSim* base ) +{ + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3PrismaticJoint* joint = &base->prismaticJoint; + + b3Vec3 torque = b3MulSV( world->inv_h, joint->angularImpulse ); + torque = b3RotateVector( base->localFrameA.q, torque ); + torque = b3RotateVector( transformA.q, torque ); + return torque; +} + +void b3PreparePrismaticJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_prismaticJoint ); + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB ); + + B3_ASSERT( bodyB->setIndex == b3_awakeSet ); + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3PrismaticJoint* joint = &base->prismaticJoint; + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + joint->rotationMass = b3InvertMatrix( invInertiaSum ); + + // Initial joint axes in world space + b3Matrix3 matrixA = b3MakeMatrixFromQuat( joint->frameA.q ); + joint->jointAxis = matrixA.cx; + joint->perpAxisY = matrixA.cy; + joint->perpAxisZ = matrixA.cz; + + joint->springSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h ); + + if ( context->enableWarmStarting == false ) + { + joint->perpImpulse = (b3Vec2){ 0.0f, 0.0f }; + joint->angularImpulse = (b3Vec3){ 0.0f, 0.0f, 0.0f }; + joint->motorImpulse = 0.0f; + joint->springImpulse = 0.0f; + joint->lowerImpulse = 0.0f; + joint->upperImpulse = 0.0f; + } +} + +void b3WarmStartPrismaticJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_prismaticJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3PrismaticJoint* joint = &base->prismaticJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + // todo make this code and the wheel joint more similar + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + b3Vec3 d = b3Add( b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), joint->deltaCenter ), b3Sub( rB, rA ) ); + b3Vec3 jointAxis = b3RotateVector( stateA->deltaRotation, joint->jointAxis ); + b3Vec3 sAx = b3Cross( b3Add( rA, d ), jointAxis ); + b3Vec3 sBx = b3Cross( rB, jointAxis ); + + b3Vec3 perpY = b3RotateVector( stateA->deltaRotation, joint->perpAxisY ); + b3Vec3 perpZ = b3RotateVector( stateA->deltaRotation, joint->perpAxisZ ); + b3Vec3 sAy = b3Cross( b3Add( rA, d ), perpY ); + b3Vec3 sBy = b3Cross( rB, perpY ); + b3Vec3 sAz = b3Cross( b3Add( rA, d ), perpZ ); + b3Vec3 sBz = b3Cross( rB, perpZ ); + + float axialImpulse = joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse; + b3Vec2 perpImpulse = joint->perpImpulse; + + b3Vec3 P = b3Blend3( axialImpulse, jointAxis, perpImpulse.x, perpY, perpImpulse.y, perpZ ); + b3Vec3 LA = b3Add( b3Blend3( axialImpulse, sAx, perpImpulse.x, sAy, perpImpulse.y, sAz ), joint->angularImpulse ); + b3Vec3 LB = b3Add( b3Blend3( axialImpulse, sBx, perpImpulse.x, sBy, perpImpulse.y, sBz ), joint->angularImpulse ); + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, LA ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, LB ) ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3SolvePrismaticJoint( b3JointSim* base, b3StepContext* context, bool useBias ) +{ + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3PrismaticJoint* joint = &base->prismaticJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + bool fixedRotation = base->fixedRotation; + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 dcA = stateA->deltaPosition; + b3Vec3 dcB = stateB->deltaPosition; + b3Vec3 d = b3Add( b3Add( b3Sub( dcB, dcA ), joint->deltaCenter ), b3Sub( rB, rA ) ); + + b3Vec3 jointAxis = b3RotateVector( stateA->deltaRotation, joint->jointAxis ); + b3Vec3 sAx = b3Cross( b3Add( rA, d ), jointAxis ); + b3Vec3 sBx = b3Cross( rB, jointAxis ); + float jointTranslation = b3Dot( d, jointAxis ); + float targetTranslation = joint->targetTranslation; + + // The axial effective mass must be fresh to avoid divergence when the joint is stressed + float ka = mA + mB + b3Dot( sAx, b3MulMV( iA, sAx ) ) + b3Dot( sBx, b3MulMV( iB, sBx ) ); + float axialMass = ka > 0.0f ? 1.0f / ka : 0.0f; + + // Solve spring + if ( joint->enableSpring && fixedRotation == false ) + { + // Get the substep relative rotation + float c = jointTranslation - targetTranslation; + + float bias = joint->springSoftness.biasRate * c; + float massScale = joint->springSoftness.massScale; + float impulseScale = joint->springSoftness.impulseScale; + + b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) ); + float cdot = b3Dot( vRel, jointAxis ); + float deltaImpulse = -massScale * axialMass * ( cdot + bias ) - impulseScale * joint->springImpulse; + joint->springImpulse += deltaImpulse; + + b3Vec3 P = b3MulSV( deltaImpulse, jointAxis ); + b3Vec3 LA = b3MulSV( deltaImpulse, sAx ); + b3Vec3 LB = b3MulSV( deltaImpulse, sBx ); + + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, LA ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, LB ) ); + } + + if ( joint->enableMotor && fixedRotation == false ) + { + b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) ); + float cdot = b3Dot( vRel, jointAxis ) - joint->motorSpeed; + + float deltaImpulse = -axialMass * cdot; + float newImpulse = joint->motorImpulse + deltaImpulse; + float maxImpulse = joint->maxMotorForce * context->h; + newImpulse = b3ClampFloat( newImpulse, -maxImpulse, maxImpulse ); + deltaImpulse = newImpulse - joint->motorImpulse; + joint->motorImpulse = newImpulse; + + b3Vec3 P = b3MulSV( deltaImpulse, jointAxis ); + b3Vec3 LA = b3MulSV( deltaImpulse, sAx ); + b3Vec3 LB = b3MulSV( deltaImpulse, sBx ); + + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, LA ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, LB ) ); + } + + if ( joint->enableLimit && fixedRotation == false ) + { + float speculativeDistance = 0.25f * ( joint->upperTranslation - joint->lowerTranslation ); + + // Lower limit + { + float C = jointTranslation - joint->lowerTranslation; + + if ( C < speculativeDistance ) + { + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( C > 0.0f ) + { + // speculation + bias = C * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * C; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) ); + float cdot = b3Dot( vRel, jointAxis ); + float oldImpulse = joint->lowerImpulse; + float deltaImpulse = -massScale * axialMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->lowerImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + deltaImpulse = joint->lowerImpulse - oldImpulse; + + b3Vec3 P = b3MulSV( deltaImpulse, jointAxis ); + b3Vec3 LA = b3MulSV( deltaImpulse, sAx ); + b3Vec3 LB = b3MulSV( deltaImpulse, sBx ); + + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, LA ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, LB ) ); + } + else + { + joint->lowerImpulse = 0.0f; + } + } + + // Upper limit + { + float C = joint->upperTranslation - jointTranslation; + + if ( C < speculativeDistance ) + { + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( C > 0.0f ) + { + // speculation + bias = C * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * C; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // sign flipped on Cdot + b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) ); + float cdot = -b3Dot( vRel, jointAxis ); + float oldImpulse = joint->upperImpulse; + float deltaImpulse = -massScale * axialMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->upperImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + + // sign flipped on applied impulse + float negDeltaImpulse = oldImpulse - joint->upperImpulse; + b3Vec3 P = b3MulSV( negDeltaImpulse, jointAxis ); + b3Vec3 LA = b3MulSV( negDeltaImpulse, sAx ); + b3Vec3 LB = b3MulSV( negDeltaImpulse, sBx ); + + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, LA ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, LB ) ); + } + else + { + joint->upperImpulse = 0.0f; + } + } + } + + // Rotation constraint + if ( fixedRotation == false ) + { + b3Vec3 bias = { 0.0f, 0.0f, 0.0f }; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( useBias ) + { + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + b3Quat targetQuat = b3Quat_identity; + b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, targetQuat ); + b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) ); + + bias = b3MulSV( base->constraintSoftness.biasRate, c ); + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + b3Vec3 cdot = b3Sub( wB, wA ); + b3Vec3 impulse = b3Sub( + b3MulSV( -massScale, b3MulMV( joint->rotationMass, b3Add( cdot, bias ) ) ), + b3MulSV( impulseScale, joint->angularImpulse ) ); + joint->angularImpulse = b3Add( joint->angularImpulse, impulse ); + + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + + // Solve point-to-line constraint + { + b3Vec3 perpY = b3RotateVector( stateA->deltaRotation, joint->perpAxisY ); + b3Vec3 perpZ = b3RotateVector( stateA->deltaRotation, joint->perpAxisZ ); + + b3Vec2 bias = { 0.0f, 0.0f }; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias ) + { + b3Vec2 c = { b3Dot( perpY, d ), b3Dot( perpZ, d ) }; + bias = b3MulSV2( base->constraintSoftness.biasRate, c ); + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) ); + b3Vec2 cdot = { b3Dot( perpY, vRel ), b3Dot( perpZ, vRel ) }; + + // K = [(1/mA + 1/mB) * eye(2) - skew(rA) * invIA * skew(rA) - skew(rB) * invIB * skew(rB)] + // Jx = [-perpX, -cross(d + rA, perpX), perpX, cross(rB, perpX)] + b3Vec3 sAy = b3Cross( b3Add( rA, d ), perpY ); + b3Vec3 sBy = b3Cross( rB, perpY ); + b3Vec3 sAz = b3Cross( b3Add( rA, d ), perpZ ); + b3Vec3 sBz = b3Cross( rB, perpZ ); + + float kyy = mA + mB + b3Dot( sAy, b3MulMV( iA, sAy ) ) + b3Dot( sBy, b3MulMV( iB, sBy ) ); + float kyz = b3Dot( sAy, b3MulMV( iA, sAz ) ) + b3Dot( sBy, b3MulMV( iB, sBz ) ); + float kzz = mA + mB + b3Dot( sAz, b3MulMV( iA, sAz ) ) + b3Dot( sBz, b3MulMV( iB, sBz ) ); + + b3Matrix2 K = { { kyy, kyz }, { kyz, kzz } }; + + b3Vec2 oldImpulse = joint->perpImpulse; + b3Vec2 sol = b3Solve2( K, b3Add2( cdot, bias ) ); + b3Vec2 deltaImpulse = b3Sub2( b3MulSV2( -massScale, sol ), b3MulSV2( impulseScale, oldImpulse ) ); + joint->perpImpulse = b3Add2( oldImpulse, deltaImpulse ); + + b3Vec3 P = b3Blend2( deltaImpulse.x, perpY, deltaImpulse.y, perpZ ); + + vA = b3MulSub( vA, mA, P ); + wA = b3Sub( wA, b3MulMV( iA, b3Blend2( deltaImpulse.x, sAy, deltaImpulse.y, sAz ) ) ); + vB = b3MulAdd( vB, mB, P ); + wB = b3Add( wB, b3MulMV( iB, b3Blend2( deltaImpulse.x, sBy, deltaImpulse.y, sBz ) ) ); + } + + B3_ASSERT( b3IsValidVec3( vA ) ); + B3_ASSERT( b3IsValidVec3( wA ) ); + B3_ASSERT( b3IsValidVec3( vB ) ); + B3_ASSERT( b3IsValidVec3( wB ) ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3DrawPrismaticJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ) +{ + b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA ); + b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB ); + + b3Matrix3 R = b3MakeMatrixFromQuat( frameA.q ); + b3Vec3 axis = R.cx; + b3Vec3 perpY = R.cy; + b3Vec3 perpZ = R.cz; + + float s = 0.2f * scale; + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( s, perpY ) ), b3_colorGreen, draw->context ); + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( s, perpZ ) ), b3_colorBlue, draw->context ); + + b3PrismaticJoint* joint = &base->prismaticJoint; + if ( joint->enableLimit ) + { + b3Pos p1 = b3OffsetPos( frameA.p, b3MulSV( joint->lowerTranslation, axis ) ); + b3Pos p2 = b3OffsetPos( frameA.p, b3MulSV( joint->upperTranslation, axis ) ); + draw->DrawSegmentFcn( p1, p2, b3_colorOrange, draw->context ); + draw->DrawPointFcn( p1, 10.0f, b3_colorGreen, draw->context ); + draw->DrawPointFcn( p2, 10.0f, b3_colorRed, draw->context ); + } + else + { + b3Pos p1 = b3OffsetPos( frameA.p, b3MulSV( -0.5f * scale, axis ) ); + b3Pos p2 = b3OffsetPos( frameA.p, b3MulSV( 0.5f * scale, axis ) ); + draw->DrawSegmentFcn( p1, p2, b3_colorOrange, draw->context ); + } + + draw->DrawPointFcn( frameB.p, 8.0f, b3_colorViolet, draw->context ); +} diff --git a/vendor/box3d/src/src/qsort.h b/vendor/box3d/src/src/qsort.h new file mode 100644 index 000000000..9ab426bac --- /dev/null +++ b/vendor/box3d/src/src/qsort.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013, 2017 Alexey Tourbin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * This is a traditional Quicksort implementation which mostly follows + * [Sedgewick 1978]. Sorting is performed entirely on array indices, + * while actual access to the array elements is abstracted out with the + * user-defined `LESS` and `SWAP` primitives. + * + * Synopsis: + * QSORT(N, LESS, SWAP); + * where + * N - the number of elements in A[]; + * LESS(i, j) - compares A[i] to A[j]; + * SWAP(i, j) - exchanges A[i] with A[j]. + */ + +#ifndef QSORT_H +#define QSORT_H + +/* Sort 3 elements. */ +#define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ +do { \ + if (Q_LESS(q_a2, q_a1)) { \ + if (Q_LESS(q_a3, q_a2)) \ + Q_SWAP(q_a1, q_a3); \ + else { \ + Q_SWAP(q_a1, q_a2); \ + if (Q_LESS(q_a3, q_a2)) \ + Q_SWAP(q_a2, q_a3); \ + } \ + } \ + else if (Q_LESS(q_a3, q_a2)) { \ + Q_SWAP(q_a2, q_a3); \ + if (Q_LESS(q_a2, q_a1)) \ + Q_SWAP(q_a1, q_a2); \ + } \ +} while (0) + +/* Partition [q_l,q_r] around a pivot. After partitioning, + * [q_l,q_j] are the elements that are less than or equal to the pivot, + * while [q_i,q_r] are the elements greater than or equal to the pivot. */ +#define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ +do { \ + /* The middle element, not to be confused with the median. */ \ + Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \ + /* Reorder the second, the middle, and the last items. \ + * As [Edelkamp Weiss 2016] explain, using the second element \ + * instead of the first one helps avoid bad behaviour for \ + * decreasingly sorted arrays. This method is used in recent \ + * versions of gcc's std::sort, see gcc bug 58437#c13, although \ + * the details are somewhat different (cf. #c14). */ \ + Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \ + /* Place the median at the beginning. */ \ + Q_SWAP(q_l, q_m); \ + /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ + * q_i and q_j are initially off by one, they get decremented \ + * in the do-while loops. */ \ + q_i = q_l + 1; q_j = q_r; \ + while (1) { \ + do q_i++; while (Q_LESS(q_i, q_l)); \ + do q_j--; while (Q_LESS(q_l, q_j)); \ + if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \ + Q_SWAP(q_i, q_j); \ + } \ + /* Compensate for the i==j case. */ \ + q_i = q_j + 1; \ + /* Put the median to its final place. */ \ + Q_SWAP(q_l, q_j); \ + /* The median is not part of the left subfile. */ \ + q_j--; \ +} while (0) + +/* Insertion sort is applied to small subfiles - this is contrary to + * Sedgewick's suggestion to run a separate insertion sort pass after + * the partitioning is done. The reason I don't like a separate pass + * is that it triggers extra comparisons, because it can't see that the + * medians are already in their final positions and need not be rechecked. + * Since I do not assume that comparisons are cheap, I also do not try + * to eliminate the (q_j > q_l) boundary check. */ +#define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ +do { \ + Q_UINT q_i, q_j; \ + /* For each item starting with the second... */ \ + for (q_i = q_l + 1; q_i <= q_r; q_i++) \ + /* move it down the array so that the first part is sorted. */ \ + for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \ + Q_SWAP(q_j, q_j - 1); \ +} while (0) + +/* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to + * Q_THRESH, the algorithm performs recursive partitioning. When the size + * drops below Q_THRESH, the algorithm switches to insertion sort. + * The minimum valid value is probably 5 (with 5 items, the second and + * the middle items, the middle itself being rounded down, are distinct). */ +#define Q_THRESH 16 + +/* The main loop. */ +#define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ +do { \ + Q_UINT q_l = 0; \ + Q_UINT q_r = (Q_N) - 1; \ + Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ + struct { Q_UINT q_l, q_r; } \ + /* On 32-bit platforms, to sort a "char[3GB+]" array, \ + * it may take full 32 stack frames. On 64-bit CPUs, \ + * though, the address space is limited to 48 bits. \ + * The usage is further reduced if Q_N has a 32-bit type. */ \ + q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ + while (1) { \ + if (q_r - q_l + 1 >= Q_THRESH) { \ + Q_UINT q_i, q_j; \ + Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ + /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ + * Dealing with them depends on which one is bigger. */ \ + if (q_j - q_l >= q_r - q_i) \ + Q_SUBFILES(q_l, q_j, q_i, q_r); \ + else \ + Q_SUBFILES(q_i, q_r, q_l, q_j); \ + } \ + else { \ + Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ + /* Pop subfiles from the stack, until it gets empty. */ \ + if (q_sp == 0) break; \ + q_sp--; \ + q_l = q_st[q_sp].q_l; \ + q_r = q_st[q_sp].q_r; \ + } \ + } \ +} while (0) + +/* The missing part: dealing with subfiles. + * Assumes that the first subfile is not smaller than the second. */ +#define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ +do { \ + /* If the second subfile is only a single element, it needs \ + * no further processing. The first subfile will be processed \ + * on the next iteration (both subfiles cannot be only a single \ + * element, due to Q_THRESH). */ \ + if (q_l2 == q_r2) { \ + q_l = q_l1; \ + q_r = q_r1; \ + } \ + else { \ + /* Otherwise, both subfiles need processing. \ + * Push the larger subfile onto the stack. */ \ + q_st[q_sp].q_l = q_l1; \ + q_st[q_sp].q_r = q_r1; \ + q_sp++; \ + /* Process the smaller subfile on the next iteration. */ \ + q_l = q_l2; \ + q_r = q_r2; \ + } \ +} while (0) + +/* And now, ladies and gentlemen, may I proudly present to you... */ +#define QSORT(Q_N, Q_LESS, Q_SWAP) \ +do { \ + if ((Q_N) > 1) \ + /* We could check sizeof(Q_N) and use "unsigned", but at least \ + * on x86_64, this has the performance penalty of up to 5%. */ \ + Q_LOOP(unsigned long, Q_N, Q_LESS, Q_SWAP); \ +} while (0) + +#endif + +/* ex:set ts=8 sts=4 sw=4 noet: */ diff --git a/vendor/box3d/src/src/recording.c b/vendor/box3d/src/src/recording.c new file mode 100644 index 000000000..4d5c5995d --- /dev/null +++ b/vendor/box3d/src/src/recording.c @@ -0,0 +1,1279 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "recording.h" + +#include "body.h" +#include "compound.h" +#include "physics_world.h" +#include "world_snapshot.h" + +#include "box3d/box3d.h" +#include "box3d/constants.h" + +#include +#include + +// Buffer helpers + +void b3RecBufAppend( b3RecBuffer* buf, const void* data, int size ) +{ + if ( size <= 0 ) + { + return; + } + + if ( buf->countOnly ) + { + buf->size += size; + return; + } + + if ( buf->size + size > buf->capacity ) + { + int newCap = buf->capacity * 2; + if ( newCap < buf->size + size + 64 ) + { + newCap = buf->size + size + 64; + } + if ( buf->data == NULL ) + { + buf->data = b3Alloc( (size_t)newCap ); + } + else + { + buf->data = b3GrowAlloc( buf->data, buf->capacity, newCap ); + } + buf->capacity = newCap; + } + + memcpy( buf->data + buf->size, data, (size_t)size ); + buf->size += size; +} + +void b3RecBufFree( b3RecBuffer* buf ) +{ + if ( buf->data != NULL ) + { + b3Free( buf->data, (size_t)buf->capacity ); + buf->data = NULL; + buf->capacity = 0; + buf->size = 0; + } +} + +// Write primitives + +void b3RecW_U8( b3RecBuffer* buf, uint8_t v ) +{ + b3RecBufAppend( buf, &v, 1 ); +} + +void b3RecW_U16( b3RecBuffer* buf, uint16_t v ) +{ + uint8_t b[2] = { (uint8_t)v, (uint8_t)( v >> 8 ) }; + b3RecBufAppend( buf, b, 2 ); +} + +void b3RecW_U32( b3RecBuffer* buf, uint32_t v ) +{ + uint8_t b[4] = { (uint8_t)v, (uint8_t)( v >> 8 ), (uint8_t)( v >> 16 ), (uint8_t)( v >> 24 ) }; + b3RecBufAppend( buf, b, 4 ); +} + +void b3RecW_U64( b3RecBuffer* buf, uint64_t v ) +{ + uint8_t b[8] = { (uint8_t)v, (uint8_t)( v >> 8 ), (uint8_t)( v >> 16 ), (uint8_t)( v >> 24 ), + (uint8_t)( v >> 32 ), (uint8_t)( v >> 40 ), (uint8_t)( v >> 48 ), (uint8_t)( v >> 56 ) }; + b3RecBufAppend( buf, b, 8 ); +} + +void b3RecW_I32( b3RecBuffer* buf, int32_t v ) +{ + b3RecW_U32( buf, (uint32_t)v ); +} + +void b3RecW_F32( b3RecBuffer* buf, float v ) +{ + uint32_t bits; + memcpy( &bits, &v, 4 ); + b3RecW_U32( buf, bits ); +} + +void b3RecW_F64( b3RecBuffer* buf, double v ) +{ + uint64_t bits; + memcpy( &bits, &v, 8 ); + b3RecW_U64( buf, bits ); +} + +void b3RecW_BOOL( b3RecBuffer* buf, bool v ) +{ + b3RecW_U8( buf, v ? 1u : 0u ); +} + +void b3RecW_VEC3( b3RecBuffer* buf, b3Vec3 v ) +{ + b3RecW_F32( buf, v.x ); + b3RecW_F32( buf, v.y ); + b3RecW_F32( buf, v.z ); +} + +void b3RecW_QUAT( b3RecBuffer* buf, b3Quat v ) +{ + b3RecW_F32( buf, v.v.x ); + b3RecW_F32( buf, v.v.y ); + b3RecW_F32( buf, v.v.z ); + b3RecW_F32( buf, v.s ); +} + +void b3RecW_TRANSFORM( b3RecBuffer* buf, b3Transform v ) +{ + b3RecW_VEC3( buf, v.p ); + b3RecW_QUAT( buf, v.q ); +} + +// World position at full precision so recordings reproduce the simulation far from the origin. +// In the float build this is three floats, wire-identical to VEC3. +void b3RecW_POSITION( b3RecBuffer* buf, b3Pos v ) +{ +#if defined( BOX3D_DOUBLE_PRECISION ) + b3RecW_F64( buf, v.x ); + b3RecW_F64( buf, v.y ); + b3RecW_F64( buf, v.z ); +#else + b3RecW_F32( buf, v.x ); + b3RecW_F32( buf, v.y ); + b3RecW_F32( buf, v.z ); +#endif +} + +void b3RecW_WORLDXF( b3RecBuffer* buf, b3WorldTransform v ) +{ + b3RecW_POSITION( buf, v.p ); + b3RecW_QUAT( buf, v.q ); +} + +void b3RecW_MATRIX3( b3RecBuffer* buf, b3Matrix3 v ) +{ + b3RecW_VEC3( buf, v.cx ); + b3RecW_VEC3( buf, v.cy ); + b3RecW_VEC3( buf, v.cz ); +} + +void b3RecW_AABB( b3RecBuffer* buf, b3AABB v ) +{ + b3RecW_VEC3( buf, v.lowerBound ); + b3RecW_VEC3( buf, v.upperBound ); +} + +void b3RecW_QUERYFILTER( b3RecBuffer* buf, b3QueryFilter v ) +{ + b3RecW_U64( buf, v.categoryBits ); + b3RecW_U64( buf, v.maskBits ); +} + +// Variable length: count, then count points, then radius. The point cloud lives behind a pointer so +// it cannot ride along as POD. +void b3RecW_SHAPEPROXY( b3RecBuffer* buf, b3ShapeProxy v ) +{ + int count = v.count; + if ( count < 0 ) + count = 0; + if ( count > B3_MAX_SHAPE_CAST_POINTS ) + count = B3_MAX_SHAPE_CAST_POINTS; + b3RecW_I32( buf, count ); + for ( int i = 0; i < count; ++i ) + { + b3RecW_VEC3( buf, v.points[i] ); + } + b3RecW_F32( buf, v.radius ); +} + +void b3RecW_TREESTATS( b3RecBuffer* buf, b3TreeStats v ) +{ + b3RecW_I32( buf, v.nodeVisits ); + b3RecW_I32( buf, v.leafVisits ); +} + +void b3RecW_RAYRESULT( b3RecBuffer* buf, b3RayResult v ) +{ + b3RecW_SHAPEID( buf, v.shapeId ); + b3RecW_POSITION( buf, v.point ); + b3RecW_VEC3( buf, v.normal ); + b3RecW_U64( buf, v.userMaterialId ); + b3RecW_F32( buf, v.fraction ); + b3RecW_I32( buf, v.triangleIndex ); + b3RecW_I32( buf, v.childIndex ); + b3RecW_BOOL( buf, v.hit ); +} + +void b3RecW_PLANERESULT( b3RecBuffer* buf, b3PlaneResult v ) +{ + b3RecW_VEC3( buf, v.plane.normal ); + b3RecW_F32( buf, v.plane.offset ); + b3RecW_VEC3( buf, v.point ); +} + +void b3RecW_WORLDID( b3RecBuffer* buf, b3WorldId v ) +{ + b3RecW_U32( buf, b3StoreWorldId( v ) ); +} + +void b3RecW_BODYID( b3RecBuffer* buf, b3BodyId v ) +{ + b3RecW_U64( buf, b3StoreBodyId( v ) ); +} + +void b3RecW_SHAPEID( b3RecBuffer* buf, b3ShapeId v ) +{ + b3RecW_U64( buf, b3StoreShapeId( v ) ); +} + +void b3RecW_JOINTID( b3RecBuffer* buf, b3JointId v ) +{ + b3RecW_U64( buf, b3StoreJointId( v ) ); +} + +// Pointer-free POD; pointerWidth in the header gates the layout on replay +void b3RecW_SPHERE( b3RecBuffer* buf, b3Sphere v ) +{ + b3RecBufAppend( buf, &v, (int)sizeof( b3Sphere ) ); +} + +void b3RecW_CAPSULE( b3RecBuffer* buf, b3Capsule v ) +{ + b3RecBufAppend( buf, &v, (int)sizeof( b3Capsule ) ); +} + +void b3RecW_GEOMID( b3RecBuffer* buf, uint32_t v ) +{ + b3RecW_U32( buf, v ); +} + +void b3RecW_FILTER( b3RecBuffer* buf, b3Filter v ) +{ + b3RecW_U64( buf, v.categoryBits ); + b3RecW_U64( buf, v.maskBits ); + b3RecW_I32( buf, v.groupIndex ); +} + +void b3RecW_MATERIAL( b3RecBuffer* buf, b3SurfaceMaterial v ) +{ + b3RecW_F32( buf, v.friction ); + b3RecW_F32( buf, v.restitution ); + b3RecW_F32( buf, v.rollingResistance ); + b3RecW_VEC3( buf, v.tangentVelocity ); + b3RecW_U64( buf, v.userMaterialId ); + b3RecW_U32( buf, v.customColor ); +} + +void b3RecW_MASSDATA( b3RecBuffer* buf, b3MassData v ) +{ + b3RecW_F32( buf, v.mass ); + b3RecW_VEC3( buf, v.center ); + b3RecW_MATRIX3( buf, v.inertia ); +} + +void b3RecW_LOCKS( b3RecBuffer* buf, b3MotionLocks v ) +{ + b3RecW_BOOL( buf, v.linearX ); + b3RecW_BOOL( buf, v.linearY ); + b3RecW_BOOL( buf, v.linearZ ); + b3RecW_BOOL( buf, v.angularX ); + b3RecW_BOOL( buf, v.angularY ); + b3RecW_BOOL( buf, v.angularZ ); +} + +static void b3RecW_STR( b3RecBuffer* buf, const char* s ) +{ + if ( s == NULL ) + { + b3RecW_U16( buf, 0xFFFFu ); + return; + } + int len = 0; + while ( s[len] != '\0' && len < 65534 ) + { + len++; + } + b3RecW_U16( buf, (uint16_t)len ); + if ( len > 0 ) + { + b3RecBufAppend( buf, s, len ); + } +} + +// Hand-written def helpers. Zero pointer and cookie fields before serializing. +// Readers call b3Default*Def() first to get the cookie, then overwrite fields. + +// Tripwire: each def serializer below is paired with a reader in recording_replay.c, and the two must +// stay field-for-field in sync. Add a field to a def and the size changes, firing the matching assert +// so the writer and reader both get updated. Only enforced on the 64-bit target; each def lists the +// single-precision and double-precision sizes (equal for most), so either build configuration passes. +_Static_assert( sizeof( void* ) != 8 || sizeof( b3ExplosionDef ) == 32 || sizeof( b3ExplosionDef ) == 48, + "b3ExplosionDef changed: update b3RecW_EXPLOSIONDEF and b3RecR_EXPLOSIONDEF together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3BodyDef ) == 104 || sizeof( b3BodyDef ) == 120, + "b3BodyDef changed: update b3RecW_BODYDEF and b3RecR_BODYDEF together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3ShapeDef ) == 120, + "b3ShapeDef changed: update b3RecW_SHAPEDEF and b3RecR_SHAPEDEF together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3ParallelJointDef ) == 128, + "b3ParallelJointDef changed: update b3RecW_PARALLELJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3DistanceJointDef ) == 160, + "b3DistanceJointDef changed: update b3RecW_DISTANCEJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3FilterJointDef ) == 112, + "b3FilterJointDef changed: update b3RecW_FILTERJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3MotorJointDef ) == 168, + "b3MotorJointDef changed: update b3RecW_MOTORJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3PrismaticJointDef ) == 152, + "b3PrismaticJointDef changed: update b3RecW_PRISMATICJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3RevoluteJointDef ) == 152, + "b3RevoluteJointDef changed: update b3RecW_REVOLUTEJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3SphericalJointDef ) == 184, + "b3SphericalJointDef changed: update b3RecW_SPHERICALJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3WeldJointDef ) == 128, + "b3WeldJointDef changed: update b3RecW_WELDJOINTDEF and its reader together" ); +_Static_assert( sizeof( void* ) != 8 || sizeof( b3WheelJointDef ) == 184, + "b3WheelJointDef changed: update b3RecW_WHEELJOINTDEF and its reader together" ); + +void b3RecW_EXPLOSIONDEF( b3RecBuffer* buf, b3ExplosionDef v ) +{ + b3RecW_U64( buf, v.maskBits ); + b3RecW_POSITION( buf, v.position ); + b3RecW_F32( buf, v.radius ); + b3RecW_F32( buf, v.falloff ); + b3RecW_F32( buf, v.impulsePerArea ); +} + +void b3RecW_BODYDEF( b3RecBuffer* buf, b3BodyDef v ) +{ + b3RecW_I32( buf, (int32_t)v.type ); + b3RecW_POSITION( buf, v.position ); + b3RecW_QUAT( buf, v.rotation ); + b3RecW_VEC3( buf, v.linearVelocity ); + b3RecW_VEC3( buf, v.angularVelocity ); + b3RecW_F32( buf, v.linearDamping ); + b3RecW_F32( buf, v.angularDamping ); + b3RecW_F32( buf, v.gravityScale ); + b3RecW_F32( buf, v.sleepThreshold ); + b3RecW_STR( buf, v.name ); + // userData: not preserved + b3RecW_U64( buf, 0u ); + b3RecW_LOCKS( buf, v.motionLocks ); + b3RecW_BOOL( buf, v.enableSleep ); + b3RecW_BOOL( buf, v.isAwake ); + b3RecW_BOOL( buf, v.isBullet ); + b3RecW_BOOL( buf, v.isEnabled ); + b3RecW_BOOL( buf, v.allowFastRotation ); + b3RecW_BOOL( buf, v.enableContactRecycling ); + // internalValue omitted +} + +void b3RecW_SHAPEDEF( b3RecBuffer* buf, b3ShapeDef v ) +{ + b3RecW_STR( buf, v.name ); + + // userData: not preserved + b3RecW_U64( buf, 0u ); + // Per-triangle materials: length-prefixed so the reader can rebuild the array. + // Guard NULL so a default def (materialCount=0, materials=NULL) round-trips cleanly. + int matCount = ( v.materials != NULL ) ? v.materialCount : 0; + b3RecW_I32( buf, matCount ); + for ( int i = 0; i < matCount; ++i ) + { + b3RecW_MATERIAL( buf, v.materials[i] ); + } + b3RecW_MATERIAL( buf, v.baseMaterial ); + b3RecW_F32( buf, v.density ); + b3RecW_F32( buf, v.explosionScale ); + b3RecW_FILTER( buf, v.filter ); + b3RecW_BOOL( buf, v.enableCustomFiltering ); + b3RecW_BOOL( buf, v.isSensor ); + b3RecW_BOOL( buf, v.enableSensorEvents ); + b3RecW_BOOL( buf, v.enableContactEvents ); + b3RecW_BOOL( buf, v.enableHitEvents ); + b3RecW_BOOL( buf, v.enablePreSolveEvents ); + b3RecW_BOOL( buf, v.invokeContactCreation ); + b3RecW_BOOL( buf, v.updateBodyMass ); + b3RecW_BOOL( buf, v.enableSpeculativeContact ); + // internalValue omitted +} + +// Joint defs share a base. Body ids are written as packed ids for replay remapping. +static void b3RecW_JointBase( b3RecBuffer* buf, const b3JointDef* base ) +{ + // userData: not preserved + b3RecW_U64( buf, 0u ); + b3RecW_BODYID( buf, base->bodyIdA ); + b3RecW_BODYID( buf, base->bodyIdB ); + b3RecW_TRANSFORM( buf, base->localFrameA ); + b3RecW_TRANSFORM( buf, base->localFrameB ); + b3RecW_F32( buf, base->forceThreshold ); + b3RecW_F32( buf, base->torqueThreshold ); + b3RecW_F32( buf, base->constraintHertz ); + b3RecW_F32( buf, base->constraintDampingRatio ); + b3RecW_F32( buf, base->drawScale ); + b3RecW_BOOL( buf, base->collideConnected ); + // internalValue omitted +} + +void b3RecW_PARALLELJOINTDEF( b3RecBuffer* buf, b3ParallelJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_F32( buf, v.hertz ); + b3RecW_F32( buf, v.dampingRatio ); + b3RecW_F32( buf, v.maxTorque ); +} + +void b3RecW_DISTANCEJOINTDEF( b3RecBuffer* buf, b3DistanceJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_F32( buf, v.length ); + b3RecW_BOOL( buf, v.enableSpring ); + b3RecW_F32( buf, v.lowerSpringForce ); + b3RecW_F32( buf, v.upperSpringForce ); + b3RecW_F32( buf, v.hertz ); + b3RecW_F32( buf, v.dampingRatio ); + b3RecW_BOOL( buf, v.enableLimit ); + b3RecW_F32( buf, v.minLength ); + b3RecW_F32( buf, v.maxLength ); + b3RecW_BOOL( buf, v.enableMotor ); + b3RecW_F32( buf, v.maxMotorForce ); + b3RecW_F32( buf, v.motorSpeed ); +} + +void b3RecW_FILTERJOINTDEF( b3RecBuffer* buf, b3FilterJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); +} + +void b3RecW_MOTORJOINTDEF( b3RecBuffer* buf, b3MotorJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_VEC3( buf, v.linearVelocity ); + b3RecW_F32( buf, v.maxVelocityForce ); + b3RecW_VEC3( buf, v.angularVelocity ); + b3RecW_F32( buf, v.maxVelocityTorque ); + b3RecW_F32( buf, v.linearHertz ); + b3RecW_F32( buf, v.linearDampingRatio ); + b3RecW_F32( buf, v.maxSpringForce ); + b3RecW_F32( buf, v.angularHertz ); + b3RecW_F32( buf, v.angularDampingRatio ); + b3RecW_F32( buf, v.maxSpringTorque ); +} + +void b3RecW_PRISMATICJOINTDEF( b3RecBuffer* buf, b3PrismaticJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_BOOL( buf, v.enableSpring ); + b3RecW_F32( buf, v.hertz ); + b3RecW_F32( buf, v.dampingRatio ); + b3RecW_F32( buf, v.targetTranslation ); + b3RecW_BOOL( buf, v.enableLimit ); + b3RecW_F32( buf, v.lowerTranslation ); + b3RecW_F32( buf, v.upperTranslation ); + b3RecW_BOOL( buf, v.enableMotor ); + b3RecW_F32( buf, v.maxMotorForce ); + b3RecW_F32( buf, v.motorSpeed ); +} + +void b3RecW_REVOLUTEJOINTDEF( b3RecBuffer* buf, b3RevoluteJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_F32( buf, v.targetAngle ); + b3RecW_BOOL( buf, v.enableSpring ); + b3RecW_F32( buf, v.hertz ); + b3RecW_F32( buf, v.dampingRatio ); + b3RecW_BOOL( buf, v.enableLimit ); + b3RecW_F32( buf, v.lowerAngle ); + b3RecW_F32( buf, v.upperAngle ); + b3RecW_BOOL( buf, v.enableMotor ); + b3RecW_F32( buf, v.maxMotorTorque ); + b3RecW_F32( buf, v.motorSpeed ); +} + +void b3RecW_SPHERICALJOINTDEF( b3RecBuffer* buf, b3SphericalJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_BOOL( buf, v.enableSpring ); + b3RecW_F32( buf, v.hertz ); + b3RecW_F32( buf, v.dampingRatio ); + b3RecW_QUAT( buf, v.targetRotation ); + b3RecW_BOOL( buf, v.enableConeLimit ); + b3RecW_F32( buf, v.coneAngle ); + b3RecW_BOOL( buf, v.enableTwistLimit ); + b3RecW_F32( buf, v.lowerTwistAngle ); + b3RecW_F32( buf, v.upperTwistAngle ); + b3RecW_BOOL( buf, v.enableMotor ); + b3RecW_F32( buf, v.maxMotorTorque ); + b3RecW_VEC3( buf, v.motorVelocity ); +} + +void b3RecW_WELDJOINTDEF( b3RecBuffer* buf, b3WeldJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_F32( buf, v.linearHertz ); + b3RecW_F32( buf, v.angularHertz ); + b3RecW_F32( buf, v.linearDampingRatio ); + b3RecW_F32( buf, v.angularDampingRatio ); +} + +void b3RecW_WHEELJOINTDEF( b3RecBuffer* buf, b3WheelJointDef v ) +{ + b3RecW_JointBase( buf, &v.base ); + b3RecW_BOOL( buf, v.enableSuspensionSpring ); + b3RecW_F32( buf, v.suspensionHertz ); + b3RecW_F32( buf, v.suspensionDampingRatio ); + b3RecW_BOOL( buf, v.enableSuspensionLimit ); + b3RecW_F32( buf, v.lowerSuspensionLimit ); + b3RecW_F32( buf, v.upperSuspensionLimit ); + b3RecW_BOOL( buf, v.enableSpinMotor ); + b3RecW_F32( buf, v.maxSpinTorque ); + b3RecW_F32( buf, v.spinSpeed ); + b3RecW_BOOL( buf, v.enableSteering ); + b3RecW_F32( buf, v.steeringHertz ); + b3RecW_F32( buf, v.steeringDampingRatio ); + b3RecW_F32( buf, v.targetSteeringAngle ); + b3RecW_F32( buf, v.maxSteeringTorque ); + b3RecW_BOOL( buf, v.enableSteeringLimit ); + b3RecW_F32( buf, v.lowerSteeringLimit ); + b3RecW_F32( buf, v.upperSteeringLimit ); +} + +// Query recording. A query collects a variable number of hits through a user callback, so the count +// is not known until the callback stops firing. The record is built in a local buffer with a +// reserved hit-count slot, then committed whole under the lock so concurrent query threads never +// interleave records in the shared buffer. + +int b3RecReserveU32( b3RecBuffer* buf ) +{ + int offset = buf->size; + uint8_t zero[4] = { 0, 0, 0, 0 }; + b3RecBufAppend( buf, zero, 4 ); + return offset; +} + +void b3RecPatchU32( b3RecBuffer* buf, int offset, uint32_t v ) +{ + B3_ASSERT( offset >= 0 && offset + 4 <= buf->size ); + uint8_t* p = buf->data + offset; + p[0] = (uint8_t)v; + p[1] = (uint8_t)( v >> 8 ); + p[2] = (uint8_t)( v >> 16 ); + p[3] = (uint8_t)( v >> 24 ); +} + +// Frame and append one record into the buffer. Caller holds rec->lock. +static void b3RecCommitRecordLocked( b3Recording* rec, uint8_t opcode, const uint8_t* payload, int payloadSize ) +{ + B3_ASSERT( payloadSize >= 0 && payloadSize < ( 1 << 24 ) ); + b3RecW_U8( &rec->buffer, opcode ); + uint8_t sz[3] = { (uint8_t)payloadSize, (uint8_t)( payloadSize >> 8 ), (uint8_t)( payloadSize >> 16 ) }; + b3RecBufAppend( &rec->buffer, sz, 3 ); + b3RecBufAppend( &rec->buffer, payload, payloadSize ); +} + +void b3RecCommitRecord( b3Recording* rec, uint8_t opcode, const uint8_t* payload, int payloadSize ) +{ + b3LockMutex( rec->lock ); + b3RecCommitRecordLocked( rec, opcode, payload, payloadSize ); + b3UnlockMutex( rec->lock ); +} + +void b3RecQueryBegin( b3RecQueryWriter* w, void* context, uint64_t tagId, const char* tagName ) +{ + w->buf = (b3RecBuffer){ 0 }; + w->userFcn.overlapFcn = NULL; + w->userContext = context; + w->hitCount = 0; + w->countOffset = 0; + w->tagId = tagId; + w->tagName = tagName; +} + +void b3RecQueryCommit( b3Recording* rec, uint8_t opcode, b3RecQueryWriter* w ) +{ + b3LockMutex( rec->lock ); + // A tagged query writes its identity key right before the query record, under one lock so the pair + // stays adjacent even with concurrent queries. The key is the hash of the caller (id, name), which + // are interned once into the trailing tag table so the viewer can show them. + bool tagged = w->tagId != 0 || ( w->tagName != NULL && w->tagName[0] != '\0' ); + if ( tagged ) + { + uint64_t key = b3HashQueryTag( w->tagId, w->tagName ); + b3RecInternTag( rec, key, w->tagId, w->tagName ); + b3RecBuffer tagBuf = { 0 }; + b3RecW_U64( &tagBuf, key ); + b3RecCommitRecordLocked( rec, b3_recOpQueryTag, tagBuf.data, tagBuf.size ); + b3RecBufFree( &tagBuf ); + } + b3RecCommitRecordLocked( rec, opcode, w->buf.data, w->buf.size ); + b3UnlockMutex( rec->lock ); + b3RecBufFree( &w->buf ); +} + +bool b3RecOverlapTrampoline( b3ShapeId id, void* ctx ) +{ + b3RecQueryWriter* w = (b3RecQueryWriter*)ctx; + // The user fcn is NULL for an unfiltered mover cast: accept all, still record the decision so + // replay reproduces the same per-shape accept stream. + bool ret = w->userFcn.overlapFcn != NULL ? w->userFcn.overlapFcn( id, w->userContext ) : true; + b3RecW_SHAPEID( &w->buf, id ); + b3RecW_BOOL( &w->buf, ret ); + w->hitCount++; + return ret; +} + +float b3RecCastTrampoline( b3ShapeId id, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, int triangleIndex, + int childIndex, void* ctx ) +{ + b3RecQueryWriter* w = (b3RecQueryWriter*)ctx; + float ret = w->userFcn.castFcn( id, point, normal, fraction, userMaterialId, triangleIndex, childIndex, w->userContext ); + b3RecW_SHAPEID( &w->buf, id ); + b3RecW_POSITION( &w->buf, point ); + b3RecW_VEC3( &w->buf, normal ); + b3RecW_F32( &w->buf, fraction ); + b3RecW_U64( &w->buf, userMaterialId ); + b3RecW_I32( &w->buf, triangleIndex ); + b3RecW_I32( &w->buf, childIndex ); + b3RecW_F32( &w->buf, ret ); + w->hitCount++; + return ret; +} + +// 3D delivers every plane for one shape in a single call. Record the shape, its plane count, each +// plane, then the user return so replay reproduces the same per-shape batch. One hit per shape. +bool b3RecPlaneTrampoline( b3ShapeId id, const b3PlaneResult* planes, int planeCount, void* ctx ) +{ + b3RecQueryWriter* w = (b3RecQueryWriter*)ctx; + bool ret = w->userFcn.planeFcn( id, planes, planeCount, w->userContext ); + b3RecW_SHAPEID( &w->buf, id ); + b3RecW_I32( &w->buf, planeCount ); + for ( int i = 0; i < planeCount; ++i ) + { + b3RecW_PLANERESULT( &w->buf, planes[i] ); + } + b3RecW_BOOL( &w->buf, ret ); + w->hitCount++; + return ret; +} + +// Record framing + +void b3RecBeginRecord( b3Recording* rec, uint8_t opcode ) +{ + b3RecW_U8( &rec->buffer, opcode ); + rec->recordStart = rec->buffer.size; + // Reserve 3 bytes for the u24 payload size, backpatched in b3RecEndRecord. + uint8_t zero[3] = { 0, 0, 0 }; + b3RecBufAppend( &rec->buffer, zero, 3 ); +} + +void b3RecEndRecord( b3Recording* rec ) +{ + int payloadSize = rec->buffer.size - rec->recordStart - 3; + B3_ASSERT( payloadSize >= 0 && payloadSize < ( 1 << 24 ) ); + uint8_t* p = rec->buffer.data + rec->recordStart; + p[0] = (uint8_t)payloadSize; + p[1] = (uint8_t)( payloadSize >> 8 ); + p[2] = (uint8_t)( payloadSize >> 16 ); +} + +// Codegen pass 1b: arg writers +#define ARG( TAG, field ) b3RecW_##TAG( &rec->buffer, a->field ); +#define B3_REC_OP( op, Name, RET, ... ) \ + void b3RecWriteArgs_##Name( b3Recording* rec, const b3RecArgs_##Name* a ) \ + { \ + __VA_ARGS__ \ + } +#include "recording_ops.inl" +#undef B3_REC_OP +#undef ARG + +// Codegen: full writers. Setters may run on threads that each own a distinct object, +// so hold the lock across the whole record. Without it a concurrent writer splices its bytes between +// our begin and end and the record desyncs replay. Same lock the query commit path takes. +#define B3_REC_OP( op, Name, RET, ... ) \ + void b3RecWrite_##Name( b3Recording* rec, const b3RecArgs_##Name* a ) \ + { \ + b3LockMutex( rec->lock ); \ + b3RecBeginRecord( rec, (uint8_t)( op ) ); \ + b3RecWriteArgs_##Name( rec, a ); \ + b3RecEndRecord( rec ); \ + b3UnlockMutex( rec->lock ); \ + } +#include "recording_ops.inl" +#undef B3_REC_OP + +// Codegen: create-op writers that append the returned id inside the record +#define B3_REC_RETWRITE( op, Name, idType, idW ) \ + void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, idType id ) \ + { \ + b3LockMutex( rec->lock ); \ + b3RecBeginRecord( rec, (uint8_t)( op ) ); \ + b3RecWriteArgs_##Name( rec, a ); \ + idW( &rec->buffer, id ); \ + b3RecEndRecord( rec ); \ + b3UnlockMutex( rec->lock ); \ + } +#define B3_REC_RETWRITE_RET_NONE( op, Name ) +#define B3_REC_RETWRITE_RET_BODYID( op, Name ) B3_REC_RETWRITE( op, Name, b3BodyId, b3RecW_BODYID ) +#define B3_REC_RETWRITE_RET_SHAPEID( op, Name ) B3_REC_RETWRITE( op, Name, b3ShapeId, b3RecW_SHAPEID ) +#define B3_REC_RETWRITE_RET_JOINTID( op, Name ) B3_REC_RETWRITE( op, Name, b3JointId, b3RecW_JOINTID ) +#define B3_REC_OP( op, Name, RET, ... ) B3_REC_RETWRITE_##RET( op, Name ) +#include "recording_ops.inl" +#undef B3_REC_OP +#undef B3_REC_RETWRITE_RET_NONE +#undef B3_REC_RETWRITE_RET_BODYID +#undef B3_REC_RETWRITE_RET_SHAPEID +#undef B3_REC_RETWRITE_RET_JOINTID +#undef B3_REC_RETWRITE + +// Geometry registry + +// Full 64-bit content hash, so distinct blobs of the same length get independent bits. A reseeded +// 32-bit djb2 cannot: djb2 is affine in its seed, so a same-length collision survives every seed and +// the high word would just track the low one. Word folded for speed, byte order normalized on +// big-endian to match b3Hash, then a splitmix64 finalizer so tiny inputs still spread across all bits. +// From Fowler/Noll/Vo FNV-1a salted by length, then the splitmix64 mix. +uint64_t b3Hash64Blob( const uint8_t* bytes, int n ) +{ + uint64_t h = 0xcbf29ce484222325ull ^ (uint64_t)(uint32_t)n; + const uint64_t prime = 0x100000001b3ull; + int i = 0; + + while ( i + 8 <= n ) + { + uint64_t word; + memcpy( &word, bytes + i, sizeof( word ) ); +#if defined( __BYTE_ORDER__ ) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + word = ( ( word & 0x00000000000000FFULL ) << 56 ) | ( ( word & 0x000000000000FF00ULL ) << 40 ) | + ( ( word & 0x0000000000FF0000ULL ) << 24 ) | ( ( word & 0x00000000FF000000ULL ) << 8 ) | + ( ( word & 0x000000FF00000000ULL ) >> 8 ) | ( ( word & 0x0000FF0000000000ULL ) >> 24 ) | + ( ( word & 0x00FF000000000000ULL ) >> 40 ) | ( ( word & 0xFF00000000000000ULL ) >> 56 ); +#endif + h = ( h ^ word ) * prime; + i += 8; + } + + while ( i < n ) + { + h = ( h ^ (uint64_t)bytes[i] ) * prime; + i += 1; + } + + h ^= h >> 30; + h *= 0xbf58476d1ce4e5b9ull; + h ^= h >> 27; + h *= 0x94d049bb133111ebull; + h ^= h >> 31; + return h; +} + +// Content hash to chain head, so dedup is near O(1). Colliding hashes share a head and are walked +// through b3GeometryEntry::hashNext, so the byteCount + memcmp check always finds an existing blob. +#define NAME b3GeometryHashMap +#define KEY_TY uint64_t +#define VAL_TY uint32_t +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +// Tag key to tag index, so interning a query tag is O(1) rather than a linear scan over the tag table. +#define NAME b3RecTagMap +#define KEY_TY uint64_t +#define VAL_TY uint32_t +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +// Append a fresh entry and splice it onto the front of its hash chain. The map value is the chain head. +static uint32_t b3RegistryPush( b3GeometryRegistry* reg, b3GeometryHashMap* map, b3GeometryHashMap_itr itr, bool hashPresent, + b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ) +{ + if ( reg->count >= reg->capacity ) + { + int newCap = reg->capacity < 8 ? 8 : reg->capacity * 2; + reg->entries = (b3GeometryEntry*)b3GrowAlloc( reg->entries, reg->capacity * (int)sizeof( b3GeometryEntry ), + newCap * (int)sizeof( b3GeometryEntry ) ); + reg->capacity = newCap; + } + + uint32_t id = (uint32_t)reg->count; + b3GeometryEntry* entry = reg->entries + reg->count; + entry->contentHash = contentHash; + entry->id = id; + entry->kind = kind; + entry->byteCount = byteCount; + entry->bytes = bytes; // take ownership + entry->hashNext = hashPresent ? (int)itr.data->val : B3_NULL_INDEX; + reg->count++; + + if ( hashPresent ) + { + itr.data->val = id; + } + else + { + b3GeometryHashMap_insert( map, contentHash, id ); + } + return id; +} + +static b3GeometryHashMap* b3RegistryMap( b3GeometryRegistry* reg ) +{ + if ( reg->dedupMap == NULL ) + { + b3GeometryHashMap* fresh = (b3GeometryHashMap*)b3Alloc( sizeof( b3GeometryHashMap ) ); + b3GeometryHashMap_init( fresh ); + reg->dedupMap = fresh; + } + return (b3GeometryHashMap*)reg->dedupMap; +} + +uint32_t b3InternGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ) +{ + b3GeometryHashMap* map = b3RegistryMap( reg ); + + b3GeometryHashMap_itr itr = b3GeometryHashMap_get( map, contentHash ); + bool hashPresent = b3GeometryHashMap_is_end( itr ) == false; + if ( hashPresent ) + { + // Walk every entry sharing this hash so a collision still finds the identical blob. + for ( int idx = (int)itr.data->val; idx != B3_NULL_INDEX; idx = reg->entries[idx].hashNext ) + { + b3GeometryEntry* e = reg->entries + idx; + if ( e->byteCount == byteCount && memcmp( e->bytes, bytes, (size_t)byteCount ) == 0 ) + { + // Duplicate: the caller transferred ownership; return existing id + b3Free( bytes, (size_t)byteCount ); + return e->id; + } + } + } + + return b3RegistryPush( reg, map, itr, hashPresent, kind, contentHash, bytes, byteCount ); +} + +uint32_t b3AppendGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ) +{ + b3GeometryHashMap* map = b3RegistryMap( reg ); + b3GeometryHashMap_itr itr = b3GeometryHashMap_get( map, contentHash ); + bool hashPresent = b3GeometryHashMap_is_end( itr ) == false; + return b3RegistryPush( reg, map, itr, hashPresent, kind, contentHash, bytes, byteCount ); +} + +void b3FreeRegistry( b3GeometryRegistry* reg ) +{ + for ( int i = 0; i < reg->count; ++i ) + { + b3Free( reg->entries[i].bytes, (size_t)reg->entries[i].byteCount ); + } + if ( reg->entries != NULL ) + { + b3Free( reg->entries, (size_t)( reg->capacity * (int)sizeof( b3GeometryEntry ) ) ); + } + if ( reg->dedupMap != NULL ) + { + b3GeometryHashMap_cleanup( (b3GeometryHashMap*)reg->dedupMap ); + b3Free( reg->dedupMap, sizeof( b3GeometryHashMap ) ); + } + reg->entries = NULL; + reg->count = 0; + reg->capacity = 0; + reg->dedupMap = NULL; +} + +uint64_t b3HashQueryTag( uint64_t id, const char* name ) +{ + uint64_t h = B3_SNAP_FNV_INIT; + for ( int i = 0; i < 8; ++i ) + { + h = ( h ^ ( ( id >> ( 8 * i ) ) & 0xFFu ) ) * B3_SNAP_FNV_PRIME; + } + if ( name != NULL ) + { + for ( int i = 0; name[i] != '\0'; ++i ) + { + h = ( h ^ (uint8_t)name[i] ) * B3_SNAP_FNV_PRIME; + } + } + // Never 0 so the key doubles as the tagged flag. + return h != 0 ? h : 1; +} + +static b3RecTagMap* b3RecTags( b3Recording* rec ) +{ + if ( rec->tagMap == NULL ) + { + b3RecTagMap* fresh = b3Alloc( sizeof( b3RecTagMap ) ); + b3RecTagMap_init( fresh ); + rec->tagMap = fresh; + } + return rec->tagMap; +} + +void b3RecInternTag( b3Recording* rec, uint64_t key, uint64_t id, const char* name ) +{ + b3RecTagMap* map = b3RecTags( rec ); + if ( b3RecTagMap_is_end( b3RecTagMap_get( map, key ) ) == false ) + { + return; // first id/name for a key wins + } + + if ( rec->tagCount == rec->tagCapacity ) + { + int newCap = rec->tagCapacity == 0 ? 8 : 2 * rec->tagCapacity; + rec->tags = b3GrowAlloc( rec->tags, rec->tagCapacity * (int)sizeof( b3RecTag ), newCap * (int)sizeof( b3RecTag ) ); + rec->tagCapacity = newCap; + } + + uint32_t index = (uint32_t)rec->tagCount; + b3RecTag* tag = &rec->tags[rec->tagCount++]; + tag->key = key; + tag->id = id; + int n = 0; + while ( name != NULL && name[n] != '\0' && n < B3_MAX_QUERY_NAME_LENGTH ) + { + tag->queryName[n] = name[n]; + n++; + } + tag->queryName[n] = '\0'; + b3RecTagMap_insert( map, key, index ); +} + +// Write the trailing registry block: u32 entryCount then per-entry { u8 kind, u32 byteCount, bytes }, +// followed by the query-tag table { u32 tagCount, per-tag uu64 id, STR name }. A reader built before +// the tag table stops after the geometry entries and ignores the trailing tag bytes. +void b3RecWriteRegistry( b3Recording* rec ) +{ + b3RecW_U32( &rec->buffer, (uint32_t)rec->registry.count ); + for ( int i = 0; i < rec->registry.count; ++i ) + { + b3GeometryEntry* e = rec->registry.entries + i; + b3RecW_U8( &rec->buffer, (uint8_t)e->kind ); + b3RecW_U32( &rec->buffer, (uint32_t)e->byteCount ); + b3RecBufAppend( &rec->buffer, e->bytes, e->byteCount ); + } + + b3RecW_U32( &rec->buffer, (uint32_t)rec->tagCount ); + for ( int i = 0; i < rec->tagCount; ++i ) + { + b3RecW_U64( &rec->buffer, rec->tags[i].key ); + b3RecW_U64( &rec->buffer, rec->tags[i].id ); + b3RecW_STR( &rec->buffer, rec->tags[i].queryName ); + } +} + +// Lifecycle + +b3Recording* b3CreateRecording( int byteCapacity ) +{ + b3Recording* rec = (b3Recording*)b3Alloc( sizeof( b3Recording ) ); + *rec = (b3Recording){ 0 }; + + int initCap = byteCapacity > 0 ? byteCapacity : 65536; + rec->buffer.data = (uint8_t*)b3Alloc( (size_t)initCap ); + rec->buffer.capacity = initCap; + rec->buffer.size = 0; + rec->lock = b3CreateMutex(); + return rec; +} + +void b3DestroyRecording( b3Recording* recording ) +{ + if ( recording == NULL ) + { + return; + } + + b3RecBufFree( &recording->buffer ); + b3FreeRegistry( &recording->registry ); + if ( recording->tags != NULL ) + { + b3Free( recording->tags, (size_t)recording->tagCapacity * sizeof( b3RecTag ) ); + } + if ( recording->tagMap != NULL ) + { + b3RecTagMap_cleanup( (b3RecTagMap*)recording->tagMap ); + b3Free( recording->tagMap, sizeof( b3RecTagMap ) ); + } + b3DestroyMutex( recording->lock ); + b3Free( recording, sizeof( b3Recording ) ); +} + +const uint8_t* b3Recording_GetData( const b3Recording* recording ) +{ + return recording->buffer.data; +} + +int b3Recording_GetSize( const b3Recording* recording ) +{ + return recording->buffer.size; +} + +void b3RecAccumulateBounds( b3Recording* rec, b3AABB bounds ) +{ + rec->accumulatedBounds = rec->haveBounds ? b3AABB_Union( rec->accumulatedBounds, bounds ) : bounds; + rec->haveBounds = true; +} + +void b3StartRecordingIntoBuffer( b3World* world, b3Recording* recording ) +{ + // Reset so a recording handle can be reused for a fresh session + recording->buffer.size = 0; + recording->recordStart = 0; + recording->haveBounds = false; + b3FreeRegistry( &recording->registry ); + if ( recording->tags != NULL ) + { + b3Free( recording->tags, (size_t)recording->tagCapacity * sizeof( b3RecTag ) ); + recording->tags = NULL; + } + if ( recording->tagMap != NULL ) + { + b3RecTagMap_cleanup( (b3RecTagMap*)recording->tagMap ); + b3Free( recording->tagMap, sizeof( b3RecTagMap ) ); + recording->tagMap = NULL; + } + recording->tagCount = 0; + recording->tagCapacity = 0; + + b3RecHeader hdr = { 0 }; + hdr.magic = B3_REC_MAGIC; + hdr.versionMajor = B3_REC_VERSION_MAJOR; + hdr.versionMinor = B3_REC_VERSION_MINOR; + hdr.pointerWidth = (uint8_t)sizeof( void* ); + hdr.bigEndian = 0; + hdr.validationEnabled = B3_ENABLE_VALIDATION ? 1u : 0u; + hdr.lengthScale = b3GetLengthUnitsPerMeter(); + hdr.registryOffset = 0; // backpatched in b3StopRecordingInternal + hdr.registryByteCount = 0; + + world->recording = recording; + + // Every recording is snapshot-seeded. The seed blob follows the header so replay restores in + // place and the world id stays stable across a restart or backward scrub. An empty world still + // serializes a valid blob, so there is no from-creation special case. + b3RecBuffer snapBuf = { 0 }; + b3SerializeWorld( world, &snapBuf, recording ); + hdr.snapshotSize = (uint64_t)snapBuf.size; + + b3RecBufAppend( &recording->buffer, &hdr, (int)sizeof( hdr ) ); + b3RecBufAppend( &recording->buffer, snapBuf.data, snapBuf.size ); + b3RecBufFree( &snapBuf ); + + // Anchor the recording with the current world state hash so replay can assert + // determinism from the very first step. + b3WorldId worldId = { (uint16_t)( world->worldId + 1 ), world->generation }; + b3RecArgs_StateHash stateHash = { worldId, b3HashWorldState( world ) }; + b3RecWrite_StateHash( recording, &stateHash ); +} + +void b3StopRecordingInternal( b3World* world ) +{ + if ( world->recording == NULL ) + { + return; + } + + b3Recording* rec = world->recording; + world->recording = NULL; + + // Write accumulated bounds so a viewer can frame the whole recorded motion + b3RecArgs_RecordingBounds rb = { 0 }; + if ( rec->haveBounds ) + { + rb.bounds = rec->accumulatedBounds; + } + b3RecWrite_RecordingBounds( rec, &rb ); + + // End-of-stream marker; the buffer is now self-contained + b3WorldId wid = { (uint16_t)( world->worldId + 1 ), world->generation }; + b3RecArgs_DestroyWorld a = { wid }; + b3RecWrite_DestroyWorld( rec, &a ); + + // Write the trailing registry block + int registryOffset = rec->buffer.size; + b3RecWriteRegistry( rec ); + int registryByteCount = rec->buffer.size - registryOffset; + + // Backpatch registryOffset and registryByteCount into the header + uint8_t* hdrBytes = rec->buffer.data; + uint64_t regOff = (uint64_t)registryOffset; + uint64_t regSz = (uint64_t)registryByteCount; + // Little-endian backpatch in place; offsetof keeps this correct if the header layout shifts + uint8_t* pOff = hdrBytes + offsetof( b3RecHeader, registryOffset ); + uint8_t* pSz = hdrBytes + offsetof( b3RecHeader, registryByteCount ); + for ( int i = 0; i < 8; ++i ) + { + pOff[i] = (uint8_t)( regOff >> ( 8 * i ) ); + pSz[i] = (uint8_t)( regSz >> ( 8 * i ) ); + } +} + +// Convenience file I/O + +bool b3SaveRecordingToFile( const b3Recording* recording, const char* path ) +{ + if ( recording == NULL || path == NULL ) + { + return false; + } + + FILE* f = fopen( path, "wb" ); + if ( f == NULL ) + { + return false; + } + + size_t written = fwrite( recording->buffer.data, 1, (size_t)recording->buffer.size, f ); + fclose( f ); + return (int)written == recording->buffer.size; +} + +b3Recording* b3LoadRecordingFromFile( const char* path ) +{ + if ( path == NULL ) + { + return NULL; + } + + FILE* f = fopen( path, "rb" ); + if ( f == NULL ) + { + return NULL; + } + + if ( fseek( f, 0, SEEK_END ) != 0 ) + { + fclose( f ); + return NULL; + } + + long fileSize = ftell( f ); + // Anything smaller than the fixed header can't be a recording + if ( fileSize < (long)sizeof( b3RecHeader ) || fileSize > INT_MAX ) + { + fclose( f ); + return NULL; + } + fseek( f, 0, SEEK_SET ); + + b3Recording* rec = b3CreateRecording( (int)fileSize ); + size_t readSize = fread( rec->buffer.data, 1, (size_t)fileSize, f ); + fclose( f ); + + if ( (long)readSize != fileSize ) + { + b3DestroyRecording( rec ); + return NULL; + } + + // Validate magic so a wrong file fails at load rather than deep in the player + b3RecHeader hdr; + memcpy( &hdr, rec->buffer.data, sizeof( hdr ) ); + if ( hdr.magic != B3_REC_MAGIC ) + { + b3DestroyRecording( rec ); + return NULL; + } + + rec->buffer.size = (int)fileSize; + return rec; +} + +// Geometry interning helpers + +uint32_t b3RecInternHull( b3Recording* rec, const b3HullData* hull ) +{ + int byteCount = hull->byteCount; + uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + memcpy( bytes, hull, (size_t)byteCount ); + uint64_t h = b3Hash64Blob( bytes, byteCount ); + return b3InternGeometry( &rec->registry, b3_geometryHull, h, bytes, byteCount ); +} + +uint32_t b3RecInternMesh( b3Recording* rec, const b3MeshData* mesh ) +{ + int byteCount = mesh->byteCount; + uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + memcpy( bytes, mesh, (size_t)byteCount ); + uint64_t h = b3Hash64Blob( bytes, byteCount ); + return b3InternGeometry( &rec->registry, b3_geometryMesh, h, bytes, byteCount ); +} + +uint32_t b3RecInternHeightField( b3Recording* rec, const b3HeightFieldData* hf ) +{ + int byteCount = hf->byteCount; + uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + memcpy( bytes, hf, (size_t)byteCount ); + uint64_t h = b3Hash64Blob( bytes, byteCount ); + return b3InternGeometry( &rec->registry, b3_geometryHeightField, h, bytes, byteCount ); +} + +uint32_t b3RecInternCompound( b3Recording* rec, const b3CompoundData* compound ) +{ + int byteCount = compound->byteCount; + uint8_t* bytes = (uint8_t*)b3Alloc( (size_t)byteCount ); + memcpy( bytes, compound, (size_t)byteCount ); + // Null the tree node pointer in the copy so the canonical bytes are pointer-free. + // b3ConvertBytesToCompound fixes it back on load via nodeOffset. + ( (b3CompoundData*)bytes )->tree.nodes = NULL; + uint64_t h = b3Hash64Blob( bytes, byteCount ); + return b3InternGeometry( &rec->registry, b3_geometryCompound, h, bytes, byteCount ); +} + +uint64_t b3HashWorldState( b3World* world ) +{ + uint64_t hash = B3_SNAP_FNV_INIT; + const uint64_t prime = B3_SNAP_FNV_PRIME; + + int bodyCount = world->bodies.count; + for ( int i = 0; i < bodyCount; ++i ) + { + b3Body* body = world->bodies.data + i; + if ( body->id != i ) + { + // Free or never-used slot + continue; + } + + b3BodySim* sim = b3GetBodySim( world, body ); + + uint32_t bits; + +#define B3_HASH_FLOAT( f ) \ + memcpy( &bits, &( f ), 4 ); \ + hash = ( hash ^ (uint64_t)bits ) * prime; + + hash = b3FnvMixPosition( hash, sim->transform.p ); + B3_HASH_FLOAT( sim->transform.q.v.x ) + B3_HASH_FLOAT( sim->transform.q.v.y ) + B3_HASH_FLOAT( sim->transform.q.v.z ) + B3_HASH_FLOAT( sim->transform.q.s ) + + b3BodyState* state = b3GetBodyState( world, body ); + if ( state != NULL ) + { + B3_HASH_FLOAT( state->linearVelocity.x ) + B3_HASH_FLOAT( state->linearVelocity.y ) + B3_HASH_FLOAT( state->linearVelocity.z ) + B3_HASH_FLOAT( state->angularVelocity.x ) + B3_HASH_FLOAT( state->angularVelocity.y ) + B3_HASH_FLOAT( state->angularVelocity.z ) + } + +#undef B3_HASH_FLOAT + } + + return hash; +} diff --git a/vendor/box3d/src/src/recording.h b/vendor/box3d/src/src/recording.h new file mode 100644 index 000000000..3ba2fd3bb --- /dev/null +++ b/vendor/box3d/src/src/recording.h @@ -0,0 +1,386 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core.h" + +#include "box3d/id.h" +#include "box3d/math_functions.h" +#include "box3d/types.h" + +#include +#include +#include +#include + +// FNV-1a 64-bit constants +#define B3_SNAP_FNV_INIT 14695981039346656037ull +#define B3_SNAP_FNV_PRIME 1099511628211ull + +// Mix a world position at full width so the determinism gate validates past float precision +// when the body is far from the origin. +static inline uint64_t b3FnvMixPosition( uint64_t hash, b3Pos p ) +{ +#if defined( BOX3D_DOUBLE_PRECISION ) + uint64_t bx, by, bz; + memcpy( &bx, &p.x, 8 ); + memcpy( &by, &p.y, 8 ); + memcpy( &bz, &p.z, 8 ); +#else + uint32_t fx, fy, fz; + memcpy( &fx, &p.x, 4 ); + memcpy( &fy, &p.y, 4 ); + memcpy( &fz, &p.z, 4 ); + uint64_t bx = fx, by = fy, bz = fz; +#endif + hash = ( hash ^ bx ) * B3_SNAP_FNV_PRIME; + hash = ( hash ^ by ) * B3_SNAP_FNV_PRIME; + hash = ( hash ^ bz ) * B3_SNAP_FNV_PRIME; + return hash; +} + +typedef struct b3World b3World; + +// Magic 'B3RC' in little-endian: bytes B(0x42) 3(0x33) R(0x52) C(0x43) +#define B3_REC_MAGIC 0x43523342u + +// Major recording version is bumped when writers change. +// Major version 4 added b3ShapeDef::enableSpeculativeContact +#define B3_REC_VERSION_MAJOR 4 + +// Minor tracks op-stream additions that keep the 48 byte header shape. +// Minor version 3 added name cache. +#define B3_REC_VERSION_MINOR 3 + +// File header, fixed 48 bytes, little-endian. Contains the registry locator so the player +// can load geometry before replaying any ops. +typedef struct b3RecHeader +{ + uint32_t magic; // 'B3RC' = 0x43523342 + uint16_t versionMajor; // B3_REC_VERSION_MAJOR + uint16_t versionMinor; // B3_REC_VERSION_MINOR + uint8_t pointerWidth; // sizeof(void*), gates POD-def struct layout + uint8_t bigEndian; // 0 on all supported targets + uint8_t validationEnabled; // 1 if built with BOX3D_VALIDATE, diagnostic only + uint8_t reserved; + float lengthScale; // b3GetLengthUnitsPerMeter() + uint32_t reserved2; + uint32_t reserved3; // explicit pad so the 64-bit fields align with no implicit gap + uint64_t snapshotSize; // bytes of snapshot blob after the header (0 in Phase 1) + uint64_t registryOffset; // absolute offset to trailing registry block, backpatched at stop + uint64_t registryByteCount; // size of the registry block +} b3RecHeader; + +_Static_assert( sizeof( b3RecHeader ) == 48, "recording header must be 48 bytes" ); + +// Growable append-only byte buffer. Doubles on demand. countOnly mode tallies size without +// allocating, used to size a buffer cheaply before a second filling pass. +typedef struct b3RecBuffer +{ + uint8_t* data; + int capacity; + int size; + bool countOnly; +} b3RecBuffer; + +// Geometry kinds for the trailing registry section +typedef enum b3GeometryKind +{ + b3_geometryHull, + b3_geometryMesh, + b3_geometryHeightField, + b3_geometryCompound, +} b3GeometryKind; + +// One entry per unique geometry blob. id == index in the entries array. +// hashNext chains entries that share a content hash so dedup stays exact under a hash collision, +// which the keyframe registry depends on to never grow during capture. B3_NULL_INDEX ends the chain. +typedef struct b3GeometryEntry +{ + uint64_t contentHash; + uint32_t id; + b3GeometryKind kind; + int byteCount; + uint8_t* bytes; + int hashNext; +} b3GeometryEntry; + +// Growable array of geometry entries. Ids are array indices, so the array is serialized in order. +// dedupMap maps content hash to entry id for O(1) dedup; it is opaque here and owned by recording.c. +typedef struct b3GeometryRegistry +{ + b3GeometryEntry* entries; + int count; + int capacity; + void* dedupMap; +} b3GeometryRegistry; + +// Limit the maximum query name length to make recording simpler. Query names longer than this +// probably indicate a bug in user code. +#define B3_MAX_QUERY_NAME_LENGTH 64 + +// Query tag from b3QueryFilter. +// Stored once per key in the trailing block so a tagged query carries only the 8 byte key on the wire. +// Shared by the recorder (accumulate) and the player (load). +typedef struct b3RecTag +{ + // hash of (id, queryName) + uint64_t key; + uint64_t id; + char queryName[B3_MAX_QUERY_NAME_LENGTH + 1]; +} b3RecTag; + +// User-owned recording buffer. The world appends into it while active; the host saves and +// destroys it. Opaque across the public API. +typedef struct b3Recording +{ + b3RecBuffer buffer; + int recordStart; // offset of the 3-byte size field for u24 backpatch + b3Mutex* lock; // serializes record writes from concurrent threads + b3GeometryRegistry registry; + + // Interned query tags accumulated during capture, written to the tail of the registry block at stop. + // tagMap maps a tag key to its index for O(1) dedup. + b3RecTag* tags; + int tagCount; + int tagCapacity; + void* tagMap; + + // Union of world bounds over every recorded step, written at stop. + b3AABB accumulatedBounds; + bool haveBounds; +} b3Recording; + +// C type aliases per TAG, used in the X-macro codegen arg structs +typedef bool b3RecCType_BOOL; +typedef int32_t b3RecCType_I32; +typedef uint8_t b3RecCType_U8; +typedef uint16_t b3RecCType_U16; +typedef uint32_t b3RecCType_U32; +typedef uint64_t b3RecCType_U64; +typedef float b3RecCType_F32; +typedef double b3RecCType_F64; +typedef b3Vec3 b3RecCType_VEC3; +typedef b3Quat b3RecCType_QUAT; +typedef b3Transform b3RecCType_TRANSFORM; +typedef b3Pos b3RecCType_POSITION; +typedef b3WorldTransform b3RecCType_WORLDXF; +typedef b3Matrix3 b3RecCType_MATRIX3; +typedef b3AABB b3RecCType_AABB; +typedef b3Sphere b3RecCType_SPHERE; +typedef b3Capsule b3RecCType_CAPSULE; +typedef b3QueryFilter b3RecCType_QUERYFILTER; +typedef b3ShapeProxy b3RecCType_SHAPEPROXY; +// Geometry reference: a plain u32 id into the trailing registry +typedef uint32_t b3RecCType_GEOMID; +typedef b3Filter b3RecCType_FILTER; +typedef b3SurfaceMaterial b3RecCType_MATERIAL; +typedef b3MassData b3RecCType_MASSDATA; +typedef b3MotionLocks b3RecCType_LOCKS; +typedef const char* b3RecCType_STR; +typedef b3WorldId b3RecCType_WORLDID; +typedef b3BodyId b3RecCType_BODYID; +typedef b3ShapeId b3RecCType_SHAPEID; +typedef b3JointId b3RecCType_JOINTID; +typedef b3BodyDef b3RecCType_BODYDEF; +typedef b3ShapeDef b3RecCType_SHAPEDEF; +typedef b3ExplosionDef b3RecCType_EXPLOSIONDEF; +typedef b3ParallelJointDef b3RecCType_PARALLELJOINTDEF; +typedef b3DistanceJointDef b3RecCType_DISTANCEJOINTDEF; +typedef b3FilterJointDef b3RecCType_FILTERJOINTDEF; +typedef b3MotorJointDef b3RecCType_MOTORJOINTDEF; +typedef b3PrismaticJointDef b3RecCType_PRISMATICJOINTDEF; +typedef b3RevoluteJointDef b3RecCType_REVOLUTEJOINTDEF; +typedef b3SphericalJointDef b3RecCType_SPHERICALJOINTDEF; +typedef b3WeldJointDef b3RecCType_WELDJOINTDEF; +typedef b3WheelJointDef b3RecCType_WHEELJOINTDEF; + +// Codegen pass 1a: arg structs. Generated here so call sites (body.c, shape.c, etc.) can see them. +#define ARG( TAG, field ) b3RecCType_##TAG field; +#define B3_REC_OP( op, Name, RET, ... ) \ + typedef struct \ + { \ + __VA_ARGS__ \ + } b3RecArgs_##Name; +#include "recording_ops.inl" +#undef B3_REC_OP +#undef ARG + +// Opcode constants generated from the manifest, so call sites name an op instead of a raw byte and +// can't drift from the manifest if an op is renumbered. +enum +{ +#define B3_REC_OP( op, Name, RET, ... ) b3_recOp##Name = ( op ), +#include "recording_ops.inl" +#undef B3_REC_OP +}; + +// Low-level buffer helpers +void b3RecBufAppend( b3RecBuffer* buf, const void* data, int size ); +void b3RecBufFree( b3RecBuffer* buf ); + +// Write primitives +void b3RecW_U8( b3RecBuffer* buf, uint8_t v ); +void b3RecW_U16( b3RecBuffer* buf, uint16_t v ); +void b3RecW_U32( b3RecBuffer* buf, uint32_t v ); +void b3RecW_U64( b3RecBuffer* buf, uint64_t v ); +void b3RecW_I32( b3RecBuffer* buf, int32_t v ); +void b3RecW_F32( b3RecBuffer* buf, float v ); +void b3RecW_F64( b3RecBuffer* buf, double v ); +void b3RecW_BOOL( b3RecBuffer* buf, bool v ); +void b3RecW_VEC3( b3RecBuffer* buf, b3Vec3 v ); +void b3RecW_QUAT( b3RecBuffer* buf, b3Quat v ); +void b3RecW_TRANSFORM( b3RecBuffer* buf, b3Transform v ); +// World position: doubles in large-world mode, floats otherwise (wire-identical to VEC3 in float build) +void b3RecW_POSITION( b3RecBuffer* buf, b3Pos v ); +void b3RecW_WORLDXF( b3RecBuffer* buf, b3WorldTransform v ); +void b3RecW_MATRIX3( b3RecBuffer* buf, b3Matrix3 v ); +void b3RecW_AABB( b3RecBuffer* buf, b3AABB v ); +void b3RecW_QUERYFILTER( b3RecBuffer* buf, b3QueryFilter v ); +void b3RecW_SHAPEPROXY( b3RecBuffer* buf, b3ShapeProxy v ); +void b3RecW_TREESTATS( b3RecBuffer* buf, b3TreeStats v ); +void b3RecW_RAYRESULT( b3RecBuffer* buf, b3RayResult v ); +void b3RecW_PLANERESULT( b3RecBuffer* buf, b3PlaneResult v ); +void b3RecW_WORLDID( b3RecBuffer* buf, b3WorldId v ); +void b3RecW_BODYID( b3RecBuffer* buf, b3BodyId v ); +void b3RecW_SHAPEID( b3RecBuffer* buf, b3ShapeId v ); +void b3RecW_JOINTID( b3RecBuffer* buf, b3JointId v ); +void b3RecW_SPHERE( b3RecBuffer* buf, b3Sphere v ); +void b3RecW_CAPSULE( b3RecBuffer* buf, b3Capsule v ); +void b3RecW_GEOMID( b3RecBuffer* buf, uint32_t v ); +void b3RecW_FILTER( b3RecBuffer* buf, b3Filter v ); +void b3RecW_MATERIAL( b3RecBuffer* buf, b3SurfaceMaterial v ); +void b3RecW_MASSDATA( b3RecBuffer* buf, b3MassData v ); +void b3RecW_LOCKS( b3RecBuffer* buf, b3MotionLocks v ); +void b3RecW_EXPLOSIONDEF( b3RecBuffer* buf, b3ExplosionDef v ); +void b3RecW_BODYDEF( b3RecBuffer* buf, b3BodyDef v ); +void b3RecW_SHAPEDEF( b3RecBuffer* buf, b3ShapeDef v ); +void b3RecW_PARALLELJOINTDEF( b3RecBuffer* buf, b3ParallelJointDef v ); +void b3RecW_DISTANCEJOINTDEF( b3RecBuffer* buf, b3DistanceJointDef v ); +void b3RecW_FILTERJOINTDEF( b3RecBuffer* buf, b3FilterJointDef v ); +void b3RecW_MOTORJOINTDEF( b3RecBuffer* buf, b3MotorJointDef v ); +void b3RecW_PRISMATICJOINTDEF( b3RecBuffer* buf, b3PrismaticJointDef v ); +void b3RecW_REVOLUTEJOINTDEF( b3RecBuffer* buf, b3RevoluteJointDef v ); +void b3RecW_SPHERICALJOINTDEF( b3RecBuffer* buf, b3SphericalJointDef v ); +void b3RecW_WELDJOINTDEF( b3RecBuffer* buf, b3WeldJointDef v ); +void b3RecW_WHEELJOINTDEF( b3RecBuffer* buf, b3WheelJointDef v ); + +// Record framing +void b3RecBeginRecord( b3Recording* rec, uint8_t opcode ); +void b3RecEndRecord( b3Recording* rec ); + +// Per-op arg writers (no framing) and full writers (framing + args), generated from the manifest. +#define B3_REC_OP( op, Name, RET, ... ) \ + void b3RecWriteArgs_##Name( b3Recording* rec, const b3RecArgs_##Name* a ); \ + void b3RecWrite_##Name( b3Recording* rec, const b3RecArgs_##Name* a ); +#include "recording_ops.inl" +#undef B3_REC_OP + +// Create ops: declare writers that also append the returned id inside the record. +#define B3_REC_RETDECL_RET_NONE( Name ) +#define B3_REC_RETDECL_RET_BODYID( Name ) void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, b3BodyId id ); +#define B3_REC_RETDECL_RET_SHAPEID( Name ) void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, b3ShapeId id ); +#define B3_REC_RETDECL_RET_JOINTID( Name ) void b3RecWriteRet_##Name( b3Recording* rec, const b3RecArgs_##Name* a, b3JointId id ); +#define B3_REC_OP( op, Name, RET, ... ) B3_REC_RETDECL_##RET( Name ) +#include "recording_ops.inl" +#undef B3_REC_OP +#undef B3_REC_RETDECL_RET_NONE +#undef B3_REC_RETDECL_RET_BODYID +#undef B3_REC_RETDECL_RET_SHAPEID +#undef B3_REC_RETDECL_RET_JOINTID + +// Record a void op. The branch is free when recording is off. +#define B3_REC( world, Name, ... ) \ + do \ + { \ + if ( ( world )->recording != NULL ) \ + { \ + b3RecArgs_##Name recArgs = { __VA_ARGS__ }; \ + b3RecWrite_##Name( ( world )->recording, &recArgs ); \ + } \ + } \ + while ( 0 ) + +// Record a create op and its returned id in one framed record. Place after the real create call. +#define B3_REC_CREATE( world, Name, id, ... ) \ + do \ + { \ + if ( ( world )->recording != NULL ) \ + { \ + b3RecArgs_##Name recCreateArgs = { __VA_ARGS__ }; \ + b3RecWriteRet_##Name( ( world )->recording, &recCreateArgs, id ); \ + } \ + } \ + while ( 0 ) + +// Patch helpers for the query hit-count backfill +int b3RecReserveU32( b3RecBuffer* buf ); +void b3RecPatchU32( b3RecBuffer* buf, int offset, uint32_t v ); + +// Commit a finished query record under the lock. The payload buffer stays owned by the caller. +void b3RecCommitRecord( b3Recording* rec, uint8_t opcode, const uint8_t* payload, int payloadSize ); + +// Per-query writer context: holds the user fcn+ctx, the local payload buffer, and the hit counter +typedef struct b3RecQueryWriter +{ + union + { + b3OverlapResultFcn* overlapFcn; + b3CastResultFcn* castFcn; + b3PlaneResultFcn* planeFcn; + b3MoverFilterFcn* moverFilterFcn; + } userFcn; + void* userContext; + b3RecBuffer buf; // per-call local payload, heap-backed + int countOffset; // offset of the reserved u32 hit-count slot + uint32_t hitCount; + uint64_t tagId; // caller query id, 0 = untagged. Emitted as a QueryTag before the record. + const char* tagName; // caller query name, interned by id. NULL = none. +} b3RecQueryWriter; + +void b3RecQueryBegin( b3RecQueryWriter* w, void* context, uint64_t tagId, const char* tagName ); +void b3RecQueryCommit( b3Recording* rec, uint8_t opcode, b3RecQueryWriter* w ); + +// Recording trampolines: replace the user fcn so hits are captured before dispatch. The overlap +// trampoline doubles for the mover filter, which has the same bool(shapeId, ctx) shape. +bool b3RecOverlapTrampoline( b3ShapeId id, void* ctx ); +float b3RecCastTrampoline( b3ShapeId id, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, int triangleIndex, + int childIndex, void* ctx ); +bool b3RecPlaneTrampoline( b3ShapeId id, const b3PlaneResult* planes, int planeCount, void* ctx ); + +// Geometry registry +uint32_t b3InternGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ); +// Append an entry unconditionally and return its id, which equals its array index. Unlike +// b3InternGeometry it never deduplicates, so the keyframe seed can mirror slots 1:1 even when an +// already-recorded file carries byte-identical duplicate slots (a hash collision wrote them apart). +uint32_t b3AppendGeometry( b3GeometryRegistry* reg, b3GeometryKind kind, uint64_t contentHash, uint8_t* bytes, int byteCount ); +void b3FreeRegistry( b3GeometryRegistry* reg ); +void b3RecWriteRegistry( b3Recording* rec ); + +// Hash a query (id, name) pair into the stable key the viewer tracks the query by. Never returns 0, +// so the key doubles as a tagged/untagged flag. +uint64_t b3HashQueryTag( uint64_t id, const char* name ); + +// Record a key->(id, name) mapping once, deduped by key (a repeated key keeps its first id/name). +void b3RecInternTag( b3Recording* rec, uint64_t key, uint64_t id, const char* name ); + +// Intern each large geometry kind and return a stable u32 id for use in create ops. +// Caller does NOT free bytes; b3InternGeometry takes ownership (frees on duplicate). +uint32_t b3RecInternHull( b3Recording* rec, const b3HullData* hull ); +uint32_t b3RecInternMesh( b3Recording* rec, const b3MeshData* mesh ); +uint32_t b3RecInternHeightField( b3Recording* rec, const b3HeightFieldData* hf ); +uint32_t b3RecInternCompound( b3Recording* rec, const b3CompoundData* compound ); + +uint64_t b3Hash64Blob( const uint8_t* bytes, int n ); + +// Lifecycle engine-side hooks +void b3StartRecordingIntoBuffer( b3World* world, b3Recording* recording ); +void b3StopRecordingInternal( b3World* world ); + +// Fold one step's world bounds into the running union. +void b3RecAccumulateBounds( b3Recording* rec, b3AABB bounds ); + +// Deterministic hash over all body transforms and velocities. +// Called by both recorder and replayer to verify simulation reproduces exactly. +uint64_t b3HashWorldState( b3World* world ); diff --git a/vendor/box3d/src/src/recording_ops.inl b/vendor/box3d/src/src/recording_ops.inl new file mode 100644 index 000000000..b8a3625da --- /dev/null +++ b/vendor/box3d/src/src/recording_ops.inl @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// X-macro manifest for the recording system. +// Include with B3_REC_OP and ARG defined. No commas between ARG tokens. +// +// B3_REC_OP( opcode, Name, RET, ARGS ) +// RET in { RET_NONE, RET_BODYID, RET_SHAPEID, RET_JOINTID } +// ARGS = zero or more ARG( TAG, fieldName ) tokens, NO commas between them +// +// Opcode ranges: +// 0x00-0x0F world lifecycle and config +// 0x10-0x1F body create/destroy +// 0x20-0x3F body mutators +// 0x40-0x4F shape create/destroy +// 0x50-0x6F shape mutators +// 0x80 step +// 0x90-0xE7 joints (create, generic, per-type) +// 0xE8-0xEF spatial queries +// 0xF0-0xFF markers + +// Recordings are seeded with a world snapshot, not a CreateWorld op. DestroyWorld stays as the +// end-of-session marker the viewer reads. +B3_REC_OP( 0x01, DestroyWorld, RET_NONE, ARG( WORLDID, world ) ) +B3_REC_OP( 0x80, Step, RET_NONE, ARG( WORLDID, world ) ARG( F32, dt ) ARG( I32, subStepCount ) ) + +// World config. The world arg is informational; replay always targets its own world. +B3_REC_OP( 0x02, WorldEnableSleeping, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x03, WorldEnableContinuous, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x04, WorldSetRestitutionThreshold, RET_NONE, ARG( WORLDID, world ) ARG( F32, value ) ) +B3_REC_OP( 0x05, WorldSetHitEventThreshold, RET_NONE, ARG( WORLDID, world ) ARG( F32, value ) ) +B3_REC_OP( 0x06, WorldSetGravity, RET_NONE, ARG( WORLDID, world ) ARG( VEC3, gravity ) ) +B3_REC_OP( 0x07, WorldExplode, RET_NONE, ARG( WORLDID, world ) ARG( EXPLOSIONDEF, def ) ) +B3_REC_OP( 0x08, WorldSetContactTuning, RET_NONE, + ARG( WORLDID, world ) ARG( F32, hertz ) ARG( F32, dampingRatio ) ARG( F32, contactSpeed ) ) +B3_REC_OP( 0x09, WorldSetContactRecycleDistance, RET_NONE, ARG( WORLDID, world ) ARG( F32, recycleDistance ) ) +B3_REC_OP( 0x0A, WorldSetMaximumLinearSpeed, RET_NONE, ARG( WORLDID, world ) ARG( F32, maximumLinearSpeed ) ) +B3_REC_OP( 0x0B, WorldEnableWarmStarting, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x0C, WorldRebuildStaticTree, RET_NONE, ARG( WORLDID, world ) ) +B3_REC_OP( 0x0D, WorldEnableSpeculative, RET_NONE, ARG( WORLDID, world ) ARG( BOOL, flag ) ) + +// Body +B3_REC_OP( 0x10, CreateBody, RET_BODYID, ARG( WORLDID, world ) ARG( BODYDEF, def ) ) +B3_REC_OP( 0x11, DestroyBody, RET_NONE, ARG( BODYID, body ) ) +B3_REC_OP( 0x20, BodySetTransform, RET_NONE, ARG( BODYID, body ) ARG( POSITION, position ) ARG( QUAT, rotation ) ) +B3_REC_OP( 0x21, BodySetLinearVelocity, RET_NONE, ARG( BODYID, body ) ARG( VEC3, v ) ) +B3_REC_OP( 0x22, BodySetType, RET_NONE, ARG( BODYID, body ) ARG( I32, type ) ) +B3_REC_OP( 0x23, BodySetName, RET_NONE, ARG( BODYID, body ) ARG( STR, name ) ) +B3_REC_OP( 0x24, BodySetAngularVelocity, RET_NONE, ARG( BODYID, body ) ARG( VEC3, w ) ) +B3_REC_OP( 0x25, BodySetTargetTransform, RET_NONE, ARG( BODYID, body ) ARG( WORLDXF, target ) ARG( F32, timeStep ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x26, BodyApplyForce, RET_NONE, ARG( BODYID, body ) ARG( VEC3, force ) ARG( POSITION, point ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x27, BodyApplyForceToCenter, RET_NONE, ARG( BODYID, body ) ARG( VEC3, force ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x28, BodyApplyTorque, RET_NONE, ARG( BODYID, body ) ARG( VEC3, torque ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x29, BodyApplyLinearImpulse, RET_NONE, ARG( BODYID, body ) ARG( VEC3, impulse ) ARG( POSITION, point ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x2A, BodyApplyLinearImpulseToCenter, RET_NONE, ARG( BODYID, body ) ARG( VEC3, impulse ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x2B, BodyApplyAngularImpulse, RET_NONE, ARG( BODYID, body ) ARG( VEC3, impulse ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x2C, BodySetMassData, RET_NONE, ARG( BODYID, body ) ARG( MASSDATA, massData ) ) +B3_REC_OP( 0x2D, BodyApplyMassFromShapes, RET_NONE, ARG( BODYID, body ) ) +B3_REC_OP( 0x2E, BodySetLinearDamping, RET_NONE, ARG( BODYID, body ) ARG( F32, damping ) ) +B3_REC_OP( 0x2F, BodySetAngularDamping, RET_NONE, ARG( BODYID, body ) ARG( F32, damping ) ) +B3_REC_OP( 0x30, BodySetGravityScale, RET_NONE, ARG( BODYID, body ) ARG( F32, scale ) ) +B3_REC_OP( 0x31, BodySetAwake, RET_NONE, ARG( BODYID, body ) ARG( BOOL, awake ) ) +B3_REC_OP( 0x32, BodyEnableSleep, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x33, BodySetSleepThreshold, RET_NONE, ARG( BODYID, body ) ARG( F32, threshold ) ) +B3_REC_OP( 0x34, BodyDisable, RET_NONE, ARG( BODYID, body ) ) +B3_REC_OP( 0x35, BodyEnable, RET_NONE, ARG( BODYID, body ) ) +B3_REC_OP( 0x36, BodySetMotionLocks, RET_NONE, ARG( BODYID, body ) ARG( LOCKS, locks ) ) +B3_REC_OP( 0x37, BodySetBullet, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x38, BodyEnableContactRecycling, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x39, BodyEnableHitEvents, RET_NONE, ARG( BODYID, body ) ARG( BOOL, flag ) ) + +// Shape create/destroy +B3_REC_OP( 0x40, CreateSphereShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( SPHERE, sphere ) ) +B3_REC_OP( 0x41, CreateCapsuleShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( CAPSULE, capsule ) ) +B3_REC_OP( 0x42, CreateHullShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) ) +B3_REC_OP( 0x43, CreateMeshShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) ARG( VEC3, scale ) ) +B3_REC_OP( 0x44, CreateHeightFieldShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) ) +B3_REC_OP( 0x45, CreateCompoundShape, RET_SHAPEID, ARG( BODYID, body ) ARG( SHAPEDEF, def ) ARG( GEOMID, geometryId ) ) +B3_REC_OP( 0x46, DestroyShape, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, updateBodyMass ) ) + +// Shape mutators +B3_REC_OP( 0x50, ShapeSetDensity, RET_NONE, ARG( SHAPEID, shape ) ARG( F32, density ) ARG( BOOL, updateBodyMass ) ) +B3_REC_OP( 0x51, ShapeSetFriction, RET_NONE, ARG( SHAPEID, shape ) ARG( F32, friction ) ) +B3_REC_OP( 0x52, ShapeSetRestitution, RET_NONE, ARG( SHAPEID, shape ) ARG( F32, restitution ) ) +B3_REC_OP( 0x53, ShapeSetSurfaceMaterial, RET_NONE, ARG( SHAPEID, shape ) ARG( MATERIAL, material ) ) +B3_REC_OP( 0x54, ShapeSetFilter, RET_NONE, ARG( SHAPEID, shape ) ARG( FILTER, filter ) ARG( BOOL, invokeContacts ) ) +B3_REC_OP( 0x55, ShapeEnableSensorEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x56, ShapeEnableContactEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x57, ShapeEnablePreSolveEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x58, ShapeEnableHitEvents, RET_NONE, ARG( SHAPEID, shape ) ARG( BOOL, flag ) ) +B3_REC_OP( 0x59, ShapeSetSphere, RET_NONE, ARG( SHAPEID, shape ) ARG( SPHERE, sphere ) ) +B3_REC_OP( 0x5A, ShapeSetCapsule, RET_NONE, ARG( SHAPEID, shape ) ARG( CAPSULE, capsule ) ) +B3_REC_OP( 0x5B, ShapeApplyWind, RET_NONE, + ARG( SHAPEID, shape ) ARG( VEC3, wind ) ARG( F32, drag ) ARG( F32, lift ) ARG( F32, maxSpeed ) ARG( BOOL, wake ) ) +B3_REC_OP( 0x5C, ShapeSetName, RET_NONE, ARG( SHAPEID, shape ) ARG( STR, name ) ) + +// Joint create and destroy +B3_REC_OP( 0x90, CreateParallelJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( PARALLELJOINTDEF, def ) ) +B3_REC_OP( 0x91, CreateDistanceJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( DISTANCEJOINTDEF, def ) ) +B3_REC_OP( 0x92, CreateFilterJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( FILTERJOINTDEF, def ) ) +B3_REC_OP( 0x93, CreateMotorJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( MOTORJOINTDEF, def ) ) +B3_REC_OP( 0x94, CreatePrismaticJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( PRISMATICJOINTDEF, def ) ) +B3_REC_OP( 0x95, CreateRevoluteJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( REVOLUTEJOINTDEF, def ) ) +B3_REC_OP( 0x96, CreateSphericalJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( SPHERICALJOINTDEF, def ) ) +B3_REC_OP( 0x97, CreateWeldJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( WELDJOINTDEF, def ) ) +B3_REC_OP( 0x98, CreateWheelJoint, RET_JOINTID, ARG( WORLDID, world ) ARG( WHEELJOINTDEF, def ) ) +B3_REC_OP( 0x99, DestroyJoint, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, wakeAttached ) ) + +// Generic joint mutators +B3_REC_OP( 0x9A, JointSetLocalFrameA, RET_NONE, ARG( JOINTID, joint ) ARG( TRANSFORM, localFrame ) ) +B3_REC_OP( 0x9B, JointSetLocalFrameB, RET_NONE, ARG( JOINTID, joint ) ARG( TRANSFORM, localFrame ) ) +B3_REC_OP( 0x9C, JointSetCollideConnected, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, shouldCollide ) ) +B3_REC_OP( 0x9D, JointWakeBodies, RET_NONE, ARG( JOINTID, joint ) ) +B3_REC_OP( 0x9E, JointSetConstraintTuning, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0x9F, JointSetForceThreshold, RET_NONE, ARG( JOINTID, joint ) ARG( F32, threshold ) ) +B3_REC_OP( 0xA0, JointSetTorqueThreshold, RET_NONE, ARG( JOINTID, joint ) ARG( F32, threshold ) ) + +// Parallel joint +B3_REC_OP( 0xA1, ParallelJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xA2, ParallelJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xA3, ParallelJointSetMaxTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxTorque ) ) + +// Distance joint +B3_REC_OP( 0xA4, DistanceJointSetLength, RET_NONE, ARG( JOINTID, joint ) ARG( F32, length ) ) +B3_REC_OP( 0xA5, DistanceJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) ) +B3_REC_OP( 0xA6, DistanceJointSetSpringForceRange, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lowerForce ) ARG( F32, upperForce ) ) +B3_REC_OP( 0xA7, DistanceJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xA8, DistanceJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xA9, DistanceJointEnableLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) ) +B3_REC_OP( 0xAA, DistanceJointSetLengthRange, RET_NONE, ARG( JOINTID, joint ) ARG( F32, minLength ) ARG( F32, maxLength ) ) +B3_REC_OP( 0xAB, DistanceJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) ) +B3_REC_OP( 0xAC, DistanceJointSetMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, motorSpeed ) ) +B3_REC_OP( 0xAD, DistanceJointSetMaxMotorForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, force ) ) + +// Motor joint +B3_REC_OP( 0xAE, MotorJointSetLinearVelocity, RET_NONE, ARG( JOINTID, joint ) ARG( VEC3, velocity ) ) +B3_REC_OP( 0xAF, MotorJointSetAngularVelocity, RET_NONE, ARG( JOINTID, joint ) ARG( VEC3, velocity ) ) +B3_REC_OP( 0xB0, MotorJointSetMaxVelocityForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxForce ) ) +B3_REC_OP( 0xB1, MotorJointSetMaxVelocityTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxTorque ) ) +B3_REC_OP( 0xB2, MotorJointSetLinearHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xB3, MotorJointSetLinearDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, damping ) ) +B3_REC_OP( 0xB4, MotorJointSetAngularHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xB5, MotorJointSetAngularDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, damping ) ) +B3_REC_OP( 0xB6, MotorJointSetMaxSpringForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxForce ) ) +B3_REC_OP( 0xB7, MotorJointSetMaxSpringTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, maxTorque ) ) + +// Prismatic joint +B3_REC_OP( 0xB8, PrismaticJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) ) +B3_REC_OP( 0xB9, PrismaticJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xBA, PrismaticJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xBB, PrismaticJointSetTargetTranslation, RET_NONE, ARG( JOINTID, joint ) ARG( F32, translation ) ) +B3_REC_OP( 0xBC, PrismaticJointEnableLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) ) +B3_REC_OP( 0xBD, PrismaticJointSetLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) ) +B3_REC_OP( 0xBE, PrismaticJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) ) +B3_REC_OP( 0xBF, PrismaticJointSetMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, motorSpeed ) ) +B3_REC_OP( 0xC0, PrismaticJointSetMaxMotorForce, RET_NONE, ARG( JOINTID, joint ) ARG( F32, force ) ) + +// Revolute joint +B3_REC_OP( 0xC1, RevoluteJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) ) +B3_REC_OP( 0xC2, RevoluteJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xC3, RevoluteJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xC4, RevoluteJointSetTargetAngle, RET_NONE, ARG( JOINTID, joint ) ARG( F32, angle ) ) +B3_REC_OP( 0xC5, RevoluteJointEnableLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) ) +B3_REC_OP( 0xC6, RevoluteJointSetLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) ) +B3_REC_OP( 0xC7, RevoluteJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) ) +B3_REC_OP( 0xC8, RevoluteJointSetMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, motorSpeed ) ) +B3_REC_OP( 0xC9, RevoluteJointSetMaxMotorTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) ) + +// Spherical joint +B3_REC_OP( 0xCA, SphericalJointEnableConeLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) ) +B3_REC_OP( 0xCB, SphericalJointSetConeLimit, RET_NONE, ARG( JOINTID, joint ) ARG( F32, angleRadians ) ) +B3_REC_OP( 0xCC, SphericalJointEnableTwistLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableLimit ) ) +B3_REC_OP( 0xCD, SphericalJointSetTwistLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) ) +B3_REC_OP( 0xCE, SphericalJointEnableSpring, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableSpring ) ) +B3_REC_OP( 0xCF, SphericalJointSetSpringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xD0, SphericalJointSetSpringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xD1, SphericalJointSetTargetRotation, RET_NONE, ARG( JOINTID, joint ) ARG( QUAT, targetRotation ) ) +B3_REC_OP( 0xD2, SphericalJointEnableMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, enableMotor ) ) +B3_REC_OP( 0xD3, SphericalJointSetMotorVelocity, RET_NONE, ARG( JOINTID, joint ) ARG( VEC3, motorVelocity ) ) +B3_REC_OP( 0xD4, SphericalJointSetMaxMotorTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) ) + +// Weld joint +B3_REC_OP( 0xD5, WeldJointSetLinearHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xD6, WeldJointSetLinearDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xD7, WeldJointSetAngularHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xD8, WeldJointSetAngularDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) + +// Wheel joint +B3_REC_OP( 0xD9, WheelJointEnableSuspension, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) ) +B3_REC_OP( 0xDA, WheelJointSetSuspensionHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xDB, WheelJointSetSuspensionDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xDC, WheelJointEnableSuspensionLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) ) +B3_REC_OP( 0xDD, WheelJointSetSuspensionLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) ) +B3_REC_OP( 0xDE, WheelJointEnableSpinMotor, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) ) +B3_REC_OP( 0xDF, WheelJointSetSpinMotorSpeed, RET_NONE, ARG( JOINTID, joint ) ARG( F32, speed ) ) + +// Wheel joint continued, overflow past the 0xDF range. +B3_REC_OP( 0xE0, WheelJointSetMaxSpinTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) ) +B3_REC_OP( 0xE1, WheelJointEnableSteering, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) ) +B3_REC_OP( 0xE2, WheelJointSetSteeringHertz, RET_NONE, ARG( JOINTID, joint ) ARG( F32, hertz ) ) +B3_REC_OP( 0xE3, WheelJointSetSteeringDampingRatio, RET_NONE, ARG( JOINTID, joint ) ARG( F32, dampingRatio ) ) +B3_REC_OP( 0xE4, WheelJointSetMaxSteeringTorque, RET_NONE, ARG( JOINTID, joint ) ARG( F32, torque ) ) +B3_REC_OP( 0xE5, WheelJointEnableSteeringLimit, RET_NONE, ARG( JOINTID, joint ) ARG( BOOL, flag ) ) +B3_REC_OP( 0xE6, WheelJointSetSteeringLimits, RET_NONE, ARG( JOINTID, joint ) ARG( F32, lower ) ARG( F32, upper ) ) +B3_REC_OP( 0xE7, WheelJointSetTargetSteeringAngle, RET_NONE, ARG( JOINTID, joint ) ARG( F32, radians ) ) + +// Spatial queries. Inputs flow through the manifest (reader side). The hit tail and result are +// hand-written in recording.c / recording_replay.c since they are variable length. +B3_REC_OP( 0xE8, QueryOverlapAABB, RET_NONE, ARG( WORLDID, world ) ARG( AABB, aabb ) ARG( QUERYFILTER, filter ) ) +B3_REC_OP( 0xE9, QueryOverlapShape, RET_NONE, + ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( SHAPEPROXY, proxy ) ARG( QUERYFILTER, filter ) ) +B3_REC_OP( 0xEA, QueryCastRay, RET_NONE, + ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( VEC3, translation ) ARG( QUERYFILTER, filter ) ) +B3_REC_OP( 0xEB, QueryCastShape, RET_NONE, + ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( SHAPEPROXY, proxy ) ARG( VEC3, translation ) + ARG( QUERYFILTER, filter ) ) +B3_REC_OP( 0xEC, QueryCastRayClosest, RET_NONE, + ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( VEC3, translation ) ARG( QUERYFILTER, filter ) ) +B3_REC_OP( 0xED, QueryCastMover, RET_NONE, + ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( CAPSULE, mover ) ARG( VEC3, translation ) ARG( QUERYFILTER, filter ) ) +B3_REC_OP( 0xEE, QueryCollideMover, RET_NONE, + ARG( WORLDID, world ) ARG( POSITION, origin ) ARG( CAPSULE, mover ) ARG( QUERYFILTER, filter ) ) + +// Identity key (hash of the caller id + label) for the query that immediately follows. Emitted only +// for a tagged query. The id and label are interned in the trailing tag table, so only the 8 byte key +// rides the stream. +B3_REC_OP( 0xEF, QueryTag, RET_NONE, ARG( U64, key ) ) + +B3_REC_OP( 0xF1, StateHash, RET_NONE, ARG( WORLDID, world ) ARG( U64, hash ) ) + +// Accumulated world bounds over the whole recording, written once at stop. +B3_REC_OP( 0xF2, RecordingBounds, RET_NONE, ARG( AABB, bounds ) ) diff --git a/vendor/box3d/src/src/recording_replay.c b/vendor/box3d/src/src/recording_replay.c new file mode 100644 index 000000000..584912d59 --- /dev/null +++ b/vendor/box3d/src/src/recording_replay.c @@ -0,0 +1,3634 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "recording_replay.h" + +#include "body.h" +#include "compound.h" +#include "physics_world.h" +#include "world_snapshot.h" + +#include "box3d/box3d.h" + +#include +#include +#include +#include + +// Read primitives + +static void b3RecRdrCheck( b3RecReader* rdr, int size ) +{ + if ( size < 0 || (int64_t)rdr->cursor + (int64_t)size > (int64_t)rdr->size ) + { + rdr->ok = false; + } +} + +static void b3RecRdrBlob( b3RecReader* rdr, void* out, int size ) +{ + b3RecRdrCheck( rdr, size ); + if ( !rdr->ok ) + { + memset( out, 0, (size_t)size ); + return; + } + memcpy( out, rdr->data + rdr->cursor, (size_t)size ); + rdr->cursor += size; +} + +uint8_t b3RecR_U8( b3RecReader* rdr ) +{ + b3RecRdrCheck( rdr, 1 ); + if ( !rdr->ok ) + { + return 0; + } + return rdr->data[rdr->cursor++]; +} + +uint16_t b3RecR_U16( b3RecReader* rdr ) +{ + b3RecRdrCheck( rdr, 2 ); + if ( !rdr->ok ) + { + return 0; + } + uint16_t v = (uint16_t)rdr->data[rdr->cursor] | ( (uint16_t)rdr->data[rdr->cursor + 1] << 8 ); + rdr->cursor += 2; + return v; +} + +uint32_t b3RecR_U24( b3RecReader* rdr ) +{ + b3RecRdrCheck( rdr, 3 ); + if ( !rdr->ok ) + { + return 0; + } + uint32_t v = (uint32_t)rdr->data[rdr->cursor] | ( (uint32_t)rdr->data[rdr->cursor + 1] << 8 ) | + ( (uint32_t)rdr->data[rdr->cursor + 2] << 16 ); + rdr->cursor += 3; + return v; +} + +uint32_t b3RecR_U32( b3RecReader* rdr ) +{ + b3RecRdrCheck( rdr, 4 ); + if ( !rdr->ok ) + { + return 0; + } + uint32_t v = (uint32_t)rdr->data[rdr->cursor] | ( (uint32_t)rdr->data[rdr->cursor + 1] << 8 ) | + ( (uint32_t)rdr->data[rdr->cursor + 2] << 16 ) | ( (uint32_t)rdr->data[rdr->cursor + 3] << 24 ); + rdr->cursor += 4; + return v; +} + +uint64_t b3RecR_U64( b3RecReader* rdr ) +{ + b3RecRdrCheck( rdr, 8 ); + if ( !rdr->ok ) + { + return 0; + } + uint64_t v = (uint64_t)rdr->data[rdr->cursor] | ( (uint64_t)rdr->data[rdr->cursor + 1] << 8 ) | + ( (uint64_t)rdr->data[rdr->cursor + 2] << 16 ) | ( (uint64_t)rdr->data[rdr->cursor + 3] << 24 ) | + ( (uint64_t)rdr->data[rdr->cursor + 4] << 32 ) | ( (uint64_t)rdr->data[rdr->cursor + 5] << 40 ) | + ( (uint64_t)rdr->data[rdr->cursor + 6] << 48 ) | ( (uint64_t)rdr->data[rdr->cursor + 7] << 56 ); + rdr->cursor += 8; + return v; +} + +int32_t b3RecR_I32( b3RecReader* rdr ) +{ + return (int32_t)b3RecR_U32( rdr ); +} + +float b3RecR_F32( b3RecReader* rdr ) +{ + uint32_t bits = b3RecR_U32( rdr ); + float v; + memcpy( &v, &bits, 4 ); + return v; +} + +double b3RecR_F64( b3RecReader* rdr ) +{ + uint64_t bits = b3RecR_U64( rdr ); + double v; + memcpy( &v, &bits, 8 ); + return v; +} + +bool b3RecR_BOOL( b3RecReader* rdr ) +{ + return b3RecR_U8( rdr ) != 0u; +} + +b3Vec3 b3RecR_VEC3( b3RecReader* rdr ) +{ + b3Vec3 v; + v.x = b3RecR_F32( rdr ); + v.y = b3RecR_F32( rdr ); + v.z = b3RecR_F32( rdr ); + return v; +} + +b3Quat b3RecR_QUAT( b3RecReader* rdr ) +{ + b3Quat q; + q.v.x = b3RecR_F32( rdr ); + q.v.y = b3RecR_F32( rdr ); + q.v.z = b3RecR_F32( rdr ); + q.s = b3RecR_F32( rdr ); + return q; +} + +b3Transform b3RecR_TRANSFORM( b3RecReader* rdr ) +{ + b3Transform t; + t.p = b3RecR_VEC3( rdr ); + t.q = b3RecR_QUAT( rdr ); + return t; +} + +b3Pos b3RecR_POSITION( b3RecReader* rdr ) +{ + b3Pos p; +#if defined( BOX3D_DOUBLE_PRECISION ) + p.x = b3RecR_F64( rdr ); + p.y = b3RecR_F64( rdr ); + p.z = b3RecR_F64( rdr ); +#else + p.x = b3RecR_F32( rdr ); + p.y = b3RecR_F32( rdr ); + p.z = b3RecR_F32( rdr ); +#endif + return p; +} + +b3WorldTransform b3RecR_WORLDXF( b3RecReader* rdr ) +{ + b3WorldTransform t; + t.p = b3RecR_POSITION( rdr ); + t.q = b3RecR_QUAT( rdr ); + return t; +} + +b3Matrix3 b3RecR_MATRIX3( b3RecReader* rdr ) +{ + b3Matrix3 m; + m.cx = b3RecR_VEC3( rdr ); + m.cy = b3RecR_VEC3( rdr ); + m.cz = b3RecR_VEC3( rdr ); + return m; +} + +b3AABB b3RecR_AABB( b3RecReader* rdr ) +{ + b3AABB v; + v.lowerBound = b3RecR_VEC3( rdr ); + v.upperBound = b3RecR_VEC3( rdr ); + return v; +} + +b3QueryFilter b3RecR_QUERYFILTER( b3RecReader* rdr ) +{ + // id and name are not on the wire here, they ride the separate QueryTag op. Start from the default + // so they keep the untagged sentinel instead of garbage. + b3QueryFilter f = b3DefaultQueryFilter(); + f.categoryBits = b3RecR_U64( rdr ); + f.maskBits = b3RecR_U64( rdr ); + return f; +} + +// Reserve reader scratch for a count taken from an untrusted file. Every recorded element +// consumes at least one byte, so a valid count can never exceed the bytes left in the file. +// Reject anything larger (or negative, or that would overflow the byte size) by failing the read +// rather than allocating wildly. A grow keeps the old contents so callers can accumulate across +// reserves, as the collide-mover dispatcher does one shape group at a time. +static bool b3RecReserveScratch( b3RecReader* rdr, void** data, int* cap, int need, int elemSize ) +{ + int remaining = rdr->size - rdr->cursor; + if ( need < 0 || remaining < 0 || need > remaining || need > INT_MAX / elemSize ) + { + rdr->ok = false; + return false; + } + if ( need <= *cap ) + { + return true; + } + int newCap = need <= INT_MAX / elemSize - 8 ? need + 8 : need; + void* grown = b3Alloc( (size_t)newCap * (size_t)elemSize ); + if ( *data != NULL ) + { + memcpy( grown, *data, (size_t)*cap * (size_t)elemSize ); + b3Free( *data, (size_t)*cap * (size_t)elemSize ); + } + *data = grown; + *cap = newCap; + return true; +} + +// Variable length, mirrors b3RecW_SHAPEPROXY: count, count points, radius. The decoded proxy borrows +// the reader's scratch for its point cloud, valid until the next proxy read. +b3ShapeProxy b3RecR_SHAPEPROXY( b3RecReader* rdr ) +{ + b3ShapeProxy p = { 0 }; + int count = b3RecR_I32( rdr ); + if ( count < 0 ) + count = 0; + if ( count > B3_MAX_SHAPE_CAST_POINTS ) + count = B3_MAX_SHAPE_CAST_POINTS; + if ( count > 0 && + b3RecReserveScratch( rdr, (void**)&rdr->proxyScratch, &rdr->proxyScratchCap, count, (int)sizeof( b3Vec3 ) ) ) + { + for ( int i = 0; i < count; ++i ) + { + rdr->proxyScratch[i] = b3RecR_VEC3( rdr ); + } + p.points = rdr->proxyScratch; + p.count = count; + } + p.radius = b3RecR_F32( rdr ); + return p; +} + +b3TreeStats b3RecR_TREESTATS( b3RecReader* rdr ) +{ + b3TreeStats v; + v.nodeVisits = b3RecR_I32( rdr ); + v.leafVisits = b3RecR_I32( rdr ); + return v; +} + +b3RayResult b3RecR_RAYRESULT( b3RecReader* rdr ) +{ + b3RayResult v = { 0 }; + // shapeId keeps the recorded world0; b3RecMakeShapeId is applied at compare time + v.shapeId = b3RecR_SHAPEID( rdr ); + v.point = b3RecR_POSITION( rdr ); + v.normal = b3RecR_VEC3( rdr ); + v.userMaterialId = b3RecR_U64( rdr ); + v.fraction = b3RecR_F32( rdr ); + v.triangleIndex = b3RecR_I32( rdr ); + v.childIndex = b3RecR_I32( rdr ); + v.hit = b3RecR_BOOL( rdr ); + return v; +} + +b3PlaneResult b3RecR_PLANERESULT( b3RecReader* rdr ) +{ + b3PlaneResult v; + v.plane.normal = b3RecR_VEC3( rdr ); + v.plane.offset = b3RecR_F32( rdr ); + v.point = b3RecR_VEC3( rdr ); + return v; +} + +b3WorldId b3RecR_WORLDID( b3RecReader* rdr ) +{ + return b3LoadWorldId( b3RecR_U32( rdr ) ); +} + +b3BodyId b3RecR_BODYID( b3RecReader* rdr ) +{ + return b3LoadBodyId( b3RecR_U64( rdr ) ); +} + +b3ShapeId b3RecR_SHAPEID( b3RecReader* rdr ) +{ + return b3LoadShapeId( b3RecR_U64( rdr ) ); +} + +b3JointId b3RecR_JOINTID( b3RecReader* rdr ) +{ + return b3LoadJointId( b3RecR_U64( rdr ) ); +} + +b3Sphere b3RecR_SPHERE( b3RecReader* rdr ) +{ + b3Sphere s; + b3RecRdrBlob( rdr, &s, (int)sizeof( s ) ); + return s; +} + +b3Capsule b3RecR_CAPSULE( b3RecReader* rdr ) +{ + b3Capsule c; + b3RecRdrBlob( rdr, &c, (int)sizeof( c ) ); + return c; +} + +uint32_t b3RecR_GEOMID( b3RecReader* rdr ) +{ + return b3RecR_U32( rdr ); +} + +b3Filter b3RecR_FILTER( b3RecReader* rdr ) +{ + b3Filter f; + f.categoryBits = b3RecR_U64( rdr ); + f.maskBits = b3RecR_U64( rdr ); + f.groupIndex = b3RecR_I32( rdr ); + return f; +} + +b3SurfaceMaterial b3RecR_MATERIAL( b3RecReader* rdr ) +{ + b3SurfaceMaterial m = b3DefaultSurfaceMaterial(); + m.friction = b3RecR_F32( rdr ); + m.restitution = b3RecR_F32( rdr ); + m.rollingResistance = b3RecR_F32( rdr ); + m.tangentVelocity = b3RecR_VEC3( rdr ); + m.userMaterialId = b3RecR_U64( rdr ); + m.customColor = b3RecR_U32( rdr ); + return m; +} + +b3MassData b3RecR_MASSDATA( b3RecReader* rdr ) +{ + b3MassData md; + md.mass = b3RecR_F32( rdr ); + md.center = b3RecR_VEC3( rdr ); + md.inertia = b3RecR_MATRIX3( rdr ); + return md; +} + +b3MotionLocks b3RecR_LOCKS( b3RecReader* rdr ) +{ + b3MotionLocks locks; + locks.linearX = b3RecR_BOOL( rdr ); + locks.linearY = b3RecR_BOOL( rdr ); + locks.linearZ = b3RecR_BOOL( rdr ); + locks.angularX = b3RecR_BOOL( rdr ); + locks.angularY = b3RecR_BOOL( rdr ); + locks.angularZ = b3RecR_BOOL( rdr ); + return locks; +} + +// Rotating set of static string buffers, valid until the next 4 STR reads. +const char* b3RecR_STR( b3RecReader* rdr ) +{ + char* buf = rdr->stringBuffers[rdr->nextString]; + rdr->nextString = ( rdr->nextString + 1 ) & 3; + + uint16_t len = b3RecR_U16( rdr ); + if ( len == 0xFFFFu ) + { + return NULL; + } + + int n = (int)len; + if ( n > B3_MAX_NAME_LENGTH ) + { + n = B3_MAX_NAME_LENGTH; + } + b3RecRdrCheck( rdr, (int)len ); + if ( rdr->ok && n > 0 ) + { + memcpy( buf, rdr->data + rdr->cursor, (size_t)n ); + } + rdr->cursor += (int)len; + buf[n] = '\0'; + return buf; +} + +// Def readers: start from b3Default*Def() then overlay each serialized field +// in the exact order the writer produced them. + +b3ExplosionDef b3RecR_EXPLOSIONDEF( b3RecReader* rdr ) +{ + b3ExplosionDef def = b3DefaultExplosionDef(); + def.maskBits = b3RecR_U64( rdr ); + def.position = b3RecR_POSITION( rdr ); + def.radius = b3RecR_F32( rdr ); + def.falloff = b3RecR_F32( rdr ); + def.impulsePerArea = b3RecR_F32( rdr ); + return def; +} + +b3BodyDef b3RecR_BODYDEF( b3RecReader* rdr ) +{ + b3BodyDef def = b3DefaultBodyDef(); + def.type = (b3BodyType)b3RecR_I32( rdr ); + def.position = b3RecR_POSITION( rdr ); + def.rotation = b3RecR_QUAT( rdr ); + def.linearVelocity = b3RecR_VEC3( rdr ); + def.angularVelocity = b3RecR_VEC3( rdr ); + def.linearDamping = b3RecR_F32( rdr ); + def.angularDamping = b3RecR_F32( rdr ); + def.gravityScale = b3RecR_F32( rdr ); + def.sleepThreshold = b3RecR_F32( rdr ); + def.name = b3RecR_STR( rdr ); + (void)b3RecR_U64( rdr ); // userData placeholder + def.motionLocks = b3RecR_LOCKS( rdr ); + def.enableSleep = b3RecR_BOOL( rdr ); + def.isAwake = b3RecR_BOOL( rdr ); + def.isBullet = b3RecR_BOOL( rdr ); + def.isEnabled = b3RecR_BOOL( rdr ); + def.allowFastRotation = b3RecR_BOOL( rdr ); + def.enableContactRecycling = b3RecR_BOOL( rdr ); + def.userData = NULL; + return def; +} + +b3ShapeDef b3RecR_SHAPEDEF( b3RecReader* rdr ) +{ + b3ShapeDef def = b3DefaultShapeDef(); + + def.name = b3RecR_STR( rdr ); + (void)b3RecR_U64( rdr ); // userData placeholder + + int matCount = b3RecR_I32( rdr ); + if ( matCount < 0 ) + { + matCount = 0; + } + if ( matCount > 0 && + b3RecReserveScratch( rdr, (void**)&rdr->matScratch, &rdr->matScratchCap, matCount, (int)sizeof( b3SurfaceMaterial ) ) ) + { + for ( int i = 0; i < matCount; ++i ) + { + rdr->matScratch[i] = b3RecR_MATERIAL( rdr ); + } + def.materials = rdr->matScratch; + def.materialCount = matCount; + } + else + { + for ( int i = 0; i < matCount; ++i ) + { + (void)b3RecR_MATERIAL( rdr ); + } + def.materials = NULL; + def.materialCount = 0; + } + + def.baseMaterial = b3RecR_MATERIAL( rdr ); + def.density = b3RecR_F32( rdr ); + def.explosionScale = b3RecR_F32( rdr ); + def.filter = b3RecR_FILTER( rdr ); + def.enableCustomFiltering = b3RecR_BOOL( rdr ); + def.isSensor = b3RecR_BOOL( rdr ); + def.enableSensorEvents = b3RecR_BOOL( rdr ); + def.enableContactEvents = b3RecR_BOOL( rdr ); + def.enableHitEvents = b3RecR_BOOL( rdr ); + def.enablePreSolveEvents = b3RecR_BOOL( rdr ); + def.invokeContactCreation = b3RecR_BOOL( rdr ); + def.updateBodyMass = b3RecR_BOOL( rdr ); + def.enableSpeculativeContact = b3RecR_BOOL( rdr ); + def.userData = NULL; + return def; +} + +// Shared base for all joint defs. Body ids come in with recorded world0; callers remap them. +static void b3RecR_JointBase( b3RecReader* rdr, b3JointDef* base ) +{ + (void)b3RecR_U64( rdr ); // userData + base->bodyIdA = b3RecR_BODYID( rdr ); + base->bodyIdB = b3RecR_BODYID( rdr ); + base->localFrameA = b3RecR_TRANSFORM( rdr ); + base->localFrameB = b3RecR_TRANSFORM( rdr ); + base->forceThreshold = b3RecR_F32( rdr ); + base->torqueThreshold = b3RecR_F32( rdr ); + base->constraintHertz = b3RecR_F32( rdr ); + base->constraintDampingRatio = b3RecR_F32( rdr ); + base->drawScale = b3RecR_F32( rdr ); + base->collideConnected = b3RecR_BOOL( rdr ); + base->userData = NULL; +} + +b3ParallelJointDef b3RecR_PARALLELJOINTDEF( b3RecReader* rdr ) +{ + b3ParallelJointDef def = b3DefaultParallelJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.hertz = b3RecR_F32( rdr ); + def.dampingRatio = b3RecR_F32( rdr ); + def.maxTorque = b3RecR_F32( rdr ); + return def; +} + +b3DistanceJointDef b3RecR_DISTANCEJOINTDEF( b3RecReader* rdr ) +{ + b3DistanceJointDef def = b3DefaultDistanceJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.length = b3RecR_F32( rdr ); + def.enableSpring = b3RecR_BOOL( rdr ); + def.lowerSpringForce = b3RecR_F32( rdr ); + def.upperSpringForce = b3RecR_F32( rdr ); + def.hertz = b3RecR_F32( rdr ); + def.dampingRatio = b3RecR_F32( rdr ); + def.enableLimit = b3RecR_BOOL( rdr ); + def.minLength = b3RecR_F32( rdr ); + def.maxLength = b3RecR_F32( rdr ); + def.enableMotor = b3RecR_BOOL( rdr ); + def.maxMotorForce = b3RecR_F32( rdr ); + def.motorSpeed = b3RecR_F32( rdr ); + return def; +} + +b3FilterJointDef b3RecR_FILTERJOINTDEF( b3RecReader* rdr ) +{ + b3FilterJointDef def = b3DefaultFilterJointDef(); + b3RecR_JointBase( rdr, &def.base ); + return def; +} + +b3MotorJointDef b3RecR_MOTORJOINTDEF( b3RecReader* rdr ) +{ + b3MotorJointDef def = b3DefaultMotorJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.linearVelocity = b3RecR_VEC3( rdr ); + def.maxVelocityForce = b3RecR_F32( rdr ); + def.angularVelocity = b3RecR_VEC3( rdr ); + def.maxVelocityTorque = b3RecR_F32( rdr ); + def.linearHertz = b3RecR_F32( rdr ); + def.linearDampingRatio = b3RecR_F32( rdr ); + def.maxSpringForce = b3RecR_F32( rdr ); + def.angularHertz = b3RecR_F32( rdr ); + def.angularDampingRatio = b3RecR_F32( rdr ); + def.maxSpringTorque = b3RecR_F32( rdr ); + return def; +} + +b3PrismaticJointDef b3RecR_PRISMATICJOINTDEF( b3RecReader* rdr ) +{ + b3PrismaticJointDef def = b3DefaultPrismaticJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.enableSpring = b3RecR_BOOL( rdr ); + def.hertz = b3RecR_F32( rdr ); + def.dampingRatio = b3RecR_F32( rdr ); + def.targetTranslation = b3RecR_F32( rdr ); + def.enableLimit = b3RecR_BOOL( rdr ); + def.lowerTranslation = b3RecR_F32( rdr ); + def.upperTranslation = b3RecR_F32( rdr ); + def.enableMotor = b3RecR_BOOL( rdr ); + def.maxMotorForce = b3RecR_F32( rdr ); + def.motorSpeed = b3RecR_F32( rdr ); + return def; +} + +b3RevoluteJointDef b3RecR_REVOLUTEJOINTDEF( b3RecReader* rdr ) +{ + b3RevoluteJointDef def = b3DefaultRevoluteJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.targetAngle = b3RecR_F32( rdr ); + def.enableSpring = b3RecR_BOOL( rdr ); + def.hertz = b3RecR_F32( rdr ); + def.dampingRatio = b3RecR_F32( rdr ); + def.enableLimit = b3RecR_BOOL( rdr ); + def.lowerAngle = b3RecR_F32( rdr ); + def.upperAngle = b3RecR_F32( rdr ); + def.enableMotor = b3RecR_BOOL( rdr ); + def.maxMotorTorque = b3RecR_F32( rdr ); + def.motorSpeed = b3RecR_F32( rdr ); + return def; +} + +b3SphericalJointDef b3RecR_SPHERICALJOINTDEF( b3RecReader* rdr ) +{ + b3SphericalJointDef def = b3DefaultSphericalJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.enableSpring = b3RecR_BOOL( rdr ); + def.hertz = b3RecR_F32( rdr ); + def.dampingRatio = b3RecR_F32( rdr ); + def.targetRotation = b3RecR_QUAT( rdr ); + def.enableConeLimit = b3RecR_BOOL( rdr ); + def.coneAngle = b3RecR_F32( rdr ); + def.enableTwistLimit = b3RecR_BOOL( rdr ); + def.lowerTwistAngle = b3RecR_F32( rdr ); + def.upperTwistAngle = b3RecR_F32( rdr ); + def.enableMotor = b3RecR_BOOL( rdr ); + def.maxMotorTorque = b3RecR_F32( rdr ); + def.motorVelocity = b3RecR_VEC3( rdr ); + return def; +} + +b3WeldJointDef b3RecR_WELDJOINTDEF( b3RecReader* rdr ) +{ + b3WeldJointDef def = b3DefaultWeldJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.linearHertz = b3RecR_F32( rdr ); + def.angularHertz = b3RecR_F32( rdr ); + def.linearDampingRatio = b3RecR_F32( rdr ); + def.angularDampingRatio = b3RecR_F32( rdr ); + return def; +} + +b3WheelJointDef b3RecR_WHEELJOINTDEF( b3RecReader* rdr ) +{ + b3WheelJointDef def = b3DefaultWheelJointDef(); + b3RecR_JointBase( rdr, &def.base ); + def.enableSuspensionSpring = b3RecR_BOOL( rdr ); + def.suspensionHertz = b3RecR_F32( rdr ); + def.suspensionDampingRatio = b3RecR_F32( rdr ); + def.enableSuspensionLimit = b3RecR_BOOL( rdr ); + def.lowerSuspensionLimit = b3RecR_F32( rdr ); + def.upperSuspensionLimit = b3RecR_F32( rdr ); + def.enableSpinMotor = b3RecR_BOOL( rdr ); + def.maxSpinTorque = b3RecR_F32( rdr ); + def.spinSpeed = b3RecR_F32( rdr ); + def.enableSteering = b3RecR_BOOL( rdr ); + def.steeringHertz = b3RecR_F32( rdr ); + def.steeringDampingRatio = b3RecR_F32( rdr ); + def.targetSteeringAngle = b3RecR_F32( rdr ); + def.maxSteeringTorque = b3RecR_F32( rdr ); + def.enableSteeringLimit = b3RecR_BOOL( rdr ); + def.lowerSteeringLimit = b3RecR_F32( rdr ); + def.upperSteeringLimit = b3RecR_F32( rdr ); + return def; +} + +// Outliner body tracking. Defined after the player struct; forward declared here because the +// create/destroy dispatch sits above that struct. +static void b3RecTrackBodyCreate( b3RecPlayer* player, b3BodyId id ); +static void b3RecTrackBodyDestroy( b3RecPlayer* player, b3BodyId id ); + +// Id retargeting: replace world0 with the replay world's slot index. + +static b3BodyId b3RecMakeBodyId( b3RecReader* rdr, b3BodyId recorded ) +{ + b3BodyId id; + id.index1 = recorded.index1; + id.world0 = (uint16_t)( rdr->replayWorldId.index1 - 1u ); + id.generation = recorded.generation; + return id; +} + +static b3ShapeId b3RecMakeShapeId( b3RecReader* rdr, b3ShapeId recorded ) +{ + b3ShapeId id; + id.index1 = recorded.index1; + id.world0 = (uint16_t)( rdr->replayWorldId.index1 - 1u ); + id.generation = recorded.generation; + return id; +} + +static b3JointId b3RecMakeJointId( b3RecReader* rdr, b3JointId recorded ) +{ + b3JointId id; + id.index1 = recorded.index1; + id.world0 = (uint16_t)( rdr->replayWorldId.index1 - 1u ); + id.generation = recorded.generation; + return id; +} + +// A create op appends the returned id after args. index1 and generation must match; +// world0 always differs so we ignore it. +static void b3RecCheckId( b3RecReader* rdr, const char* kind, int gotIndex, unsigned gotGen, int recIndex, unsigned recGen ) +{ + if ( gotIndex != recIndex || gotGen != recGen ) + { + printf( "b3ReplayFile: %s id mismatch (rec index1=%d gen=%u, got index1=%d gen=%u)\n", kind, recIndex, recGen, gotIndex, + gotGen ); + rdr->ok = false; + } +} + +static void b3RecCheckBodyId( b3RecReader* rdr, b3BodyId got, b3BodyId rec ) +{ + b3RecCheckId( rdr, "body", got.index1, got.generation, rec.index1, rec.generation ); +} + +static void b3RecCheckShapeId( b3RecReader* rdr, b3ShapeId got, b3ShapeId rec ) +{ + b3RecCheckId( rdr, "shape", got.index1, got.generation, rec.index1, rec.generation ); +} + +static void b3RecCheckJointId( b3RecReader* rdr, b3JointId got, b3JointId rec ) +{ + b3RecCheckId( rdr, "joint", got.index1, got.generation, rec.index1, rec.generation ); +} + +// Registry slot reconstruction. Returns the live pointer for the given slot, building it +// on first use. The hull case is handled inline at the call site since it doesn't cache. + +static void* b3RecGetLiveMesh( b3RegistrySlot* slot ) +{ + // Mesh is a self-contained blob used by reference, with no pointer fixup. Hand back the pristine + // bytes directly: they already outlive the world and are freed at teardown, so a copy would just + // double the memory. Compound can't do this, see b3RecGetLiveCompound. + return slot->bytes; +} + +static void* b3RecGetLiveHeightField( b3RegistrySlot* slot ) +{ + // Self-contained blob used by reference, like b3RecGetLiveMesh. The bytes already are a + // valid b3HeightFieldData with no pointer fixup, so hand them back directly. + return slot->bytes; +} + +static void* b3RecGetLiveCompound( b3RegistrySlot* slot ) +{ + if ( slot->live != NULL ) + { + return slot->live; + } + // The copy is unavoidable here: b3ConvertBytesToCompound rewrites its input in place, while the + // pristine bytes must survive for keyframe registry seeding (b3RecSeedKeyframeRegistry). So we + // keep both the serialized bytes and a separate converted live object. + slot->live = b3Alloc( (size_t)slot->byteCount ); + memcpy( slot->live, slot->bytes, (size_t)slot->byteCount ); + b3ConvertBytesToCompound( (uint8_t*)slot->live, slot->byteCount ); + return slot->live; +} + +// Dispatch functions, one per op + +static void b3RecDispatch_DestroyWorld( const b3RecArgs_DestroyWorld* a, b3RecReader* rdr ) +{ + (void)a; + (void)rdr; + // End-of-session marker. The replay world is torn down in b3ValidateReplay, not here. +} + +static void b3RecDispatch_Step( const b3RecArgs_Step* a, b3RecReader* rdr ) +{ + (void)a; + b3World_Step( rdr->replayWorldId, a->dt, a->subStepCount ); +} + +static void b3RecDispatch_WorldEnableSleeping( const b3RecArgs_WorldEnableSleeping* a, b3RecReader* rdr ) +{ + b3World_EnableSleeping( rdr->replayWorldId, a->flag ); +} + +static void b3RecDispatch_WorldEnableContinuous( const b3RecArgs_WorldEnableContinuous* a, b3RecReader* rdr ) +{ + b3World_EnableContinuous( rdr->replayWorldId, a->flag ); +} + +static void b3RecDispatch_WorldSetRestitutionThreshold( const b3RecArgs_WorldSetRestitutionThreshold* a, b3RecReader* rdr ) +{ + b3World_SetRestitutionThreshold( rdr->replayWorldId, a->value ); +} + +static void b3RecDispatch_WorldSetHitEventThreshold( const b3RecArgs_WorldSetHitEventThreshold* a, b3RecReader* rdr ) +{ + b3World_SetHitEventThreshold( rdr->replayWorldId, a->value ); +} + +static void b3RecDispatch_WorldSetGravity( const b3RecArgs_WorldSetGravity* a, b3RecReader* rdr ) +{ + b3World_SetGravity( rdr->replayWorldId, a->gravity ); +} + +static void b3RecDispatch_WorldExplode( const b3RecArgs_WorldExplode* a, b3RecReader* rdr ) +{ + b3World_Explode( rdr->replayWorldId, &a->def ); +} + +static void b3RecDispatch_WorldSetContactTuning( const b3RecArgs_WorldSetContactTuning* a, b3RecReader* rdr ) +{ + b3World_SetContactTuning( rdr->replayWorldId, a->hertz, a->dampingRatio, a->contactSpeed ); +} + +static void b3RecDispatch_WorldSetContactRecycleDistance( const b3RecArgs_WorldSetContactRecycleDistance* a, b3RecReader* rdr ) +{ + b3World_SetContactRecycleDistance( rdr->replayWorldId, a->recycleDistance ); +} + +static void b3RecDispatch_WorldSetMaximumLinearSpeed( const b3RecArgs_WorldSetMaximumLinearSpeed* a, b3RecReader* rdr ) +{ + b3World_SetMaximumLinearSpeed( rdr->replayWorldId, a->maximumLinearSpeed ); +} + +static void b3RecDispatch_WorldEnableWarmStarting( const b3RecArgs_WorldEnableWarmStarting* a, b3RecReader* rdr ) +{ + b3World_EnableWarmStarting( rdr->replayWorldId, a->flag ); +} + +static void b3RecDispatch_WorldRebuildStaticTree( const b3RecArgs_WorldRebuildStaticTree* a, b3RecReader* rdr ) +{ + (void)a; + b3World_RebuildStaticTree( rdr->replayWorldId ); +} + +static void b3RecDispatch_WorldEnableSpeculative( const b3RecArgs_WorldEnableSpeculative* a, b3RecReader* rdr ) +{ + b3World_EnableSpeculative( rdr->replayWorldId, a->flag ); +} + +static void b3RecDispatch_CreateBody( const b3RecArgs_CreateBody* a, b3RecReader* rdr ) +{ + b3BodyId recId = b3RecR_BODYID( rdr ); + b3BodyId gotId = b3CreateBody( rdr->replayWorldId, &a->def ); + b3RecCheckBodyId( rdr, gotId, recId ); + if ( rdr->owner != NULL ) + { + b3RecTrackBodyCreate( rdr->owner, gotId ); + } +} + +static void b3RecDispatch_DestroyBody( const b3RecArgs_DestroyBody* a, b3RecReader* rdr ) +{ + b3BodyId id = b3RecMakeBodyId( rdr, a->body ); + if ( rdr->owner != NULL ) + { + b3RecTrackBodyDestroy( rdr->owner, id ); + } + b3DestroyBody( id ); +} + +static void b3RecDispatch_BodySetTransform( const b3RecArgs_BodySetTransform* a, b3RecReader* rdr ) +{ + b3Body_SetTransform( b3RecMakeBodyId( rdr, a->body ), a->position, a->rotation ); +} + +static void b3RecDispatch_BodySetLinearVelocity( const b3RecArgs_BodySetLinearVelocity* a, b3RecReader* rdr ) +{ + b3Body_SetLinearVelocity( b3RecMakeBodyId( rdr, a->body ), a->v ); +} + +static void b3RecDispatch_BodySetType( const b3RecArgs_BodySetType* a, b3RecReader* rdr ) +{ + b3Body_SetType( b3RecMakeBodyId( rdr, a->body ), (b3BodyType)a->type ); +} + +static void b3RecDispatch_BodySetName( const b3RecArgs_BodySetName* a, b3RecReader* rdr ) +{ + b3Body_SetName( b3RecMakeBodyId( rdr, a->body ), a->name ); +} + +static void b3RecDispatch_BodySetAngularVelocity( const b3RecArgs_BodySetAngularVelocity* a, b3RecReader* rdr ) +{ + b3Body_SetAngularVelocity( b3RecMakeBodyId( rdr, a->body ), a->w ); +} + +static void b3RecDispatch_BodySetTargetTransform( const b3RecArgs_BodySetTargetTransform* a, b3RecReader* rdr ) +{ + b3Body_SetTargetTransform( b3RecMakeBodyId( rdr, a->body ), a->target, a->timeStep, a->wake ); +} + +static void b3RecDispatch_BodyApplyForce( const b3RecArgs_BodyApplyForce* a, b3RecReader* rdr ) +{ + b3Body_ApplyForce( b3RecMakeBodyId( rdr, a->body ), a->force, a->point, a->wake ); +} + +static void b3RecDispatch_BodyApplyForceToCenter( const b3RecArgs_BodyApplyForceToCenter* a, b3RecReader* rdr ) +{ + b3Body_ApplyForceToCenter( b3RecMakeBodyId( rdr, a->body ), a->force, a->wake ); +} + +static void b3RecDispatch_BodyApplyTorque( const b3RecArgs_BodyApplyTorque* a, b3RecReader* rdr ) +{ + b3Body_ApplyTorque( b3RecMakeBodyId( rdr, a->body ), a->torque, a->wake ); +} + +static void b3RecDispatch_BodyApplyLinearImpulse( const b3RecArgs_BodyApplyLinearImpulse* a, b3RecReader* rdr ) +{ + b3Body_ApplyLinearImpulse( b3RecMakeBodyId( rdr, a->body ), a->impulse, a->point, a->wake ); +} + +static void b3RecDispatch_BodyApplyLinearImpulseToCenter( const b3RecArgs_BodyApplyLinearImpulseToCenter* a, b3RecReader* rdr ) +{ + b3Body_ApplyLinearImpulseToCenter( b3RecMakeBodyId( rdr, a->body ), a->impulse, a->wake ); +} + +static void b3RecDispatch_BodyApplyAngularImpulse( const b3RecArgs_BodyApplyAngularImpulse* a, b3RecReader* rdr ) +{ + b3Body_ApplyAngularImpulse( b3RecMakeBodyId( rdr, a->body ), a->impulse, a->wake ); +} + +static void b3RecDispatch_BodySetMassData( const b3RecArgs_BodySetMassData* a, b3RecReader* rdr ) +{ + b3Body_SetMassData( b3RecMakeBodyId( rdr, a->body ), a->massData ); +} + +static void b3RecDispatch_BodyApplyMassFromShapes( const b3RecArgs_BodyApplyMassFromShapes* a, b3RecReader* rdr ) +{ + b3Body_ApplyMassFromShapes( b3RecMakeBodyId( rdr, a->body ) ); +} + +static void b3RecDispatch_BodySetLinearDamping( const b3RecArgs_BodySetLinearDamping* a, b3RecReader* rdr ) +{ + b3Body_SetLinearDamping( b3RecMakeBodyId( rdr, a->body ), a->damping ); +} + +static void b3RecDispatch_BodySetAngularDamping( const b3RecArgs_BodySetAngularDamping* a, b3RecReader* rdr ) +{ + b3Body_SetAngularDamping( b3RecMakeBodyId( rdr, a->body ), a->damping ); +} + +static void b3RecDispatch_BodySetGravityScale( const b3RecArgs_BodySetGravityScale* a, b3RecReader* rdr ) +{ + b3Body_SetGravityScale( b3RecMakeBodyId( rdr, a->body ), a->scale ); +} + +static void b3RecDispatch_BodySetAwake( const b3RecArgs_BodySetAwake* a, b3RecReader* rdr ) +{ + b3Body_SetAwake( b3RecMakeBodyId( rdr, a->body ), a->awake ); +} + +static void b3RecDispatch_BodyEnableSleep( const b3RecArgs_BodyEnableSleep* a, b3RecReader* rdr ) +{ + b3Body_EnableSleep( b3RecMakeBodyId( rdr, a->body ), a->flag ); +} + +static void b3RecDispatch_BodySetSleepThreshold( const b3RecArgs_BodySetSleepThreshold* a, b3RecReader* rdr ) +{ + b3Body_SetSleepThreshold( b3RecMakeBodyId( rdr, a->body ), a->threshold ); +} + +static void b3RecDispatch_BodyDisable( const b3RecArgs_BodyDisable* a, b3RecReader* rdr ) +{ + b3Body_Disable( b3RecMakeBodyId( rdr, a->body ) ); +} + +static void b3RecDispatch_BodyEnable( const b3RecArgs_BodyEnable* a, b3RecReader* rdr ) +{ + b3Body_Enable( b3RecMakeBodyId( rdr, a->body ) ); +} + +static void b3RecDispatch_BodySetMotionLocks( const b3RecArgs_BodySetMotionLocks* a, b3RecReader* rdr ) +{ + b3Body_SetMotionLocks( b3RecMakeBodyId( rdr, a->body ), a->locks ); +} + +static void b3RecDispatch_BodySetBullet( const b3RecArgs_BodySetBullet* a, b3RecReader* rdr ) +{ + b3Body_SetBullet( b3RecMakeBodyId( rdr, a->body ), a->flag ); +} + +static void b3RecDispatch_BodyEnableContactRecycling( const b3RecArgs_BodyEnableContactRecycling* a, b3RecReader* rdr ) +{ + b3Body_EnableContactRecycling( b3RecMakeBodyId( rdr, a->body ), a->flag ); +} + +static void b3RecDispatch_BodyEnableHitEvents( const b3RecArgs_BodyEnableHitEvents* a, b3RecReader* rdr ) +{ + b3Body_EnableHitEvents( b3RecMakeBodyId( rdr, a->body ), a->flag ); +} + +static void b3RecDispatch_CreateSphereShape( const b3RecArgs_CreateSphereShape* a, b3RecReader* rdr ) +{ + b3ShapeId recId = b3RecR_SHAPEID( rdr ); + b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); + b3ShapeId gotId = b3CreateSphereShape( bodyId, &a->def, &a->sphere ); + b3RecCheckShapeId( rdr, gotId, recId ); +} + +static void b3RecDispatch_CreateCapsuleShape( const b3RecArgs_CreateCapsuleShape* a, b3RecReader* rdr ) +{ + b3ShapeId recId = b3RecR_SHAPEID( rdr ); + b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); + b3ShapeId gotId = b3CreateCapsuleShape( bodyId, &a->def, &a->capsule ); + b3RecCheckShapeId( rdr, gotId, recId ); +} + +static void b3RecDispatch_CreateHullShape( const b3RecArgs_CreateHullShape* a, b3RecReader* rdr ) +{ + b3ShapeId recId = b3RecR_SHAPEID( rdr ); + if ( !rdr->ok ) + { + return; + } + uint32_t id = a->geometryId; + if ( id >= (uint32_t)rdr->slotCount ) + { + printf( "b3ReplayFile: hull geometryId %u out of range\n", id ); + rdr->ok = false; + return; + } + b3RegistrySlot* slot = rdr->slots + id; + b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); + // Hull is cloned by b3CreateHullShape into the world DB; no caching needed. + b3ShapeId gotId = b3CreateHullShape( bodyId, &a->def, (const b3HullData*)slot->bytes ); + b3RecCheckShapeId( rdr, gotId, recId ); +} + +static void b3RecDispatch_CreateMeshShape( const b3RecArgs_CreateMeshShape* a, b3RecReader* rdr ) +{ + b3ShapeId recId = b3RecR_SHAPEID( rdr ); + if ( !rdr->ok ) + { + return; + } + uint32_t id = a->geometryId; + if ( id >= (uint32_t)rdr->slotCount ) + { + printf( "b3ReplayFile: mesh geometryId %u out of range\n", id ); + rdr->ok = false; + return; + } + b3RegistrySlot* slot = rdr->slots + id; + const b3MeshData* mesh = (const b3MeshData*)b3RecGetLiveMesh( slot ); + b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); + b3ShapeId gotId = b3CreateMeshShape( bodyId, &a->def, mesh, a->scale ); + b3RecCheckShapeId( rdr, gotId, recId ); +} + +static void b3RecDispatch_CreateHeightFieldShape( const b3RecArgs_CreateHeightFieldShape* a, b3RecReader* rdr ) +{ + b3ShapeId recId = b3RecR_SHAPEID( rdr ); + if ( !rdr->ok ) + { + return; + } + uint32_t id = a->geometryId; + if ( id >= (uint32_t)rdr->slotCount ) + { + printf( "b3ReplayFile: heightfield geometryId %u out of range\n", id ); + rdr->ok = false; + return; + } + b3RegistrySlot* slot = rdr->slots + id; + const b3HeightFieldData* hf = (const b3HeightFieldData*)b3RecGetLiveHeightField( slot ); + if ( hf == NULL ) + { + printf( "b3ReplayFile: heightfield geometry %u is corrupt\n", id ); + rdr->ok = false; + return; + } + b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); + b3ShapeId gotId = b3CreateHeightFieldShape( bodyId, &a->def, hf ); + b3RecCheckShapeId( rdr, gotId, recId ); +} + +static void b3RecDispatch_CreateCompoundShape( const b3RecArgs_CreateCompoundShape* a, b3RecReader* rdr ) +{ + b3ShapeId recId = b3RecR_SHAPEID( rdr ); + if ( !rdr->ok ) + { + return; + } + uint32_t id = a->geometryId; + if ( id >= (uint32_t)rdr->slotCount ) + { + printf( "b3ReplayFile: compound geometryId %u out of range\n", id ); + rdr->ok = false; + return; + } + b3RegistrySlot* slot = rdr->slots + id; + const b3CompoundData* compound = (const b3CompoundData*)b3RecGetLiveCompound( slot ); + b3BodyId bodyId = b3RecMakeBodyId( rdr, a->body ); + // b3CreateCompoundShape takes a non-const def pointer; cast away const for the scratch def + b3ShapeDef shapeDef = a->def; + b3ShapeId gotId = b3CreateCompoundShape( bodyId, &shapeDef, compound ); + b3RecCheckShapeId( rdr, gotId, recId ); +} + +static void b3RecDispatch_DestroyShape( const b3RecArgs_DestroyShape* a, b3RecReader* rdr ) +{ + b3DestroyShape( b3RecMakeShapeId( rdr, a->shape ), a->updateBodyMass ); +} + +static void b3RecDispatch_ShapeSetName( const b3RecArgs_ShapeSetName* a, b3RecReader* rdr ) +{ + b3Shape_SetName( b3RecMakeShapeId( rdr, a->shape ), a->name ); +} + +static void b3RecDispatch_ShapeSetDensity( const b3RecArgs_ShapeSetDensity* a, b3RecReader* rdr ) +{ + b3Shape_SetDensity( b3RecMakeShapeId( rdr, a->shape ), a->density, a->updateBodyMass ); +} + +static void b3RecDispatch_ShapeSetFriction( const b3RecArgs_ShapeSetFriction* a, b3RecReader* rdr ) +{ + b3Shape_SetFriction( b3RecMakeShapeId( rdr, a->shape ), a->friction ); +} + +static void b3RecDispatch_ShapeSetRestitution( const b3RecArgs_ShapeSetRestitution* a, b3RecReader* rdr ) +{ + b3Shape_SetRestitution( b3RecMakeShapeId( rdr, a->shape ), a->restitution ); +} + +static void b3RecDispatch_ShapeSetSurfaceMaterial( const b3RecArgs_ShapeSetSurfaceMaterial* a, b3RecReader* rdr ) +{ + b3Shape_SetSurfaceMaterial( b3RecMakeShapeId( rdr, a->shape ), a->material ); +} + +static void b3RecDispatch_ShapeSetFilter( const b3RecArgs_ShapeSetFilter* a, b3RecReader* rdr ) +{ + b3Shape_SetFilter( b3RecMakeShapeId( rdr, a->shape ), a->filter, a->invokeContacts ); +} + +static void b3RecDispatch_ShapeEnableSensorEvents( const b3RecArgs_ShapeEnableSensorEvents* a, b3RecReader* rdr ) +{ + b3Shape_EnableSensorEvents( b3RecMakeShapeId( rdr, a->shape ), a->flag ); +} + +static void b3RecDispatch_ShapeEnableContactEvents( const b3RecArgs_ShapeEnableContactEvents* a, b3RecReader* rdr ) +{ + b3Shape_EnableContactEvents( b3RecMakeShapeId( rdr, a->shape ), a->flag ); +} + +static void b3RecDispatch_ShapeEnablePreSolveEvents( const b3RecArgs_ShapeEnablePreSolveEvents* a, b3RecReader* rdr ) +{ + b3Shape_EnablePreSolveEvents( b3RecMakeShapeId( rdr, a->shape ), a->flag ); +} + +static void b3RecDispatch_ShapeEnableHitEvents( const b3RecArgs_ShapeEnableHitEvents* a, b3RecReader* rdr ) +{ + b3Shape_EnableHitEvents( b3RecMakeShapeId( rdr, a->shape ), a->flag ); +} + +static void b3RecDispatch_ShapeSetSphere( const b3RecArgs_ShapeSetSphere* a, b3RecReader* rdr ) +{ + b3Shape_SetSphere( b3RecMakeShapeId( rdr, a->shape ), &a->sphere ); +} + +static void b3RecDispatch_ShapeSetCapsule( const b3RecArgs_ShapeSetCapsule* a, b3RecReader* rdr ) +{ + b3Shape_SetCapsule( b3RecMakeShapeId( rdr, a->shape ), &a->capsule ); +} + +static void b3RecDispatch_ShapeApplyWind( const b3RecArgs_ShapeApplyWind* a, b3RecReader* rdr ) +{ + b3Shape_ApplyWind( b3RecMakeShapeId( rdr, a->shape ), a->wind, a->drag, a->lift, a->maxSpeed, a->wake ); +} + +// Joint creates: remap body ids in the def before calling the API. + +static void b3RecDispatch_CreateParallelJoint( const b3RecArgs_CreateParallelJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3ParallelJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateParallelJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateDistanceJoint( const b3RecArgs_CreateDistanceJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3DistanceJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateDistanceJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateFilterJoint( const b3RecArgs_CreateFilterJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3FilterJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateFilterJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateMotorJoint( const b3RecArgs_CreateMotorJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3MotorJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateMotorJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreatePrismaticJoint( const b3RecArgs_CreatePrismaticJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3PrismaticJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreatePrismaticJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateRevoluteJoint( const b3RecArgs_CreateRevoluteJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3RevoluteJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateRevoluteJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateSphericalJoint( const b3RecArgs_CreateSphericalJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3SphericalJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateSphericalJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateWeldJoint( const b3RecArgs_CreateWeldJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3WeldJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateWeldJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_CreateWheelJoint( const b3RecArgs_CreateWheelJoint* a, b3RecReader* rdr ) +{ + b3JointId recId = b3RecR_JOINTID( rdr ); + b3WheelJointDef def = a->def; + def.base.bodyIdA = b3RecMakeBodyId( rdr, def.base.bodyIdA ); + def.base.bodyIdB = b3RecMakeBodyId( rdr, def.base.bodyIdB ); + b3RecCheckJointId( rdr, b3CreateWheelJoint( rdr->replayWorldId, &def ), recId ); +} + +static void b3RecDispatch_DestroyJoint( const b3RecArgs_DestroyJoint* a, b3RecReader* rdr ) +{ + b3DestroyJoint( b3RecMakeJointId( rdr, a->joint ), a->wakeAttached ); +} + +static void b3RecDispatch_JointSetLocalFrameA( const b3RecArgs_JointSetLocalFrameA* a, b3RecReader* rdr ) +{ + b3Joint_SetLocalFrameA( b3RecMakeJointId( rdr, a->joint ), a->localFrame ); +} + +static void b3RecDispatch_JointSetLocalFrameB( const b3RecArgs_JointSetLocalFrameB* a, b3RecReader* rdr ) +{ + b3Joint_SetLocalFrameB( b3RecMakeJointId( rdr, a->joint ), a->localFrame ); +} + +static void b3RecDispatch_JointSetCollideConnected( const b3RecArgs_JointSetCollideConnected* a, b3RecReader* rdr ) +{ + b3Joint_SetCollideConnected( b3RecMakeJointId( rdr, a->joint ), a->shouldCollide ); +} + +static void b3RecDispatch_JointWakeBodies( const b3RecArgs_JointWakeBodies* a, b3RecReader* rdr ) +{ + b3Joint_WakeBodies( b3RecMakeJointId( rdr, a->joint ) ); +} + +static void b3RecDispatch_JointSetConstraintTuning( const b3RecArgs_JointSetConstraintTuning* a, b3RecReader* rdr ) +{ + b3Joint_SetConstraintTuning( b3RecMakeJointId( rdr, a->joint ), a->hertz, a->dampingRatio ); +} + +static void b3RecDispatch_JointSetForceThreshold( const b3RecArgs_JointSetForceThreshold* a, b3RecReader* rdr ) +{ + b3Joint_SetForceThreshold( b3RecMakeJointId( rdr, a->joint ), a->threshold ); +} + +static void b3RecDispatch_JointSetTorqueThreshold( const b3RecArgs_JointSetTorqueThreshold* a, b3RecReader* rdr ) +{ + b3Joint_SetTorqueThreshold( b3RecMakeJointId( rdr, a->joint ), a->threshold ); +} + +static void b3RecDispatch_ParallelJointSetSpringHertz( const b3RecArgs_ParallelJointSetSpringHertz* a, b3RecReader* rdr ) +{ + b3ParallelJoint_SetSpringHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_ParallelJointSetSpringDampingRatio( const b3RecArgs_ParallelJointSetSpringDampingRatio* a, + b3RecReader* rdr ) +{ + b3ParallelJoint_SetSpringDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_ParallelJointSetMaxTorque( const b3RecArgs_ParallelJointSetMaxTorque* a, b3RecReader* rdr ) +{ + b3ParallelJoint_SetMaxTorque( b3RecMakeJointId( rdr, a->joint ), a->maxTorque ); +} + +static void b3RecDispatch_DistanceJointSetLength( const b3RecArgs_DistanceJointSetLength* a, b3RecReader* rdr ) +{ + b3DistanceJoint_SetLength( b3RecMakeJointId( rdr, a->joint ), a->length ); +} + +static void b3RecDispatch_DistanceJointEnableSpring( const b3RecArgs_DistanceJointEnableSpring* a, b3RecReader* rdr ) +{ + b3DistanceJoint_EnableSpring( b3RecMakeJointId( rdr, a->joint ), a->enableSpring ); +} + +static void b3RecDispatch_DistanceJointSetSpringForceRange( const b3RecArgs_DistanceJointSetSpringForceRange* a, + b3RecReader* rdr ) +{ + b3DistanceJoint_SetSpringForceRange( b3RecMakeJointId( rdr, a->joint ), a->lowerForce, a->upperForce ); +} + +static void b3RecDispatch_DistanceJointSetSpringHertz( const b3RecArgs_DistanceJointSetSpringHertz* a, b3RecReader* rdr ) +{ + b3DistanceJoint_SetSpringHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_DistanceJointSetSpringDampingRatio( const b3RecArgs_DistanceJointSetSpringDampingRatio* a, + b3RecReader* rdr ) +{ + b3DistanceJoint_SetSpringDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_DistanceJointEnableLimit( const b3RecArgs_DistanceJointEnableLimit* a, b3RecReader* rdr ) +{ + b3DistanceJoint_EnableLimit( b3RecMakeJointId( rdr, a->joint ), a->enableLimit ); +} + +static void b3RecDispatch_DistanceJointSetLengthRange( const b3RecArgs_DistanceJointSetLengthRange* a, b3RecReader* rdr ) +{ + b3DistanceJoint_SetLengthRange( b3RecMakeJointId( rdr, a->joint ), a->minLength, a->maxLength ); +} + +static void b3RecDispatch_DistanceJointEnableMotor( const b3RecArgs_DistanceJointEnableMotor* a, b3RecReader* rdr ) +{ + b3DistanceJoint_EnableMotor( b3RecMakeJointId( rdr, a->joint ), a->enableMotor ); +} + +static void b3RecDispatch_DistanceJointSetMotorSpeed( const b3RecArgs_DistanceJointSetMotorSpeed* a, b3RecReader* rdr ) +{ + b3DistanceJoint_SetMotorSpeed( b3RecMakeJointId( rdr, a->joint ), a->motorSpeed ); +} + +static void b3RecDispatch_DistanceJointSetMaxMotorForce( const b3RecArgs_DistanceJointSetMaxMotorForce* a, b3RecReader* rdr ) +{ + b3DistanceJoint_SetMaxMotorForce( b3RecMakeJointId( rdr, a->joint ), a->force ); +} + +static void b3RecDispatch_MotorJointSetLinearVelocity( const b3RecArgs_MotorJointSetLinearVelocity* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetLinearVelocity( b3RecMakeJointId( rdr, a->joint ), a->velocity ); +} + +static void b3RecDispatch_MotorJointSetAngularVelocity( const b3RecArgs_MotorJointSetAngularVelocity* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetAngularVelocity( b3RecMakeJointId( rdr, a->joint ), a->velocity ); +} + +static void b3RecDispatch_MotorJointSetMaxVelocityForce( const b3RecArgs_MotorJointSetMaxVelocityForce* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetMaxVelocityForce( b3RecMakeJointId( rdr, a->joint ), a->maxForce ); +} + +static void b3RecDispatch_MotorJointSetMaxVelocityTorque( const b3RecArgs_MotorJointSetMaxVelocityTorque* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetMaxVelocityTorque( b3RecMakeJointId( rdr, a->joint ), a->maxTorque ); +} + +static void b3RecDispatch_MotorJointSetLinearHertz( const b3RecArgs_MotorJointSetLinearHertz* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetLinearHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_MotorJointSetLinearDampingRatio( const b3RecArgs_MotorJointSetLinearDampingRatio* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetLinearDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->damping ); +} + +static void b3RecDispatch_MotorJointSetAngularHertz( const b3RecArgs_MotorJointSetAngularHertz* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetAngularHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_MotorJointSetAngularDampingRatio( const b3RecArgs_MotorJointSetAngularDampingRatio* a, + b3RecReader* rdr ) +{ + b3MotorJoint_SetAngularDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->damping ); +} + +static void b3RecDispatch_MotorJointSetMaxSpringForce( const b3RecArgs_MotorJointSetMaxSpringForce* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetMaxSpringForce( b3RecMakeJointId( rdr, a->joint ), a->maxForce ); +} + +static void b3RecDispatch_MotorJointSetMaxSpringTorque( const b3RecArgs_MotorJointSetMaxSpringTorque* a, b3RecReader* rdr ) +{ + b3MotorJoint_SetMaxSpringTorque( b3RecMakeJointId( rdr, a->joint ), a->maxTorque ); +} + +static void b3RecDispatch_PrismaticJointEnableSpring( const b3RecArgs_PrismaticJointEnableSpring* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_EnableSpring( b3RecMakeJointId( rdr, a->joint ), a->enableSpring ); +} + +static void b3RecDispatch_PrismaticJointSetSpringHertz( const b3RecArgs_PrismaticJointSetSpringHertz* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_SetSpringHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_PrismaticJointSetSpringDampingRatio( const b3RecArgs_PrismaticJointSetSpringDampingRatio* a, + b3RecReader* rdr ) +{ + b3PrismaticJoint_SetSpringDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_PrismaticJointSetTargetTranslation( const b3RecArgs_PrismaticJointSetTargetTranslation* a, + b3RecReader* rdr ) +{ + b3PrismaticJoint_SetTargetTranslation( b3RecMakeJointId( rdr, a->joint ), a->translation ); +} + +static void b3RecDispatch_PrismaticJointEnableLimit( const b3RecArgs_PrismaticJointEnableLimit* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_EnableLimit( b3RecMakeJointId( rdr, a->joint ), a->enableLimit ); +} + +static void b3RecDispatch_PrismaticJointSetLimits( const b3RecArgs_PrismaticJointSetLimits* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_SetLimits( b3RecMakeJointId( rdr, a->joint ), a->lower, a->upper ); +} + +static void b3RecDispatch_PrismaticJointEnableMotor( const b3RecArgs_PrismaticJointEnableMotor* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_EnableMotor( b3RecMakeJointId( rdr, a->joint ), a->enableMotor ); +} + +static void b3RecDispatch_PrismaticJointSetMotorSpeed( const b3RecArgs_PrismaticJointSetMotorSpeed* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_SetMotorSpeed( b3RecMakeJointId( rdr, a->joint ), a->motorSpeed ); +} + +static void b3RecDispatch_PrismaticJointSetMaxMotorForce( const b3RecArgs_PrismaticJointSetMaxMotorForce* a, b3RecReader* rdr ) +{ + b3PrismaticJoint_SetMaxMotorForce( b3RecMakeJointId( rdr, a->joint ), a->force ); +} + +static void b3RecDispatch_RevoluteJointEnableSpring( const b3RecArgs_RevoluteJointEnableSpring* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_EnableSpring( b3RecMakeJointId( rdr, a->joint ), a->enableSpring ); +} + +static void b3RecDispatch_RevoluteJointSetSpringHertz( const b3RecArgs_RevoluteJointSetSpringHertz* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_SetSpringHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_RevoluteJointSetSpringDampingRatio( const b3RecArgs_RevoluteJointSetSpringDampingRatio* a, + b3RecReader* rdr ) +{ + b3RevoluteJoint_SetSpringDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_RevoluteJointSetTargetAngle( const b3RecArgs_RevoluteJointSetTargetAngle* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_SetTargetAngle( b3RecMakeJointId( rdr, a->joint ), a->angle ); +} + +static void b3RecDispatch_RevoluteJointEnableLimit( const b3RecArgs_RevoluteJointEnableLimit* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_EnableLimit( b3RecMakeJointId( rdr, a->joint ), a->enableLimit ); +} + +static void b3RecDispatch_RevoluteJointSetLimits( const b3RecArgs_RevoluteJointSetLimits* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_SetLimits( b3RecMakeJointId( rdr, a->joint ), a->lower, a->upper ); +} + +static void b3RecDispatch_RevoluteJointEnableMotor( const b3RecArgs_RevoluteJointEnableMotor* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_EnableMotor( b3RecMakeJointId( rdr, a->joint ), a->enableMotor ); +} + +static void b3RecDispatch_RevoluteJointSetMotorSpeed( const b3RecArgs_RevoluteJointSetMotorSpeed* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_SetMotorSpeed( b3RecMakeJointId( rdr, a->joint ), a->motorSpeed ); +} + +static void b3RecDispatch_RevoluteJointSetMaxMotorTorque( const b3RecArgs_RevoluteJointSetMaxMotorTorque* a, b3RecReader* rdr ) +{ + b3RevoluteJoint_SetMaxMotorTorque( b3RecMakeJointId( rdr, a->joint ), a->torque ); +} + +static void b3RecDispatch_SphericalJointEnableConeLimit( const b3RecArgs_SphericalJointEnableConeLimit* a, b3RecReader* rdr ) +{ + b3SphericalJoint_EnableConeLimit( b3RecMakeJointId( rdr, a->joint ), a->enableLimit ); +} + +static void b3RecDispatch_SphericalJointSetConeLimit( const b3RecArgs_SphericalJointSetConeLimit* a, b3RecReader* rdr ) +{ + b3SphericalJoint_SetConeLimit( b3RecMakeJointId( rdr, a->joint ), a->angleRadians ); +} + +static void b3RecDispatch_SphericalJointEnableTwistLimit( const b3RecArgs_SphericalJointEnableTwistLimit* a, b3RecReader* rdr ) +{ + b3SphericalJoint_EnableTwistLimit( b3RecMakeJointId( rdr, a->joint ), a->enableLimit ); +} + +static void b3RecDispatch_SphericalJointSetTwistLimits( const b3RecArgs_SphericalJointSetTwistLimits* a, b3RecReader* rdr ) +{ + b3SphericalJoint_SetTwistLimits( b3RecMakeJointId( rdr, a->joint ), a->lower, a->upper ); +} + +static void b3RecDispatch_SphericalJointEnableSpring( const b3RecArgs_SphericalJointEnableSpring* a, b3RecReader* rdr ) +{ + b3SphericalJoint_EnableSpring( b3RecMakeJointId( rdr, a->joint ), a->enableSpring ); +} + +static void b3RecDispatch_SphericalJointSetSpringHertz( const b3RecArgs_SphericalJointSetSpringHertz* a, b3RecReader* rdr ) +{ + b3SphericalJoint_SetSpringHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_SphericalJointSetSpringDampingRatio( const b3RecArgs_SphericalJointSetSpringDampingRatio* a, + b3RecReader* rdr ) +{ + b3SphericalJoint_SetSpringDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_SphericalJointSetTargetRotation( const b3RecArgs_SphericalJointSetTargetRotation* a, b3RecReader* rdr ) +{ + b3SphericalJoint_SetTargetRotation( b3RecMakeJointId( rdr, a->joint ), a->targetRotation ); +} + +static void b3RecDispatch_SphericalJointEnableMotor( const b3RecArgs_SphericalJointEnableMotor* a, b3RecReader* rdr ) +{ + b3SphericalJoint_EnableMotor( b3RecMakeJointId( rdr, a->joint ), a->enableMotor ); +} + +static void b3RecDispatch_SphericalJointSetMotorVelocity( const b3RecArgs_SphericalJointSetMotorVelocity* a, b3RecReader* rdr ) +{ + b3SphericalJoint_SetMotorVelocity( b3RecMakeJointId( rdr, a->joint ), a->motorVelocity ); +} + +static void b3RecDispatch_SphericalJointSetMaxMotorTorque( const b3RecArgs_SphericalJointSetMaxMotorTorque* a, b3RecReader* rdr ) +{ + b3SphericalJoint_SetMaxMotorTorque( b3RecMakeJointId( rdr, a->joint ), a->torque ); +} + +static void b3RecDispatch_WeldJointSetLinearHertz( const b3RecArgs_WeldJointSetLinearHertz* a, b3RecReader* rdr ) +{ + b3WeldJoint_SetLinearHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_WeldJointSetLinearDampingRatio( const b3RecArgs_WeldJointSetLinearDampingRatio* a, b3RecReader* rdr ) +{ + b3WeldJoint_SetLinearDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_WeldJointSetAngularHertz( const b3RecArgs_WeldJointSetAngularHertz* a, b3RecReader* rdr ) +{ + b3WeldJoint_SetAngularHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_WeldJointSetAngularDampingRatio( const b3RecArgs_WeldJointSetAngularDampingRatio* a, b3RecReader* rdr ) +{ + b3WeldJoint_SetAngularDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_WheelJointEnableSuspension( const b3RecArgs_WheelJointEnableSuspension* a, b3RecReader* rdr ) +{ + b3WheelJoint_EnableSuspension( b3RecMakeJointId( rdr, a->joint ), a->flag ); +} + +static void b3RecDispatch_WheelJointSetSuspensionHertz( const b3RecArgs_WheelJointSetSuspensionHertz* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetSuspensionHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_WheelJointSetSuspensionDampingRatio( const b3RecArgs_WheelJointSetSuspensionDampingRatio* a, + b3RecReader* rdr ) +{ + b3WheelJoint_SetSuspensionDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_WheelJointEnableSuspensionLimit( const b3RecArgs_WheelJointEnableSuspensionLimit* a, b3RecReader* rdr ) +{ + b3WheelJoint_EnableSuspensionLimit( b3RecMakeJointId( rdr, a->joint ), a->flag ); +} + +static void b3RecDispatch_WheelJointSetSuspensionLimits( const b3RecArgs_WheelJointSetSuspensionLimits* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetSuspensionLimits( b3RecMakeJointId( rdr, a->joint ), a->lower, a->upper ); +} + +static void b3RecDispatch_WheelJointEnableSpinMotor( const b3RecArgs_WheelJointEnableSpinMotor* a, b3RecReader* rdr ) +{ + b3WheelJoint_EnableSpinMotor( b3RecMakeJointId( rdr, a->joint ), a->flag ); +} + +static void b3RecDispatch_WheelJointSetSpinMotorSpeed( const b3RecArgs_WheelJointSetSpinMotorSpeed* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetSpinMotorSpeed( b3RecMakeJointId( rdr, a->joint ), a->speed ); +} + +static void b3RecDispatch_WheelJointSetMaxSpinTorque( const b3RecArgs_WheelJointSetMaxSpinTorque* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetMaxSpinTorque( b3RecMakeJointId( rdr, a->joint ), a->torque ); +} + +static void b3RecDispatch_WheelJointEnableSteering( const b3RecArgs_WheelJointEnableSteering* a, b3RecReader* rdr ) +{ + b3WheelJoint_EnableSteering( b3RecMakeJointId( rdr, a->joint ), a->flag ); +} + +static void b3RecDispatch_WheelJointSetSteeringHertz( const b3RecArgs_WheelJointSetSteeringHertz* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetSteeringHertz( b3RecMakeJointId( rdr, a->joint ), a->hertz ); +} + +static void b3RecDispatch_WheelJointSetSteeringDampingRatio( const b3RecArgs_WheelJointSetSteeringDampingRatio* a, + b3RecReader* rdr ) +{ + b3WheelJoint_SetSteeringDampingRatio( b3RecMakeJointId( rdr, a->joint ), a->dampingRatio ); +} + +static void b3RecDispatch_WheelJointSetMaxSteeringTorque( const b3RecArgs_WheelJointSetMaxSteeringTorque* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetMaxSteeringTorque( b3RecMakeJointId( rdr, a->joint ), a->torque ); +} + +static void b3RecDispatch_WheelJointEnableSteeringLimit( const b3RecArgs_WheelJointEnableSteeringLimit* a, b3RecReader* rdr ) +{ + b3WheelJoint_EnableSteeringLimit( b3RecMakeJointId( rdr, a->joint ), a->flag ); +} + +static void b3RecDispatch_WheelJointSetSteeringLimits( const b3RecArgs_WheelJointSetSteeringLimits* a, b3RecReader* rdr ) +{ + b3WheelJoint_SetSteeringLimits( b3RecMakeJointId( rdr, a->joint ), a->lower, a->upper ); +} + +static void b3RecDispatch_WheelJointSetTargetSteeringAngle( const b3RecArgs_WheelJointSetTargetSteeringAngle* a, + b3RecReader* rdr ) +{ + b3WheelJoint_SetTargetSteeringAngle( b3RecMakeJointId( rdr, a->joint ), a->radians ); +} + +static void b3RecDispatch_StateHash( const b3RecArgs_StateHash* a, b3RecReader* rdr ) +{ + b3World* world = b3GetWorldFromId( rdr->replayWorldId ); + uint64_t computed = b3HashWorldState( world ); + if ( computed != a->hash ) + { + printf( "b3ReplayFile: StateHash mismatch (recorded=0x%" PRIx64 ", computed=0x%" PRIx64 ")\n", a->hash, computed ); + rdr->diverged = true; + } +} + +static void b3RecDispatch_RecordingBounds( const b3RecArgs_RecordingBounds* a, b3RecReader* rdr ) +{ + // Primary resolve is the open-time scan, this keeps the value right if it ever moves earlier + if ( rdr->owner != NULL ) + { + rdr->owner->bounds = a->bounds; + } +} + +// Spatial query replay. The recorded inputs come through the manifest; here the variable-length hit +// tail is read back, the query re-issued against the replay world, and each callback hit compared to +// what was recorded. Any mismatch latches rdr->diverged. When a player owns the reader the hits are +// also stashed for the viewer overlay. The stash helpers dereference the player struct, defined later +// in this file, so they are forward declared and implemented in Block B below. + +static void b3RecGrow( void** data, int* capacity, int need, int keep, int elemSize ); +static b3RecDrawQuery* b3RecStashQueryBegin( b3RecPlayer* player, int kind, const b3RecRecordedHit* hits, int hitCount ); + +// Grow the reader's hit scratch to at least n entries, preserving contents. n is bounded by the file +// size since every recorded hit consumes at least one byte, so a corrupt count fails the read. +void b3RecEnsureHits( b3RecReader* rdr, int n ) +{ + b3RecReserveScratch( rdr, (void**)&rdr->hits, &rdr->hitCap, n, (int)sizeof( b3RecRecordedHit ) ); +} + +// Bitwise float compare so the determinism check is exact, not within a tolerance. +static bool b3RecF32Differs( float a, float b ) +{ + uint32_t ua, ub; + memcpy( &ua, &a, 4 ); + memcpy( &ub, &b, 4 ); + return ua != ub; +} + +static bool b3RecVec3Differs( b3Vec3 a, b3Vec3 b ) +{ + return b3RecF32Differs( a.x, b.x ) || b3RecF32Differs( a.y, b.y ) || b3RecF32Differs( a.z, b.z ); +} + +// Shared context for the replay trampolines: walks recorded hits in order, flagging any divergence +// from the re-issued query. +typedef struct b3RecReplayQueryCtx +{ + b3RecReader* rdr; + const b3RecRecordedHit* hits; + int count; + int cursor; +} b3RecReplayQueryCtx; + +static bool b3RecReplayOverlapTrampoline( b3ShapeId id, void* ctx ) +{ + b3RecReplayQueryCtx* rc = ctx; + if ( rc->cursor >= rc->count ) + { + rc->rdr->diverged = true; + return false; + } + const b3RecRecordedHit* h = &rc->hits[rc->cursor++]; + if ( id.index1 != h->id.index1 || id.generation != h->id.generation ) + { + rc->rdr->diverged = true; + } + return h->userReturnB; +} + +// The mover filter has the same bool(shapeId, ctx) shape as an overlap callback, so it replays the +// same way. A distinct typed wrapper keeps the function-pointer types clean. +static bool b3RecReplayMoverFilterTrampoline( b3ShapeId id, void* ctx ) +{ + return b3RecReplayOverlapTrampoline( id, ctx ); +} + +static float b3RecReplayCastTrampoline( b3ShapeId id, b3Pos point, b3Vec3 normal, float fraction, uint64_t userMaterialId, + int triangleIndex, int childIndex, void* ctx ) +{ + b3RecReplayQueryCtx* rc = ctx; + if ( rc->cursor >= rc->count ) + { + rc->rdr->diverged = true; + return 0.0f; + } + const b3RecRecordedHit* h = &rc->hits[rc->cursor++]; + // Positions compared through a full-width delta, truncating both sides would pass vacuously far + // from the origin. + if ( id.index1 != h->id.index1 || id.generation != h->id.generation || + b3RecVec3Differs( b3SubPos( point, h->point ), b3Vec3_zero ) || b3RecVec3Differs( normal, h->normal ) || + b3RecF32Differs( fraction, h->fraction ) || userMaterialId != h->userMaterialId || triangleIndex != h->triangleIndex || + childIndex != h->childIndex ) + { + rc->rdr->diverged = true; + } + return h->userReturnF; +} + +// 3D delivers a whole shape's planes in one call. The recorded group starts at the cursor with its +// plane count and user return replicated on each hit, so compare the batch and advance by the +// recorded count to stay aligned with the stream even when the count itself diverged. +static bool b3RecReplayPlaneTrampoline( b3ShapeId id, const b3PlaneResult* planes, int planeCount, void* ctx ) +{ + b3RecReplayQueryCtx* rc = ctx; + if ( rc->cursor >= rc->count ) + { + rc->rdr->diverged = true; + return true; + } + const b3RecRecordedHit* head = &rc->hits[rc->cursor]; + int recordedCount = head->planeCount; + bool ret = head->userReturnB; + if ( id.index1 != head->id.index1 || id.generation != head->id.generation || recordedCount != planeCount ) + { + rc->rdr->diverged = true; + } + int n = recordedCount < planeCount ? recordedCount : planeCount; + for ( int i = 0; i < n; ++i ) + { + const b3RecRecordedHit* h = &rc->hits[rc->cursor + i]; + if ( b3RecVec3Differs( h->plane.plane.normal, planes[i].plane.normal ) || + b3RecF32Differs( h->plane.plane.offset, planes[i].plane.offset ) || + b3RecVec3Differs( h->plane.point, planes[i].point ) ) + { + rc->rdr->diverged = true; + } + } + rc->cursor += recordedCount; + return ret; +} + +// Copy a decoded proxy's points into a draw record so the overlay does not depend on reader scratch. +static void b3RecStashProxy( b3RecDrawQuery* q, const b3ShapeProxy* proxy ) +{ + int count = proxy->count; + if ( count > B3_MAX_SHAPE_CAST_POINTS ) + count = B3_MAX_SHAPE_CAST_POINTS; + q->proxyCount = count; + q->proxyRadius = proxy->radius; + for ( int i = 0; i < count; ++i ) + { + q->proxyPoints[i] = proxy->points[i]; + } +} + +// Tight world-space bounds of a query's swept geometry, so the viewer can frame any query and not +// just the overlap AABB. Mover and proxy points are origin relative. A cast sweeps the shape from the +// origin to origin plus translation. The overlap AABB is already a world-space box. +static void b3RecComputeQueryBounds( b3RecDrawQuery* q ) +{ + if ( q->kind == B3_RECQ_OVERLAP_AABB ) + { + return; + } + + // Shape points relative to the origin, plus the fattening radius. A ray has no shape, so it falls + // through to a single point at the origin. + b3Vec3 local[B3_MAX_SHAPE_CAST_POINTS]; + int count = 0; + float radius = 0.0f; + switch ( q->kind ) + { + case B3_RECQ_CAST_MOVER: + case B3_RECQ_COLLIDE_MOVER: + local[0] = q->mover.center1; + local[1] = q->mover.center2; + count = 2; + radius = q->mover.radius; + break; + + case B3_RECQ_OVERLAP_SHAPE: + case B3_RECQ_CAST_SHAPE: + count = q->proxyCount; + for ( int i = 0; i < count; ++i ) + { + local[i] = q->proxyPoints[i]; + } + radius = q->proxyRadius; + break; + + default: + break; + } + if ( count == 0 ) + { + local[0] = b3Vec3_zero; + count = 1; + } + + // Sweep each point across the translation. A non-cast query has zero translation, so both ends + // coincide and the duplicates fold away. + b3Pos end = b3OffsetPos( q->origin, q->translation ); + b3Vec3 world[2 * B3_MAX_SHAPE_CAST_POINTS]; + int n = 0; + for ( int i = 0; i < count; ++i ) + { + world[n++] = b3ToVec3( b3OffsetPos( q->origin, local[i] ) ); + world[n++] = b3ToVec3( b3OffsetPos( end, local[i] ) ); + } + q->aabb = b3MakeAABB( world, n, radius ); +} + +static void b3RecDispatch_QueryOverlapAABB( const b3RecArgs_QueryOverlapAABB* a, b3RecReader* rdr ) +{ + uint32_t n = b3RecR_U32( rdr ); + b3RecEnsureHits( rdr, (int)n ); + if ( !rdr->ok ) + return; + for ( uint32_t i = 0; i < n; ++i ) + { + rdr->hits[i].id = b3RecMakeShapeId( rdr, b3RecR_SHAPEID( rdr ) ); + rdr->hits[i].userReturnB = b3RecR_BOOL( rdr ); + } + (void)b3RecR_TREESTATS( rdr ); + if ( !rdr->ok ) + return; + b3RecReplayQueryCtx rc = { rdr, rdr->hits, (int)n, 0 }; + b3World_OverlapAABB( rdr->replayWorldId, a->aabb, a->filter, b3RecReplayOverlapTrampoline, &rc ); + if ( rc.cursor != (int)n ) + rdr->diverged = true; + if ( rdr->owner ) + { + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_OVERLAP_AABB, rdr->hits, (int)n ); + q->filter = a->filter; + q->aabb = a->aabb; + b3RecComputeQueryBounds( q ); + } +} + +static void b3RecDispatch_QueryOverlapShape( const b3RecArgs_QueryOverlapShape* a, b3RecReader* rdr ) +{ + uint32_t n = b3RecR_U32( rdr ); + b3RecEnsureHits( rdr, (int)n ); + if ( !rdr->ok ) + return; + for ( uint32_t i = 0; i < n; ++i ) + { + rdr->hits[i].id = b3RecMakeShapeId( rdr, b3RecR_SHAPEID( rdr ) ); + rdr->hits[i].userReturnB = b3RecR_BOOL( rdr ); + } + (void)b3RecR_TREESTATS( rdr ); + if ( !rdr->ok ) + return; + b3RecReplayQueryCtx rc = { rdr, rdr->hits, (int)n, 0 }; + b3World_OverlapShape( rdr->replayWorldId, a->origin, &a->proxy, a->filter, b3RecReplayOverlapTrampoline, &rc ); + if ( rc.cursor != (int)n ) + rdr->diverged = true; + if ( rdr->owner ) + { + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_OVERLAP_SHAPE, rdr->hits, (int)n ); + q->filter = a->filter; + q->origin = a->origin; + b3RecStashProxy( q, &a->proxy ); + b3RecComputeQueryBounds( q ); + } +} + +static void b3RecDispatch_QueryCastRay( const b3RecArgs_QueryCastRay* a, b3RecReader* rdr ) +{ + uint32_t n = b3RecR_U32( rdr ); + b3RecEnsureHits( rdr, (int)n ); + if ( !rdr->ok ) + return; + for ( uint32_t i = 0; i < n; ++i ) + { + rdr->hits[i].id = b3RecMakeShapeId( rdr, b3RecR_SHAPEID( rdr ) ); + rdr->hits[i].point = b3RecR_POSITION( rdr ); + rdr->hits[i].normal = b3RecR_VEC3( rdr ); + rdr->hits[i].fraction = b3RecR_F32( rdr ); + rdr->hits[i].userMaterialId = b3RecR_U64( rdr ); + rdr->hits[i].triangleIndex = b3RecR_I32( rdr ); + rdr->hits[i].childIndex = b3RecR_I32( rdr ); + rdr->hits[i].userReturnF = b3RecR_F32( rdr ); + } + (void)b3RecR_TREESTATS( rdr ); + if ( !rdr->ok ) + return; + b3RecReplayQueryCtx rc = { rdr, rdr->hits, (int)n, 0 }; + b3World_CastRay( rdr->replayWorldId, a->origin, a->translation, a->filter, b3RecReplayCastTrampoline, &rc ); + if ( rc.cursor != (int)n ) + rdr->diverged = true; + if ( rdr->owner ) + { + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_CAST_RAY, rdr->hits, (int)n ); + q->filter = a->filter; + q->origin = a->origin; + q->translation = a->translation; + b3RecComputeQueryBounds( q ); + } +} + +static void b3RecDispatch_QueryCastShape( const b3RecArgs_QueryCastShape* a, b3RecReader* rdr ) +{ + uint32_t n = b3RecR_U32( rdr ); + b3RecEnsureHits( rdr, (int)n ); + if ( !rdr->ok ) + return; + for ( uint32_t i = 0; i < n; ++i ) + { + rdr->hits[i].id = b3RecMakeShapeId( rdr, b3RecR_SHAPEID( rdr ) ); + rdr->hits[i].point = b3RecR_POSITION( rdr ); + rdr->hits[i].normal = b3RecR_VEC3( rdr ); + rdr->hits[i].fraction = b3RecR_F32( rdr ); + rdr->hits[i].userMaterialId = b3RecR_U64( rdr ); + rdr->hits[i].triangleIndex = b3RecR_I32( rdr ); + rdr->hits[i].childIndex = b3RecR_I32( rdr ); + rdr->hits[i].userReturnF = b3RecR_F32( rdr ); + } + (void)b3RecR_TREESTATS( rdr ); + if ( !rdr->ok ) + return; + b3RecReplayQueryCtx rc = { rdr, rdr->hits, (int)n, 0 }; + b3World_CastShape( rdr->replayWorldId, a->origin, &a->proxy, a->translation, a->filter, b3RecReplayCastTrampoline, &rc ); + if ( rc.cursor != (int)n ) + rdr->diverged = true; + if ( rdr->owner ) + { + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_CAST_SHAPE, rdr->hits, (int)n ); + q->filter = a->filter; + q->origin = a->origin; + q->translation = a->translation; + b3RecStashProxy( q, &a->proxy ); + b3RecComputeQueryBounds( q ); + } +} + +static void b3RecDispatch_QueryCastRayClosest( const b3RecArgs_QueryCastRayClosest* a, b3RecReader* rdr ) +{ + b3RayResult rec = b3RecR_RAYRESULT( rdr ); + if ( !rdr->ok ) + return; + b3RayResult got = b3World_CastRayClosest( rdr->replayWorldId, a->origin, a->translation, a->filter ); + b3ShapeId recId = b3RecMakeShapeId( rdr, rec.shapeId ); + if ( got.hit != rec.hit || + ( got.hit && + ( got.shapeId.index1 != recId.index1 || got.shapeId.generation != recId.generation || + b3RecVec3Differs( b3SubPos( got.point, rec.point ), b3Vec3_zero ) || b3RecVec3Differs( got.normal, rec.normal ) || + b3RecF32Differs( got.fraction, rec.fraction ) || got.userMaterialId != rec.userMaterialId ) ) ) + { + rdr->diverged = true; + } + if ( rdr->owner ) + { + // Stash the closest result as a single pooled hit so the shared draw loop renders its point. + b3RecRecordedHit h = { 0 }; + h.id = recId; + h.point = rec.point; + h.normal = rec.normal; + h.fraction = rec.fraction; + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_CAST_RAY_CLOSEST, &h, rec.hit ? 1 : 0 ); + q->filter = a->filter; + q->origin = a->origin; + q->translation = a->translation; + q->rayResult = rec; + b3RecComputeQueryBounds( q ); + } +} + +static void b3RecDispatch_QueryCastMover( const b3RecArgs_QueryCastMover* a, b3RecReader* rdr ) +{ + uint32_t n = b3RecR_U32( rdr ); + b3RecEnsureHits( rdr, (int)n ); + if ( !rdr->ok ) + return; + for ( uint32_t i = 0; i < n; ++i ) + { + rdr->hits[i].id = b3RecMakeShapeId( rdr, b3RecR_SHAPEID( rdr ) ); + rdr->hits[i].userReturnB = b3RecR_BOOL( rdr ); + } + float recFraction = b3RecR_F32( rdr ); + if ( !rdr->ok ) + return; + b3RecReplayQueryCtx rc = { rdr, rdr->hits, (int)n, 0 }; + float got = b3World_CastMover( rdr->replayWorldId, a->origin, &a->mover, a->translation, a->filter, + b3RecReplayMoverFilterTrampoline, &rc ); + if ( rc.cursor != (int)n || b3RecF32Differs( got, recFraction ) ) + rdr->diverged = true; + if ( rdr->owner ) + { + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_CAST_MOVER, NULL, 0 ); + q->filter = a->filter; + q->origin = a->origin; + q->mover = a->mover; + q->translation = a->translation; + q->castFraction = recFraction; + b3RecComputeQueryBounds( q ); + } +} + +static void b3RecDispatch_QueryCollideMover( const b3RecArgs_QueryCollideMover* a, b3RecReader* rdr ) +{ + // Recorded as shapeCount groups, each: shapeId, planeCount, planeCount planes, user return. Flatten + // into one hit per plane with the group's count and return replicated, so the replay walker can + // re-group per shape. + uint32_t shapeCount = b3RecR_U32( rdr ); + int total = 0; + for ( uint32_t s = 0; s < shapeCount; ++s ) + { + b3ShapeId id = b3RecMakeShapeId( rdr, b3RecR_SHAPEID( rdr ) ); + int planeCount = b3RecR_I32( rdr ); + if ( planeCount < 0 ) + planeCount = 0; + b3RecEnsureHits( rdr, total + planeCount ); + if ( !rdr->ok ) + return; + for ( int i = 0; i < planeCount; ++i ) + { + rdr->hits[total + i].plane = b3RecR_PLANERESULT( rdr ); + } + bool ret = b3RecR_BOOL( rdr ); + for ( int i = 0; i < planeCount; ++i ) + { + rdr->hits[total + i].id = id; + rdr->hits[total + i].planeCount = planeCount; + rdr->hits[total + i].userReturnB = ret; + } + total += planeCount; + } + if ( !rdr->ok ) + return; + b3RecReplayQueryCtx rc = { rdr, rdr->hits, total, 0 }; + b3World_CollideMover( rdr->replayWorldId, a->origin, &a->mover, a->filter, b3RecReplayPlaneTrampoline, &rc ); + if ( rc.cursor != total ) + rdr->diverged = true; + if ( rdr->owner ) + { + b3RecDrawQuery* q = b3RecStashQueryBegin( rdr->owner, B3_RECQ_COLLIDE_MOVER, rdr->hits, total ); + q->filter = a->filter; + q->origin = a->origin; + q->mover = a->mover; + b3RecComputeQueryBounds( q ); + } +} + +// Stash the identity key of the query that immediately follows. Consumed by the next stash. +static void b3RecDispatch_QueryTag( const b3RecArgs_QueryTag* a, b3RecReader* rdr ) +{ + rdr->pendingQueryKey = a->key; +} + +// X-macro dispatch switch: read opcode+u24 payloadSize, dispatch, skip unknown ops. +// Returns the opcode dispatched, or -1 when the stream is exhausted or broken. + +static int b3RecDispatchOne( b3RecReader* rdr ) +{ + if ( rdr->cursor >= rdr->size || !rdr->ok ) + { + return -1; + } + uint8_t opcode = b3RecR_U8( rdr ); + uint32_t payloadSize = b3RecR_U24( rdr ); + if ( !rdr->ok ) + { + return -1; + } + + int payloadStart = rdr->cursor; + + switch ( opcode ) + { +#define ARG( TAG, field ) a.field = b3RecR_##TAG( rdr ); +#define B3_REC_OP( op, Name, RET, ... ) \ + case op: \ + { \ + b3RecArgs_##Name a; \ + memset( &a, 0, sizeof( a ) ); \ + __VA_ARGS__ \ + if ( rdr->ok ) \ + { \ + b3RecDispatch_##Name( &a, rdr ); \ + } \ + break; \ + } +#include "recording_ops.inl" +#undef B3_REC_OP +#undef ARG + default: + printf( "b3ReplayFile: unknown opcode 0x%02X, skipping %u bytes\n", opcode, payloadSize ); + // payloadStart is in bounds, so size - payloadStart is the bytes left to skip over + if ( payloadSize > (uint32_t)( rdr->size - payloadStart ) ) + { + rdr->ok = false; + } + else + { + rdr->cursor = payloadStart + (int)payloadSize; + } + break; + } + return (int)(unsigned)opcode; +} + +// Public entry point + +bool b3ValidateReplay( const void* data, int size, int workerCount ) +{ + b3RecPlayer* player = b3RecPlayer_Create( data, size, workerCount ); + if ( player == NULL ) + { + return false; + } + + while ( b3RecPlayer_StepFrame( player ) ) + { + if ( player->rdr.diverged ) + { + break; + } + } + + bool ok = player->rdr.ok && player->rdr.diverged == false; + b3RecPlayer_Destroy( player ); + return ok; +} + +// b3RecPlayer implementation + +#define B3_REC_KEYFRAME_INTERVAL_DEFAULT 16 +#define B3_REC_KEYFRAME_BUDGET_DEFAULT ( (size_t)512 * 1024 * 1024 ) + +// Overflow-safe growth for the player's accumulating arrays. Counts come from the replay itself, +// not the file, so this only guards the byte-size multiply. Preserves keep elements. +static void b3RecGrow( void** data, int* capacity, int need, int keep, int elemSize ) +{ + if ( need <= *capacity ) + { + return; + } + int newCap = *capacity == 0 ? 8 : 2 * *capacity; + if ( newCap < need ) + { + newCap = need; + } + void* grown = b3Alloc( (size_t)newCap * (size_t)elemSize ); + if ( *data != NULL ) + { + if ( keep > 0 ) + { + memcpy( grown, *data, (size_t)keep * (size_t)elemSize ); + } + b3Free( *data, (size_t)*capacity * (size_t)elemSize ); + } + *data = grown; + *capacity = newCap; +} + +// Block B: per-frame query store helpers. Forward declared above the query dispatchers, which run +// before the player struct is defined, so the player-dereferencing code lives here. + +static void b3RecGrowFrameQueries( b3RecPlayer* player ) +{ + b3RecGrow( (void**)&player->frameQueries, &player->frameQueryCap, player->frameQueryCount + 1, player->frameQueryCount, + (int)sizeof( b3RecDrawQuery ) ); +} + +static void b3RecGrowFrameHits( b3RecPlayer* player, int need ) +{ + b3RecGrow( (void**)&player->frameHits, &player->frameHitCap, player->frameHitCount + need, player->frameHitCount, + (int)sizeof( b3RecRecordedHit ) ); +} + +// Push a draw record for one query and copy its hits into the per-frame store. Ids in hits[] are +// already remapped to the replay world by the dispatcher. +static b3RecDrawQuery* b3RecStashQueryBegin( b3RecPlayer* player, int kind, const b3RecRecordedHit* hits, int hitCount ) +{ + b3RecGrowFrameQueries( player ); + b3RecDrawQuery* q = &player->frameQueries[player->frameQueryCount]; + memset( q, 0, sizeof( *q ) ); + q->kind = kind; + // Pair the query with the key from its preceding QueryTag op, if any, then clear it so the next + // untagged query reads 0. + q->key = player->rdr.pendingQueryKey; + player->rdr.pendingQueryKey = 0; + q->hitStart = player->frameHitCount; + q->hitCount = hitCount; + b3RecGrowFrameHits( player, hitCount ); + for ( int i = 0; i < hitCount; ++i ) + { + player->frameHits[player->frameHitCount + i] = hits[i]; + } + player->frameHitCount += hitCount; + player->frameQueryCount++; + return q; +} + +// Append a created body to the outliner list. Ordinals are creation order and never reused. +static void b3RecTrackBodyCreate( b3RecPlayer* player, b3BodyId id ) +{ + b3RecGrow( (void**)&player->bodyIds, &player->bodyIdCap, player->bodyIdCount + 1, player->bodyIdCount, + (int)sizeof( b3BodyId ) ); + player->bodyIds[player->bodyIdCount] = id; + player->bodyIdCount += 1; +} + +// Leave a hole so later ordinals do not shift, keeping a stored selection stable. +static void b3RecTrackBodyDestroy( b3RecPlayer* player, b3BodyId id ) +{ + for ( int i = 0; i < player->bodyIdCount; ++i ) + { + if ( B3_ID_EQUALS( player->bodyIds[i], id ) ) + { + player->bodyIds[i] = b3_nullBodyId; + return; + } + } +} + +// Snapshot bodies are restored as a struct image and never hit the CreateBody hook the tracker keys +// on, so the seed world must be walked once to populate the outliner list. Slot order is stable. +static void b3RecSeedBodyIds( b3RecPlayer* player ) +{ + b3World* world = b3GetWorldFromId( player->rdr.replayWorldId ); + player->bodyIdCount = 0; + int count = world->bodies.count; + for ( int i = 0; i < count; ++i ) + { + if ( world->bodies.data[i].id != i ) + { + continue; // free slot + } + b3RecTrackBodyCreate( player, b3MakeBodyId( world, i ) ); + } +} + +// Seed the outliner list from the current world and save it as the frame-0 restore copy. +static void b3RecSeedFrame0BodyIds( b3RecPlayer* player ) +{ + b3RecSeedBodyIds( player ); + if ( player->frame0BodyIds != NULL ) + { + b3Free( player->frame0BodyIds, (size_t)player->frame0BodyIdCount * sizeof( b3BodyId ) ); + player->frame0BodyIds = NULL; + } + player->frame0BodyIdCount = player->bodyIdCount; + if ( player->bodyIdCount > 0 ) + { + player->frame0BodyIds = (b3BodyId*)b3Alloc( (size_t)player->bodyIdCount * sizeof( b3BodyId ) ); + memcpy( player->frame0BodyIds, player->bodyIds, (size_t)player->bodyIdCount * sizeof( b3BodyId ) ); + } +} + +// Tag key to tag index, so the viewer resolves a query's caller id and label in O(1) instead of a +// linear scan over the tag table. +#define NAME b3RecTagLookup +#define KEY_TY uint64_t +#define VAL_TY uint32_t +#define HASH_FN vt_hash_integer +#define CMPR_FN vt_cmpr_integer +#define MALLOC_FN b3Alloc +#define FREE_FN b3Free +#include "verstable.h" + +// Read the optional query-tag table trailing the geometry entries: u32 tagCount then per tag +// { u64 key, u64 id, u16 len, name bytes }. A recording written before the tag table leaves rp at +// dataEnd, so nothing loads. Bounds-checked; tagCount reflects only the tags that fully fit, so a +// truncated tail loads what it can and reports the real count. +static void b3RecLoadTags( b3RecReader* rdr, const uint8_t* rp, const uint8_t* dataEnd ) +{ + b3RecReader sub = { 0 }; + sub.data = rp; + sub.size = (int)( dataEnd - rp ); + sub.ok = true; + + uint32_t count = b3RecR_U32( &sub ); + if ( sub.ok == false || count == 0 ) + { + return; + } + + // Each tag is at least 18 bytes (8 key + 8 id + 2 length). Reject a count that cannot fit the + // remaining bytes so a corrupt table cannot request a wild allocation. + if ( (size_t)count > (size_t)( sub.size - sub.cursor ) / 18 ) + { + return; + } + + b3RecTag* tags = (b3RecTag*)b3Alloc( (size_t)count * sizeof( b3RecTag ) ); + memset( tags, 0, (size_t)count * sizeof( b3RecTag ) ); + + b3RecTagLookup* map = (b3RecTagLookup*)b3Alloc( sizeof( b3RecTagLookup ) ); + b3RecTagLookup_init( map ); + + uint32_t loaded = 0; + for ( uint32_t i = 0; i < count; ++i ) + { + uint64_t key = b3RecR_U64( &sub ); + uint64_t id = b3RecR_U64( &sub ); + uint16_t len = b3RecR_U16( &sub ); + if ( sub.ok == false ) + { + break; + } + if ( len == 0xFFFFu ) + { + len = 0; // a null name is written as 0xFFFF + } + if ( (int64_t)sub.cursor + (int64_t)len > (int64_t)sub.size ) + { + break; + } + int n = len > B3_MAX_QUERY_NAME_LENGTH ? B3_MAX_QUERY_NAME_LENGTH : (int)len; + tags[loaded].key = key; + tags[loaded].id = id; + if ( n > 0 ) + { + memcpy( tags[loaded].queryName, sub.data + sub.cursor, (size_t)n ); + } + tags[loaded].queryName[n] = '\0'; + sub.cursor += len; + b3RecTagLookup_insert( map, key, loaded ); + loaded += 1; + } + + rdr->tags = tags; + rdr->tagCount = (int)loaded; + rdr->tagCapacity = (int)count; + rdr->tagMap = map; +} + +// Load the trailing registry block and fill rdr->slots/slotCount, then the optional tag table. +// Returns true on success. On failure sets rdr->ok = false and returns false. +static bool b3RecLoadSlots( b3RecReader* rdr, const void* data, int size, uint64_t registryOffset, uint64_t registryByteCount ) +{ + if ( registryOffset == 0 || registryByteCount == 0 ) + { + rdr->slots = NULL; + rdr->slotCount = 0; + return true; + } + + int regStart = (int)registryOffset; + int regEnd = regStart + (int)registryByteCount; + if ( regEnd > size ) + { + printf( "b3ReplayFile: registry block out of bounds\n" ); + return false; + } + if ( regStart + 4 > size ) + { + printf( "b3ReplayFile: registry too small\n" ); + return false; + } + + const uint8_t* dataEnd = (const uint8_t*)data + regEnd; + const uint8_t* rp = (const uint8_t*)data + regStart; + uint32_t count = (uint32_t)rp[0] | ( (uint32_t)rp[1] << 8 ) | ( (uint32_t)rp[2] << 16 ) | ( (uint32_t)rp[3] << 24 ); + rp += 4; + + if ( count == 0 ) + { + rdr->slots = NULL; + rdr->slotCount = 0; + b3RecLoadTags( rdr, rp, dataEnd ); + return true; + } + + // Each entry is at least 5 bytes (kind + 4-byte length). A count that cannot fit the remaining + // registry bytes is a corrupt header, so reject it before allocating. + if ( rp > dataEnd || (size_t)count > (size_t)( dataEnd - rp ) / 5 ) + { + printf( "b3ReplayFile: registry count out of range\n" ); + return false; + } + + b3RegistrySlot* slots = (b3RegistrySlot*)b3Alloc( (size_t)count * sizeof( b3RegistrySlot ) ); + memset( slots, 0, (size_t)count * sizeof( b3RegistrySlot ) ); + + for ( uint32_t i = 0; i < count; ++i ) + { + if ( rp + 5 > dataEnd ) + { + printf( "b3ReplayFile: registry truncated at entry %u\n", i ); + for ( uint32_t j = 0; j < i; ++j ) + { + if ( slots[j].bytes != NULL ) + { + b3Free( slots[j].bytes, (size_t)slots[j].byteCount ); + } + } + b3Free( slots, (size_t)count * sizeof( b3RegistrySlot ) ); + return false; + } + uint8_t kind = rp[0]; + uint32_t byteCount = (uint32_t)rp[1] | ( (uint32_t)rp[2] << 8 ) | ( (uint32_t)rp[3] << 16 ) | ( (uint32_t)rp[4] << 24 ); + rp += 5; + if ( rp + byteCount > dataEnd ) + { + printf( "b3ReplayFile: registry entry %u bytes out of bounds\n", i ); + for ( uint32_t j = 0; j < i; ++j ) + { + if ( slots[j].bytes != NULL ) + { + b3Free( slots[j].bytes, (size_t)slots[j].byteCount ); + } + } + b3Free( slots, (size_t)count * sizeof( b3RegistrySlot ) ); + return false; + } + uint8_t* bytes = (uint8_t*)b3Alloc( byteCount > 0 ? (size_t)byteCount : 1u ); + if ( byteCount > 0 ) + { + memcpy( bytes, rp, (size_t)byteCount ); + } + rp += byteCount; + slots[i].kind = (b3GeometryKind)kind; + slots[i].byteCount = (int)byteCount; + slots[i].bytes = bytes; + slots[i].live = NULL; + } + + rdr->slots = slots; + rdr->slotCount = (int)count; + b3RecLoadTags( rdr, rp, dataEnd ); + return true; +} + +// Free slots loaded by b3RecLoadSlots. +static void b3RecFreeSlots( b3RegistrySlot* slots, int slotCount ) +{ + if ( slots == NULL ) + { + return; + } + for ( int i = 0; i < slotCount; ++i ) + { + b3RegistrySlot* slot = slots + i; + if ( slot->live != NULL ) + { + switch ( slot->kind ) + { + // Mesh and height field have no separate live object; they borrow the bytes freed below. + case b3_geometryCompound: + b3Free( slot->live, (size_t)slot->byteCount ); + break; + default: + break; + } + } + if ( slot->bytes != NULL ) + { + b3Free( slot->bytes, slot->byteCount > 0 ? (size_t)slot->byteCount : 1u ); + } + } + b3Free( slots, (size_t)slotCount * sizeof( b3RegistrySlot ) ); +} + +// Walk the op stream once without dispatching: count Step ops and grab the first step's tuning. +static void b3RecScanFile( b3RecPlayer* player ) +{ + const uint8_t* data = player->data; + int size = player->registryEnd; + int cursor = player->headerEnd; + int frameCount = 0; + bool gotStep = false; + + while ( cursor + 4 <= size ) + { + uint8_t opcode = data[cursor]; + uint32_t payloadSize = + (uint32_t)data[cursor + 1] | ( (uint32_t)data[cursor + 2] << 8 ) | ( (uint32_t)data[cursor + 3] << 16 ); + int payloadStart = cursor + 4; + if ( payloadStart + (int)payloadSize > size ) + { + break; + } + if ( opcode == b3_recOpStep ) + { + frameCount += 1; + if ( !gotStep && payloadSize >= 12 ) + { + uint32_t dtBits = (uint32_t)data[payloadStart + 4] | ( (uint32_t)data[payloadStart + 5] << 8 ) | + ( (uint32_t)data[payloadStart + 6] << 16 ) | ( (uint32_t)data[payloadStart + 7] << 24 ); + memcpy( &player->recordedDt, &dtBits, 4 ); + player->recordedSubStepCount = + (int)( (uint32_t)data[payloadStart + 8] | ( (uint32_t)data[payloadStart + 9] << 8 ) | + ( (uint32_t)data[payloadStart + 10] << 16 ) | ( (uint32_t)data[payloadStart + 11] << 24 ) ); + gotStep = true; + } + } + else if ( opcode == 0xF2 && payloadSize >= (uint32_t)sizeof( b3AABB ) ) // RecordingBounds + { + // Payload is a single b3AABB (lower xyz, upper xyz as f32), written at stop so the + // viewer can frame the whole recorded motion without playing to the end. + memcpy( &player->bounds, data + payloadStart, sizeof( b3AABB ) ); + } + cursor = payloadStart + (int)payloadSize; + } + player->frameCount = frameCount; +} + +// Free one keyframe's heap. +static void b3FreeKeyframe( b3RecKeyframe* kf ) +{ + if ( kf->image != NULL ) + { + b3Free( kf->image, (size_t)kf->imageCapacity ); + } + if ( kf->bodyIds != NULL ) + { + b3Free( kf->bodyIds, (size_t)kf->bodyIdCount * sizeof( b3BodyId ) ); + } +} + +// Pre-populate keyframeRec's registry to mirror rdr.slots so geometry ids stay stable during +// b3SerializeWorld. Each slot becomes one entry with id == slot index, even byte-identical slots that +// a hash collision left undeduplicated in an already-recorded file. b3SerializeWorld then resolves a +// live blob back to a valid slot index via the registry's exact dedup, so capture never grows it. +static void b3RecSeedKeyframeRegistry( b3RecPlayer* player ) +{ + b3GeometryRegistry* reg = &player->keyframeRec->registry; + for ( int i = 0; i < player->rdr.slotCount; ++i ) + { + b3RegistrySlot* slot = player->rdr.slots + i; + // Copy so the registry can take ownership. + int n = slot->byteCount > 0 ? slot->byteCount : 1; + uint8_t* copy = (uint8_t*)b3Alloc( (size_t)n ); + if ( slot->byteCount > 0 ) + { + memcpy( copy, slot->bytes, (size_t)slot->byteCount ); + } + uint64_t h = b3Hash64Blob( slot->bytes, slot->byteCount ); + uint32_t id = b3AppendGeometry( reg, slot->kind, h, copy, slot->byteCount ); + // Seeding in order without dedup keeps id == slot index. + B3_ASSERT( id == (uint32_t)i ); + (void)id; + } +} + +// Capture a restore-point keyframe for the just-completed frame. rdr.cursor already points to +// the next frame's Step op. +static void b3RecCaptureKeyframe( b3RecPlayer* player ) +{ + b3World* world = b3GetWorldFromId( player->rdr.replayWorldId ); + b3RecBuffer buf = { 0 }; + + int regCountBefore = player->keyframeRec->registry.count; + B3_UNUSED( regCountBefore ); + + b3SerializeWorld( world, &buf, player->keyframeRec ); + // Registry must not grow: all geometry was pre-seeded and the registry dedups exactly. + B3_ASSERT( player->keyframeRec->registry.count == regCountBefore ); + + size_t bodyBytes = (size_t)player->bodyIdCount * sizeof( b3BodyId ); + size_t newBytes = (size_t)buf.capacity + bodyBytes; + + // Make room under the budget by doubling the spacing and evicting off-grid keyframes. + while ( player->keyframeCount > 0 && player->keyframeBytes + newBytes > player->keyframeBudget ) + { + player->keyframeInterval *= 2; + int kept = 0; + size_t keptBytes = 0; + for ( int i = 0; i < player->keyframeCount; ++i ) + { + b3RecKeyframe* kf = player->keyframes + i; + if ( kf->frame % player->keyframeInterval == 0 ) + { + player->keyframes[kept] = *kf; + keptBytes += (size_t)kf->imageCapacity + (size_t)kf->bodyIdCount * sizeof( b3BodyId ); + kept += 1; + } + else + { + b3FreeKeyframe( kf ); + } + } + bool progress = ( kept < player->keyframeCount ); + player->keyframeCount = kept; + player->keyframeBytes = keptBytes; + if ( !progress ) + { + break; + } + } + + // Grow the keyframe ring if needed. + if ( player->keyframeCount >= player->keyframeCapacity ) + { + int newCap = player->keyframeCapacity < 8 ? 8 : player->keyframeCapacity * 2; + player->keyframes = (b3RecKeyframe*)b3GrowAlloc( + player->keyframes, player->keyframeCapacity * (int)sizeof( b3RecKeyframe ), newCap * (int)sizeof( b3RecKeyframe ) ); + player->keyframeCapacity = newCap; + } + + b3RecKeyframe* kf = player->keyframes + player->keyframeCount; + kf->image = buf.data; + kf->imageSize = buf.size; + kf->imageCapacity = buf.capacity; + kf->frame = player->frame; + kf->cursor = player->rdr.cursor; + kf->divergeFrame = player->divergeFrame; + kf->diverged = player->rdr.diverged; + kf->bodyIdCount = player->bodyIdCount; + kf->bodyIds = NULL; + if ( bodyBytes > 0 ) + { + kf->bodyIds = (b3BodyId*)b3Alloc( bodyBytes ); + memcpy( kf->bodyIds, player->bodyIds, bodyBytes ); + } + + player->keyframeBytes += newBytes; + player->keyframeCount += 1; + player->lastKeyframeFrame = player->frame; +} + +// Restore the world in-place from a keyframe image. +static void b3RecPlayerRestoreKeyframe( b3RecPlayer* player, const b3RecKeyframe* kf ) +{ + b3World* world = b3GetWorldFromId( player->rdr.replayWorldId ); + if ( b3DeserializeIntoShell( kf->image, kf->imageSize, world, &player->rdr ) == false ) + { + player->rdr.ok = false; + return; + } + player->rdr.cursor = kf->cursor; + player->rdr.ok = true; + player->rdr.diverged = kf->diverged; + player->frame = kf->frame; + player->divergeFrame = kf->divergeFrame; + player->atEnd = false; + player->atPreStep = false; + + // Restore the outliner list verbatim so ordinals match this frame. + b3RecGrow( (void**)&player->bodyIds, &player->bodyIdCap, kf->bodyIdCount, 0, (int)sizeof( b3BodyId ) ); + player->bodyIdCount = kf->bodyIdCount; + if ( kf->bodyIdCount > 0 ) + { + memcpy( player->bodyIds, kf->bodyIds, (size_t)kf->bodyIdCount * sizeof( b3BodyId ) ); + } +} + +// Create a replay world carrying the host debug-shape callbacks. Every world the player +// stands up funnels through here so the sample renderer can draw replayed shapes. +static b3WorldId b3RecPlayerCreateWorld( const b3RecPlayer* player ) +{ + b3WorldDef worldDef = b3DefaultWorldDef(); + worldDef.createDebugShape = player->createDebugShape; + worldDef.destroyDebugShape = player->destroyDebugShape; + worldDef.userDebugShapeContext = player->debugShapeContext; + // Carry the requested worker count so a rebuild on Restart or backward seek keeps the same + // graph partitioning. Replaying at a different count than recorded is a determinism check. + worldDef.workerCount = b3MaxInt( 1, player->recordedWorkerCount ); + return b3CreateWorld( &worldDef ); +} + +b3RecPlayer* b3RecPlayer_Create( const void* data, int size, int workerCount ) +{ + if ( data == NULL || size < (int)sizeof( b3RecHeader ) ) + { + printf( "b3RecPlayer_Create: recording too small\n" ); + return NULL; + } + + b3RecHeader hdr; + memcpy( &hdr, data, sizeof( hdr ) ); + + if ( hdr.magic != B3_REC_MAGIC ) + { + printf( "b3RecPlayer_Create: bad magic 0x%08X\n", hdr.magic ); + return NULL; + } + // Only the major version is breaking. Minor bumps are additive op-stream changes that keep the + // header shape, and the dispatcher skips opcodes it doesn't know, so a minor mismatch still loads. + if ( hdr.versionMajor != B3_REC_VERSION_MAJOR ) + { + printf( "b3RecPlayer_Create: version mismatch %u.%u vs %u.%u\n", hdr.versionMajor, hdr.versionMinor, B3_REC_VERSION_MAJOR, + B3_REC_VERSION_MINOR ); + return NULL; + } + if ( hdr.pointerWidth != (uint8_t)sizeof( void* ) ) + { + printf( "b3RecPlayer_Create: pointer width mismatch %u vs %u\n", hdr.pointerWidth, (unsigned)sizeof( void* ) ); + return NULL; + } + if ( hdr.bigEndian != 0 ) + { + printf( "b3RecPlayer_Create: big-endian recording not supported\n" ); + return NULL; + } + + // Every recording is snapshot-seeded: the seed blob sits between the header and the op stream. + if ( hdr.snapshotSize == 0 ) + { + printf( "b3RecPlayer_Create: missing snapshot seed\n" ); + return NULL; + } + + // snapshotSize and registryOffset are 64-bit and come from the file. Validate in 64-bit so a + // hostile value can't wrap when narrowed to int, then narrow once the bounds are known good. + uint64_t headerEnd64 = (uint64_t)sizeof( b3RecHeader ) + hdr.snapshotSize; + uint64_t registryEnd64 = ( hdr.registryOffset != 0 ) ? hdr.registryOffset : (uint64_t)size; + + if ( headerEnd64 < sizeof( b3RecHeader ) || headerEnd64 > registryEnd64 || registryEnd64 > (uint64_t)size ) + { + printf( "b3RecPlayer_Create: corrupt offsets\n" ); + return NULL; + } + + int headerEnd = (int)headerEnd64; + int registryEnd = (int)registryEnd64; + + // Own a private copy so the caller can free their buffer right away. + uint8_t* copy = b3Alloc( (size_t)size ); + memcpy( copy, data, (size_t)size ); + + b3RecPlayer* player = b3Alloc( sizeof( b3RecPlayer ) ); + memset( player, 0, sizeof( b3RecPlayer ) ); + + player->data = copy; + player->size = size; + player->headerEnd = headerEnd; + player->registryEnd = registryEnd; + player->lengthScale = hdr.lengthScale; + player->previousLengthScale = b3GetLengthUnitsPerMeter(); + player->frame = 0; + player->frameCount = 0; + player->recordedDt = 0.0f; + player->recordedSubStepCount = 0; + player->recordedWorkerCount = workerCount; + player->atEnd = false; + player->atPreStep = false; + player->divergeFrame = -1; + player->keyframeMinInterval = B3_REC_KEYFRAME_INTERVAL_DEFAULT; + player->keyframeInterval = B3_REC_KEYFRAME_INTERVAL_DEFAULT; + player->keyframeBudget = B3_REC_KEYFRAME_BUDGET_DEFAULT; + player->lastKeyframeFrame = 0; + + // Set length scale so replay reproduces the same tuning constants. + if ( hdr.lengthScale > 0.0f ) + { + b3SetLengthUnitsPerMeter( hdr.lengthScale ); + } + + // Count frames and read first step's dt so the viewer can show hz up front. + b3RecScanFile( player ); + + // Create the replay world. Debug-shape callbacks are NULL here; the sample wires + // them right after Create via b3RecPlayer_SetDebugShapeCallbacks, which rebuilds. + b3WorldId worldId = b3RecPlayerCreateWorld( player ); + + // Initialize the reader. + player->rdr.data = copy; + player->rdr.size = size; + player->rdr.cursor = headerEnd; + player->rdr.replayWorldId = worldId; + player->rdr.ok = true; + player->rdr.diverged = false; + player->rdr.owner = player; + + // Load the trailing geometry registry. + if ( !b3RecLoadSlots( &player->rdr, copy, size, hdr.registryOffset, hdr.registryByteCount ) ) + { + b3DestroyWorld( worldId ); + b3Free( copy, (size_t)size ); + b3Free( player, sizeof( b3RecPlayer ) ); + return NULL; + } + + // Restore the seed snapshot to stand up the replay world. The blob doubles as the frame-0 + // restore image, owned by the copy held above. + { + int snapStart = (int)sizeof( b3RecHeader ); + int snapSize = (int)hdr.snapshotSize; + b3World* replayWorld = b3GetWorldFromId( worldId ); + if ( b3DeserializeIntoShell( copy + snapStart, snapSize, replayWorld, &player->rdr ) == false ) + { + printf( "b3RecPlayer_Create: snapshot deserialization failed\n" ); + b3DestroyWorld( worldId ); + b3RecFreeSlots( player->rdr.slots, player->rdr.slotCount ); + if ( player->rdr.tags != NULL ) + { + b3Free( player->rdr.tags, (size_t)player->rdr.tagCapacity * sizeof( b3RecTag ) ); + } + if ( player->rdr.tagMap != NULL ) + { + b3RecTagLookup_cleanup( (b3RecTagLookup*)player->rdr.tagMap ); + b3Free( player->rdr.tagMap, sizeof( b3RecTagLookup ) ); + } + b3Free( copy, (size_t)size ); + b3Free( player, sizeof( b3RecPlayer ) ); + return NULL; + } + player->rdr.cursor = headerEnd; + player->frame0Image = copy + snapStart; + player->frame0Size = snapSize; + } + + // Seed the outliner from the restored world (snapshot bodies bypass the create hook) and save + // the frame-0 restore copy. + b3RecSeedFrame0BodyIds( player ); + + // Build the keyframe recording with a pre-seeded registry that mirrors rdr.slots, + // so b3SerializeWorld geometry ids stay stable across captures. + player->keyframeRec = b3CreateRecording( 0 ); + b3RecSeedKeyframeRegistry( player ); + + return player; +} + +void b3RecPlayer_Destroy( b3RecPlayer* player ) +{ + if ( player == NULL ) + { + return; + } + + if ( b3World_IsValid( player->rdr.replayWorldId ) ) + { + b3DestroyWorld( player->rdr.replayWorldId ); + } + + // Free live geometry after destroying the world (slot->live may be used by the world). + b3RecFreeSlots( player->rdr.slots, player->rdr.slotCount ); + + // Free reader scratch. + if ( player->rdr.matScratch != NULL ) + { + b3Free( player->rdr.matScratch, (size_t)player->rdr.matScratchCap * sizeof( b3SurfaceMaterial ) ); + } + if ( player->rdr.proxyScratch != NULL ) + { + b3Free( player->rdr.proxyScratch, (size_t)player->rdr.proxyScratchCap * sizeof( b3Vec3 ) ); + } + if ( player->rdr.hits != NULL ) + { + b3Free( player->rdr.hits, (size_t)player->rdr.hitCap * sizeof( b3RecRecordedHit ) ); + } + if ( player->rdr.tags != NULL ) + { + b3Free( player->rdr.tags, (size_t)player->rdr.tagCapacity * sizeof( b3RecTag ) ); + } + if ( player->rdr.tagMap != NULL ) + { + b3RecTagLookup_cleanup( (b3RecTagLookup*)player->rdr.tagMap ); + b3Free( player->rdr.tagMap, sizeof( b3RecTagLookup ) ); + } + + // Free the per-frame query store. + if ( player->frameQueries != NULL ) + { + b3Free( player->frameQueries, (size_t)player->frameQueryCap * sizeof( b3RecDrawQuery ) ); + } + if ( player->frameHits != NULL ) + { + b3Free( player->frameHits, (size_t)player->frameHitCap * sizeof( b3RecRecordedHit ) ); + } + + // Free keyframe ring. + for ( int i = 0; i < player->keyframeCount; ++i ) + { + b3FreeKeyframe( player->keyframes + i ); + } + if ( player->keyframes != NULL ) + { + b3Free( player->keyframes, (size_t)player->keyframeCapacity * sizeof( b3RecKeyframe ) ); + } + + // The keyframe recording owns only its buffer and registry; b3DestroyRecording frees both. + if ( player->keyframeRec != NULL ) + { + b3DestroyRecording( player->keyframeRec ); + } + + // Free the outliner body lists. + if ( player->bodyIds != NULL ) + { + b3Free( player->bodyIds, (size_t)player->bodyIdCap * sizeof( b3BodyId ) ); + } + if ( player->frame0BodyIds != NULL ) + { + b3Free( player->frame0BodyIds, (size_t)player->frame0BodyIdCount * sizeof( b3BodyId ) ); + } + + // frame0Image points into the owned data copy, not separately allocated. + + b3Free( player->data, (size_t)player->size ); + + // Restore the global length scale. + b3SetLengthUnitsPerMeter( player->previousLengthScale ); + + b3Free( player, sizeof( b3RecPlayer ) ); +} + +bool b3RecPlayer_StepFrame( b3RecPlayer* player ) +{ + // This is never true when full stepping + player->atPreStep = false; + + if ( player->atEnd ) + { + return false; + } + + // Reset the per-frame query store before this frame's records are dispatched. + player->frameQueryCount = 0; + player->frameHitCount = 0; + + // A frame is its leading inputs (queries and between-step mutators), one Step, and the Step's + // trailing StateHash. Queries are recorded before the Step they belong to, so they stash here + // against the world state they were computed for. + bool stepped = false; + for ( ;; ) + { + if ( player->rdr.cursor >= player->registryEnd || !player->rdr.ok ) + { + player->atEnd = true; + return stepped; + } + + // Once stepped, the StateHash is the only record still belonging to this frame. Anything else + // begins the next frame, so stop and let the next StepFrame consume it. Capture a keyframe at + // the boundary. + if ( stepped && player->rdr.data[player->rdr.cursor] != b3_recOpStateHash ) + { + if ( player->frame > player->lastKeyframeFrame && player->frame % player->keyframeInterval == 0 ) + { + b3RecCaptureKeyframe( player ); + } + return true; + } + + int op = b3RecDispatchOne( &player->rdr ); + if ( op < 0 ) + { + player->atEnd = true; + return stepped; + } + if ( op == b3_recOpDestroyWorld ) // end of recording + { + player->atEnd = true; + return stepped; + } + if ( op == b3_recOpStep ) + { + player->frame += 1; + stepped = true; + } + else if ( op == b3_recOpStateHash ) // trailing record of the frame just stepped + { + // Latch the first frame whose state hash diverged. The hash belongs to the frame Step just + // advanced, so latch against the current frame, not the next Step which would be one late. + if ( player->divergeFrame < 0 && player->rdr.diverged ) + { + player->divergeFrame = player->frame; + } + } + } +} + +void b3RecPlayer_SubStepFrame( b3RecPlayer* player ) +{ + if ( player->atEnd ) + { + return; + } + + // Reset the per-frame query store before this frame's records are dispatched. + if ( player->atPreStep == false ) + { + player->frameQueryCount = 0; + player->frameHitCount = 0; + } + + // A frame is its leading inputs (queries and between-step mutators), one Step, and the Step's + // trailing StateHash. Queries are recorded before the Step they belong to, so they stash here + // against the world state they were computed for. + bool stepped = false; + bool haveCreateBodyOp = false; + for ( ;; ) + { + if ( player->rdr.cursor >= player->registryEnd || !player->rdr.ok ) + { + player->atEnd = true; + player->atPreStep = false; + return; + } + + // Once stepped, the StateHash is the only record still belonging to this frame. Anything else + // begins the next frame, so stop and let the next StepFrame consume it. Capture a keyframe at + // the boundary. + uint8_t currentOpCode = player->rdr.data[player->rdr.cursor]; + if ( stepped && currentOpCode != b3_recOpStateHash ) + { + if ( player->frame > player->lastKeyframeFrame && player->frame % player->keyframeInterval == 0 ) + { + b3RecCaptureKeyframe( player ); + } + return; + } + + if ( player->atPreStep == false && haveCreateBodyOp == true && currentOpCode == b3_recOpStep ) + { + player->atPreStep = true; + return; + } + + int op = b3RecDispatchOne( &player->rdr ); + if ( op < 0 ) + { + player->atEnd = true; + player->atPreStep = false; + return; + } + if ( op == b3_recOpDestroyWorld ) // end of recording + { + player->atEnd = true; + player->atPreStep = false; + return; + } + + if ( op == b3_recOpCreateBody ) + { + B3_ASSERT( player->atPreStep == false ); + haveCreateBodyOp = true; + } + + if ( op == b3_recOpStep ) + { + player->atPreStep = false; + player->frame += 1; + stepped = true; + } + else if ( op == b3_recOpStateHash ) // trailing record of the frame just stepped + { + // Latch the first frame whose state hash diverged. The hash belongs to the frame Step just + // advanced, so latch against the current frame, not the next Step which would be one late. + if ( player->divergeFrame < 0 && player->rdr.diverged ) + { + player->divergeFrame = player->frame; + } + } + } +} + +void b3RecPlayer_Restart( b3RecPlayer* player ) +{ + // Restore the frame-0 image in place so the replay world id stays stable across a restart or + // backward scrub. Stepping resumes at the first Step, which rebuilds the body list deterministically. + b3World* world = b3GetWorldFromId( player->rdr.replayWorldId ); + if ( b3DeserializeIntoShell( player->frame0Image, player->frame0Size, world, &player->rdr ) == false ) + { + player->rdr.ok = false; + return; + } + player->rdr.cursor = player->headerEnd; + player->rdr.ok = true; + player->rdr.diverged = false; + player->frame = 0; + player->divergeFrame = -1; + player->atEnd = false; + player->atPreStep = false; + + // Frame 0 is the pre-step snapshot with no recorded queries, so clear the per-frame store. This + // keeps the last stepped frame's queries from lingering on a restart or a backward scrub to 0. + player->frameQueryCount = 0; + player->frameHitCount = 0; + + // Roll the outliner body list back to its frame-0 contents. + b3RecGrow( (void**)&player->bodyIds, &player->bodyIdCap, player->frame0BodyIdCount, 0, (int)sizeof( b3BodyId ) ); + player->bodyIdCount = player->frame0BodyIdCount; + if ( player->frame0BodyIdCount > 0 ) + { + memcpy( player->bodyIds, player->frame0BodyIds, (size_t)player->frame0BodyIdCount * sizeof( b3BodyId ) ); + } +} + +void b3RecPlayer_SeekFrame( b3RecPlayer* player, int targetFrame ) +{ + if ( player == NULL ) + { + return; + } + + player->atPreStep = false; + + if ( targetFrame < 0 ) + { + targetFrame = 0; + } + + // Find the best keyframe strictly before the target. + const b3RecKeyframe* best = NULL; + for ( int i = 0; i < player->keyframeCount; ++i ) + { + const b3RecKeyframe* kf = player->keyframes + i; + if ( kf->frame < targetFrame && ( best == NULL || kf->frame > best->frame ) ) + { + best = kf; + } + } + + if ( targetFrame < player->frame ) + { + // Backward seek: restore keyframe or restart from frame 0. + if ( best != NULL ) + { + b3RecPlayerRestoreKeyframe( player, best ); + } + else + { + b3RecPlayer_Restart( player ); + } + } + else if ( best != NULL && best->frame > player->frame ) + { + // Forward seek that can skip ahead via a keyframe. + b3RecPlayerRestoreKeyframe( player, best ); + } + + while ( player->frame < targetFrame && b3RecPlayer_StepFrame( player ) ) + { + } +} + +b3WorldId b3RecPlayer_GetWorldId( const b3RecPlayer* player ) +{ + return player != NULL ? player->rdr.replayWorldId : b3_nullWorldId; +} + +int b3RecPlayer_GetFrame( const b3RecPlayer* player ) +{ + return player != NULL ? player->frame : 0; +} + +int b3RecPlayer_GetFrameCount( const b3RecPlayer* player ) +{ + return player != NULL ? player->frameCount : 0; +} + +bool b3RecPlayer_IsAtEnd( const b3RecPlayer* player ) +{ + return player != NULL ? player->atEnd : true; +} + +bool b3RecPlayer_IsAtPreStep( const b3RecPlayer* player ) +{ + return player != NULL ? player->atPreStep : false; +} + +bool b3RecPlayer_HasDiverged( const b3RecPlayer* player ) +{ + return player != NULL ? player->rdr.diverged : false; +} + +b3RecPlayerInfo b3RecPlayer_GetInfo( const b3RecPlayer* player ) +{ + b3RecPlayerInfo info = { 0 }; + if ( player != NULL ) + { + info.frameCount = player->frameCount; + info.workerCount = player->recordedWorkerCount; + info.timeStep = player->recordedDt; + info.subStepCount = player->recordedSubStepCount; + info.lengthScale = player->lengthScale; + info.bounds = player->bounds; + } + return info; +} + +int b3RecPlayer_GetDivergeFrame( const b3RecPlayer* player ) +{ + return player != NULL ? player->divergeFrame : -1; +} + +void b3RecPlayer_SetWorkerCount( b3RecPlayer* player, int count ) +{ + if ( player == NULL ) + { + return; + } + + player->recordedWorkerCount = b3ClampInt( count, 1, B3_MAX_WORKERS ); + + // Apply to the live world now so the next steps re-partition without a rebuild. Worker count is + // host state, not part of a keyframe image, so it survives an in-place restore. A rebuild on + // Restart or deep backward seek picks the count back up through b3RecPlayerCreateWorld. + if ( b3World_IsValid( player->rdr.replayWorldId ) ) + { + b3World_SetWorkerCount( player->rdr.replayWorldId, player->recordedWorkerCount ); + } +} + +void b3RecPlayer_SetKeyframePolicy( b3RecPlayer* player, size_t budgetBytes, int minIntervalFrames ) +{ + if ( player == NULL ) + { + return; + } + if ( budgetBytes > 0 ) + { + player->keyframeBudget = budgetBytes; + } + if ( minIntervalFrames > 0 ) + { + player->keyframeMinInterval = minIntervalFrames; + } + + // Drop the ring so it repopulates under the new policy on the next replay. + for ( int i = 0; i < player->keyframeCount; ++i ) + { + b3FreeKeyframe( player->keyframes + i ); + } + player->keyframeCount = 0; + player->keyframeBytes = 0; + player->keyframeInterval = player->keyframeMinInterval; + player->lastKeyframeFrame = 0; +} + +size_t b3RecPlayer_GetKeyframeBudget( const b3RecPlayer* player ) +{ + return player != NULL ? player->keyframeBudget : 0; +} + +int b3RecPlayer_GetKeyframeMinInterval( const b3RecPlayer* player ) +{ + return player != NULL ? player->keyframeMinInterval : 0; +} + +int b3RecPlayer_GetKeyframeInterval( const b3RecPlayer* player ) +{ + return player != NULL ? player->keyframeInterval : 0; +} + +size_t b3RecPlayer_GetKeyframeBytes( const b3RecPlayer* player ) +{ + return player != NULL ? player->keyframeBytes : 0; +} + +int b3RecPlayer_GetBodyCount( const b3RecPlayer* player ) +{ + return player != NULL ? player->bodyIdCount : 0; +} + +b3BodyId b3RecPlayer_GetBodyId( const b3RecPlayer* player, int index ) +{ + if ( player == NULL || index < 0 || index >= player->bodyIdCount ) + { + return b3_nullBodyId; + } + return player->bodyIds[index]; +} + +// A selected query draws in one reserved color so it stands out when every query is drawn at once. +static b3HexColor b3RecQuerySelColor( bool selected, b3HexColor base ) +{ + return selected ? b3_colorPlum : base; +} + +// Highlight each reported overlap shape by its AABB. Skip any destroyed since the query, per the +// b3Shape_GetAABB contract that overlap results may contain stale shapes. +static void b3RecDrawHitBounds( const b3RecPlayer* player, const b3RecDrawQuery* q, b3DebugDraw* draw, b3HexColor color ) +{ + if ( draw->DrawBoundsFcn == NULL ) + { + return; + } + for ( int hi = q->hitStart; hi < q->hitStart + q->hitCount; ++hi ) + { + b3ShapeId id = player->frameHits[hi].id; + if ( b3Shape_IsValid( id ) == false ) + { + continue; + } + draw->DrawBoundsFcn( b3Shape_GetAABB( id ), color, draw->context ); + } +} + +// Draw a recorded shape proxy at basePos. A lone point with no radius draws a fat point, a lone point +// with a radius draws a translucent sphere, and a multi-point cloud draws its points. Capsule and hull +// proxies are rare and fall through to the point cloud for now. +static void b3RecDrawProxy( b3DebugDraw* draw, b3Pos basePos, const b3RecDrawQuery* q, b3HexColor color ) +{ + if ( q->proxyCount == 1 ) + { + b3Pos p = b3OffsetPos( basePos, q->proxyPoints[0] ); + if ( q->proxyRadius > 0.0f ) + { + if ( draw->DrawSphereFcn ) + { + draw->DrawSphereFcn( p, q->proxyRadius, color, 0.5f, draw->context ); + } + } + else if ( draw->DrawPointFcn ) + { + draw->DrawPointFcn( p, 10.0f, color, draw->context ); + } + } + else if ( q->proxyCount == 2 && q->proxyRadius > 0.0f ) + { + if ( draw->DrawCapsuleFcn ) + { + b3Pos p1 = b3OffsetPos( basePos, q->proxyPoints[0] ); + b3Pos p2 = b3OffsetPos( basePos, q->proxyPoints[1] ); + draw->DrawCapsuleFcn( p1, p2, q->proxyRadius, color, 0.5f, draw->context ); + } + } + else if ( q->proxyCount >= 2 && draw->DrawPointFcn ) + { + for ( int i = 0; i < q->proxyCount; ++i ) + { + draw->DrawPointFcn( b3OffsetPos( basePos, q->proxyPoints[i] ), 6.0f, color, draw->context ); + } + } +} + +void b3RecPlayer_DrawFrameQueries( b3RecPlayer* player, b3DebugDraw* draw, int queryIndex, int selectedIndex ) +{ + if ( player == NULL || draw == NULL ) + { + return; + } + + // queryIndex < 0 draws every query, otherwise just the one selected in the viewer. The query at + // selectedIndex draws in one reserved color and is labeled, so it stands out among the rest. + for ( int qi = 0; qi < player->frameQueryCount; ++qi ) + { + if ( queryIndex >= 0 && qi != queryIndex ) + { + continue; + } + + const b3RecDrawQuery* q = &player->frameQueries[qi]; + bool selected = ( qi == selectedIndex ); + + switch ( q->kind ) + { + case B3_RECQ_CAST_RAY: + case B3_RECQ_CAST_RAY_CLOSEST: + { + b3Pos origin = q->origin; + b3Pos end = b3OffsetPos( origin, q->translation ); + if ( draw->DrawSegmentFcn ) + { + draw->DrawSegmentFcn( origin, end, b3RecQuerySelColor( selected, b3_colorYellow ), draw->context ); + } + for ( int hi = q->hitStart; hi < q->hitStart + q->hitCount; ++hi ) + { + const b3RecRecordedHit* h = &player->frameHits[hi]; + if ( draw->DrawPointFcn ) + { + draw->DrawPointFcn( h->point, 4.0f, b3RecQuerySelColor( selected, b3_colorYellow ), draw->context ); + } + if ( draw->DrawSegmentFcn ) + { + draw->DrawSegmentFcn( h->point, b3OffsetPos( h->point, b3MulSV( 0.2f, h->normal ) ), + b3RecQuerySelColor( selected, b3_colorYellowGreen ), draw->context ); + } + } + break; + } + case B3_RECQ_CAST_SHAPE: + { + // Draw the cast line and the proxy at its start, then each hit point and normal. + if ( draw->DrawSegmentFcn ) + { + draw->DrawSegmentFcn( q->origin, b3OffsetPos( q->origin, q->translation ), + b3RecQuerySelColor( selected, b3_colorSkyBlue ), draw->context ); + } + b3RecDrawProxy( draw, q->origin, q, b3RecQuerySelColor( selected, b3_colorLightGreen ) ); + for ( int hi = q->hitStart; hi < q->hitStart + q->hitCount; ++hi ) + { + const b3RecRecordedHit* h = &player->frameHits[hi]; + if ( draw->DrawPointFcn ) + { + draw->DrawPointFcn( h->point, 4.0f, b3RecQuerySelColor( selected, b3_colorSkyBlue ), draw->context ); + } + if ( draw->DrawSegmentFcn ) + { + draw->DrawSegmentFcn( h->point, b3OffsetPos( h->point, b3MulSV( 0.2f, h->normal ) ), + b3RecQuerySelColor( selected, b3_colorLightSkyBlue ), draw->context ); + } + if ( draw->DrawSphereFcn ) + { + b3Pos p = b3OffsetPos( q->origin, b3MulSV( h->fraction, q->translation ) ); + b3RecDrawProxy( draw, p, q, b3RecQuerySelColor( selected, b3_colorSkyBlue ) ); + } + } + break; + } + case B3_RECQ_CAST_MOVER: + { + b3Pos c1 = b3OffsetPos( q->origin, q->mover.center1 ); + b3Pos c2 = b3OffsetPos( q->origin, q->mover.center2 ); + b3HexColor c = b3_colorLightSkyBlue; + if ( draw->DrawCapsuleFcn ) + { + draw->DrawCapsuleFcn( c1, c2, q->mover.radius, b3RecQuerySelColor( selected, c ), 0.6f, draw->context ); + + if ( q->castFraction > 0.01f ) + { + b3Vec3 d = b3MulSV( q->castFraction, q->translation ); + c1 = b3OffsetPos( c1, d ); + c2 = b3OffsetPos( c2, d ); + draw->DrawCapsuleFcn( c1, c2, q->mover.radius, c, 0.3f, draw->context ); + } + } + break; + } + + case B3_RECQ_COLLIDE_MOVER: + { + b3Pos c1 = b3OffsetPos( q->origin, q->mover.center1 ); + b3Pos c2 = b3OffsetPos( q->origin, q->mover.center2 ); + b3HexColor c = b3_colorTan; + if ( draw->DrawCapsuleFcn ) + { + draw->DrawCapsuleFcn( c1, c2, q->mover.radius, b3RecQuerySelColor( selected, c ), 0.6f, draw->context ); + } + + for ( int hi = q->hitStart; hi < q->hitStart + q->hitCount; ++hi ) + { + const b3RecRecordedHit* h = &player->frameHits[hi]; + b3Pos point = b3OffsetPos( q->origin, h->plane.point ); + if ( draw->DrawSegmentFcn ) + { + draw->DrawSegmentFcn( point, b3OffsetPos( point, b3MulSV( 0.2f, h->plane.plane.normal ) ), + b3RecQuerySelColor( selected, b3_colorOrange ), draw->context ); + } + } + break; + } + + case B3_RECQ_OVERLAP_AABB: + { + if ( draw->DrawBoundsFcn ) + { + draw->DrawBoundsFcn( q->aabb, b3RecQuerySelColor( selected, b3_colorLimeGreen ), draw->context ); + } + b3RecDrawHitBounds( player, q, draw, b3RecQuerySelColor( selected, b3_colorMagenta ) ); + break; + } + case B3_RECQ_OVERLAP_SHAPE: + { + // The overlap proxy sits at the origin; draw it, then the overlapping shape bounds. + b3RecDrawProxy( draw, q->origin, q, b3RecQuerySelColor( selected, b3_colorLimeGreen ) ); + b3RecDrawHitBounds( player, q, draw, b3RecQuerySelColor( selected, b3_colorMagenta ) ); + break; + } + default: + break; + } + + // Label the selected query at its origin so it reads by name among the others. The overlap AABB + // has no origin, so anchor at the box center. Untagged queries (no key) rely on the color alone. + if ( selected && q->key != 0 && draw->DrawStringFcn != NULL ) + { + const char* name = NULL; + uint64_t id = 0; + if ( player->rdr.tagMap != NULL ) + { + b3RecTagLookup_itr it = b3RecTagLookup_get( (b3RecTagLookup*)player->rdr.tagMap, q->key ); + if ( b3RecTagLookup_is_end( it ) == false ) + { + const b3RecTag* tag = &player->rdr.tags[it.data->val]; + name = tag->queryName; + id = tag->id; + } + } + char label[64]; + if ( name != NULL && name[0] != '\0' && id != 0 ) + { + snprintf( label, sizeof( label ), "%.40s (%" PRIu64 ")", name, id ); + } + else if ( name != NULL && name[0] != '\0' ) + { + snprintf( label, sizeof( label ), "%.40s", name ); + } + else + { + snprintf( label, sizeof( label ), "#%" PRIu64, id ); + } + b3Pos labelPos = q->origin; + if ( q->kind == B3_RECQ_OVERLAP_AABB ) + { + labelPos = b3ToPos( b3AABB_Center( q->aabb ) ); + } + else if ( q->kind == B3_RECQ_CAST_MOVER || q->kind == B3_RECQ_COLLIDE_MOVER ) + { + // Sit the label just past the center2 end cap, which for an upright mover is above it. + b3Pos c1 = b3OffsetPos( q->origin, q->mover.center1 ); + b3Pos c2 = b3OffsetPos( q->origin, q->mover.center2 ); + b3Vec3 dir = b3Normalize( b3SubPos( c2, c1 ) ); + labelPos = b3OffsetPos( c2, b3MulSV( 1.25f * q->mover.radius, dir ) ); + } + draw->DrawStringFcn( labelPos, label, b3_colorWhite, draw->context ); + } + } +} + +// The internal b3RecQueryKind values match the public b3RecQueryType, so the kind copies across as a +// plain cast. Pin the first and last kinds to catch enum drift. +_Static_assert( b3_recQueryOverlapAABB == 0 && B3_RECQ_OVERLAP_AABB == 0, "query type enum drift" ); +_Static_assert( b3_recQueryCollideMover == 6 && B3_RECQ_COLLIDE_MOVER == 6, "query type enum drift" ); + +int b3RecPlayer_GetFrameQueryCount( const b3RecPlayer* player ) +{ + return player != NULL ? player->frameQueryCount : 0; +} + +b3RecQueryInfo b3RecPlayer_GetFrameQuery( const b3RecPlayer* player, int index ) +{ + b3RecQueryInfo info = { 0 }; + if ( player == NULL || index < 0 || index >= player->frameQueryCount ) + { + return info; + } + + const b3RecDrawQuery* q = &player->frameQueries[index]; + info.type = (b3RecQueryType)q->kind; + info.filter = q->filter; + info.aabb = q->aabb; + info.origin = q->origin; + info.translation = q->translation; + info.hitCount = q->hitCount; + info.key = q->key; + info.id = 0; + info.name = NULL; + if ( q->key != 0 && player->rdr.tagMap != NULL ) + { + b3RecTagLookup_itr it = b3RecTagLookup_get( (b3RecTagLookup*)player->rdr.tagMap, q->key ); + if ( b3RecTagLookup_is_end( it ) == false ) + { + const b3RecTag* tag = &player->rdr.tags[it.data->val]; + info.id = tag->id; + // An id-only tag interns an empty name; report it as none so the viewer shows the id alone. + info.name = tag->queryName[0] != '\0' ? tag->queryName : NULL; + } + } + return info; +} + +b3RecQueryHit b3RecPlayer_GetFrameQueryHit( const b3RecPlayer* player, int queryIndex, int hitIndex ) +{ + b3RecQueryHit hit = { 0 }; + if ( player == NULL || queryIndex < 0 || queryIndex >= player->frameQueryCount ) + { + return hit; + } + + const b3RecDrawQuery* q = &player->frameQueries[queryIndex]; + if ( hitIndex < 0 || hitIndex >= q->hitCount ) + { + return hit; + } + + const b3RecRecordedHit* h = &player->frameHits[q->hitStart + hitIndex]; + hit.shape = h->id; + hit.point = h->point; + hit.normal = h->normal; + hit.fraction = h->fraction; + return hit; +} + +void b3RecPlayer_SetDebugShapeCallbacks( b3RecPlayer* player, b3CreateDebugShapeCallback* createDebugShape, + b3DestroyDebugShapeCallback* destroyDebugShape, void* context ) +{ + if ( player == NULL ) + { + return; + } + + player->createDebugShape = createDebugShape; + player->destroyDebugShape = destroyDebugShape; + player->debugShapeContext = context; + + // A world fixes its debug-shape callbacks at creation, so rebuild frame 0 under the new + // wiring. The old world held no adapter shapes (its callbacks were NULL), so the tear-down + // is balanced. Geometry slots are byte blobs reused as-is, the same path Restart relies on. + if ( b3World_IsValid( player->rdr.replayWorldId ) ) + { + b3DestroyWorld( player->rdr.replayWorldId ); + } + player->rdr.replayWorldId = b3RecPlayerCreateWorld( player ); + player->rdr.cursor = player->headerEnd; + player->rdr.ok = true; + player->rdr.diverged = false; + player->frame = 0; + player->divergeFrame = -1; + player->atEnd = false; + + // Re-seed the world so its shapes are recreated through the new callbacks. + b3World* world = b3GetWorldFromId( player->rdr.replayWorldId ); + if ( b3DeserializeIntoShell( player->frame0Image, player->frame0Size, world, &player->rdr ) == false ) + { + player->rdr.ok = false; + return; + } + player->rdr.cursor = player->headerEnd; + + // Rebuild the outliner from the frame-0 world that was just stood up under the new callbacks. + b3RecSeedFrame0BodyIds( player ); +} diff --git a/vendor/box3d/src/src/recording_replay.h b/vendor/box3d/src/src/recording_replay.h new file mode 100644 index 000000000..68760f6d0 --- /dev/null +++ b/vendor/box3d/src/src/recording_replay.h @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "recording.h" + +#include +#include + +typedef struct b3RecPlayer b3RecPlayer; + +// A single recorded callback hit, used both as reader scratch during a query replay and as the +// per-frame draw store. For collide-mover, one hit is one plane, with planeCount and userReturnB +// replicated across a shape's planes so the replay walker can re-group and compare per shape. +typedef struct b3RecRecordedHit +{ + b3ShapeId id; + b3Pos point; + b3Vec3 normal; + float fraction; + uint64_t userMaterialId; + int triangleIndex; + int childIndex; + b3PlaneResult plane; // collide-mover: this plane + int planeCount; // collide-mover: planes in this hit's shape group (replicated) + float userReturnF; // cast queries + bool userReturnB; // overlap / collide-mover (per shape, replicated) +} b3RecRecordedHit; + +// Recorded query kind, matching the public b3RecQueryType order. +typedef enum b3RecQueryKind +{ + B3_RECQ_OVERLAP_AABB, + B3_RECQ_OVERLAP_SHAPE, + B3_RECQ_CAST_RAY, + B3_RECQ_CAST_SHAPE, + B3_RECQ_CAST_RAY_CLOSEST, + B3_RECQ_CAST_MOVER, + B3_RECQ_COLLIDE_MOVER, +} b3RecQueryKind; + +// Per-frame draw record for one query call. Self-contained (no aliased pointers) so the player's +// frameQueries array can grow with a plain memcpy. Geometry is origin relative except the overlap +// AABB, which is world space in 3D. +typedef struct b3RecDrawQuery +{ + int kind; + uint64_t key; // identity key (hash of caller id+name), 0 = untagged + b3QueryFilter filter; + b3AABB aabb; // world-space bounds of the query, swept for casts + b3Vec3 proxyPoints[B3_MAX_SHAPE_CAST_POINTS]; // overlap/cast shape proxy, origin relative + int proxyCount; + float proxyRadius; + b3Capsule mover; // cast/collide mover, origin relative + b3Pos origin; + b3Vec3 translation; + float castFraction; // cast-mover result fraction + b3RayResult rayResult; // cast-ray-closest result + b3ShapeId shape; + int hitStart; // first hit in the player's frameHits store + int hitCount; +} b3RecDrawQuery; + +// One slot in the preloaded geometry registry. Loaded from the trailing block before any +// ops run; live pointer built lazily on first shape create that references the id. +typedef struct b3RegistrySlot +{ + b3GeometryKind kind; + int byteCount; + uint8_t* bytes; // raw bytes from the file (always freed at teardown) + void* live; // reconstructed live object, freed after b3DestroyWorld +} b3RegistrySlot; + +// This is used to simplify the scratch buffer lifetime. Names longer than this probably +// indicate a bug. +#define B3_MAX_NAME_LENGTH 256 + +// Reader state threaded through the replay loop and all dispatch functions +typedef struct b3RecReader +{ + const uint8_t* data; + int size; + int cursor; + b3WorldId replayWorldId; + bool ok; // false on read overrun or id mismatch, fatal stop + bool diverged; // a StateHash failed, non-fatal + + // Player that owns this reader, or NULL during a headless b3ValidateReplay. Body + // create/destroy and the bounds record fold back into it for the outliner and camera framing. + b3RecPlayer* owner; + + // Scratch for per-triangle materials in shape defs (grown on demand, freed at teardown) + b3SurfaceMaterial* matScratch; + int matScratchCap; + + // Scratch for string reads. Used by b3RecR_STR to pass names to b3BodyDef and b3ShapeDef. + char stringBuffers[4][B3_MAX_NAME_LENGTH + 1]; + int nextString; + + // Preloaded geometry registry + b3RegistrySlot* slots; + int slotCount; + + // Preloaded query-tag table (key -> id, name), loaded with the registry. Resolves the caller id and + // label for the viewer. tagMap maps a key to its tag index for O(1) lookup; opaque, owned by the player. + b3RecTag* tags; + int tagCount; // tags that loaded; a truncated tail loads fewer + int tagCapacity; // tags allocated, used to free the array + void* tagMap; + + // Key from the QueryTag op preceding the next query, consumed by the next stash. 0 = untagged. + uint64_t pendingQueryKey; + + // Scratch for recorded query hits; grown on demand, freed with the player. + b3RecRecordedHit* hits; + int hitCap; + + // Scratch for a shape-proxy point cloud read from the stream. b3ShapeProxy holds the points + // behind a pointer, so a decoded proxy borrows this until the next proxy read or teardown. + b3Vec3* proxyScratch; + int proxyScratchCap; +} b3RecReader; + +// Stored snapshot for fast backward seek. +typedef struct b3RecKeyframe +{ + uint8_t* image; // serialized world image at the end of this frame + int imageSize; + int imageCapacity; // allocation size (may exceed imageSize) + int frame; // frame index this restores to + int cursor; // op-stream cursor for the frame AFTER this one + int divergeFrame; // divergeFrame state at capture + bool diverged; // rdr.diverged state at capture + + // Outliner body list as it stood at this frame, restored verbatim so ordinals are stable. + b3BodyId* bodyIds; + int bodyIdCount; +} b3RecKeyframe; + +typedef struct b3RecPlayer +{ + uint8_t* data; // owned copy of recording bytes + int size; + int headerEnd; // first byte of op stream (past header + snapshot blob) + int registryEnd; // end of op stream = start of registry block (or size) + float lengthScale; + float previousLengthScale; + int frame; + int frameCount; + float recordedDt; + int recordedSubStepCount; + int recordedWorkerCount; // worker count requested for the replay world + b3AABB bounds; // accumulated world bounds, decoded from the trailing record + bool atEnd; + // Indicates all ops for the step have been consumed up to the world step op. The next sub-step + // will clear this and perform the world step. + bool atPreStep; + int divergeFrame; // first frame that diverged, -1 until then + + // Outliner body list, indexed by creation ordinal. Holes (null ids) mark destroyed bodies so + // later ordinals never shift. Snapshotted into each keyframe and the frame-0 copy, not rebuilt + // from the world, so a stored selection survives backward seeks. + b3BodyId* bodyIds; + int bodyIdCount; + int bodyIdCap; + b3BodyId* frame0BodyIds; + int frame0BodyIdCount; + + // Per-frame query store, reset at the top of each StepFrame and filled by the query dispatchers. + // Drawn by b3RecPlayer_DrawFrameQueries and inspected via the public GetFrameQuery API. + b3RecDrawQuery* frameQueries; + int frameQueryCount; + int frameQueryCap; + b3RecRecordedHit* frameHits; + int frameHitCount; + int frameHitCap; + + // Host debug-shape callbacks applied to every world the player creates. The 3D + // sample renderer builds GPU meshes here, so a replay world without them draws + // nothing. Set once via b3RecPlayer_SetDebugShapeCallbacks; persisted so a world + // rebuilt under new callbacks keeps drawing. + b3CreateDebugShapeCallback* createDebugShape; + b3DestroyDebugShapeCallback* destroyDebugShape; + void* debugShapeContext; + + b3RecReader rdr; + + // Frame-0 restore image, points into the owned data copy. Restart and backward seek + // deserialize this in place so the replay world id stays stable. + const uint8_t* frame0Image; + int frame0Size; + + // Keyframe ring + b3RecKeyframe* keyframes; + int keyframeCount; + int keyframeCapacity; + size_t keyframeBudget; + size_t keyframeBytes; + int keyframeMinInterval; + int keyframeInterval; + int lastKeyframeFrame; + + // Pre-populated recording used by b3SerializeWorld during keyframe capture. + // Its registry mirrors rdr.slots so geometry ids stay stable. + b3Recording* keyframeRec; +} b3RecPlayer; + +// Read primitives +uint8_t b3RecR_U8( b3RecReader* rdr ); +uint16_t b3RecR_U16( b3RecReader* rdr ); +uint32_t b3RecR_U24( b3RecReader* rdr ); +uint32_t b3RecR_U32( b3RecReader* rdr ); +uint64_t b3RecR_U64( b3RecReader* rdr ); +int32_t b3RecR_I32( b3RecReader* rdr ); +float b3RecR_F32( b3RecReader* rdr ); +double b3RecR_F64( b3RecReader* rdr ); +bool b3RecR_BOOL( b3RecReader* rdr ); +b3Vec3 b3RecR_VEC3( b3RecReader* rdr ); +b3Quat b3RecR_QUAT( b3RecReader* rdr ); +b3Transform b3RecR_TRANSFORM( b3RecReader* rdr ); +b3Pos b3RecR_POSITION( b3RecReader* rdr ); +b3WorldTransform b3RecR_WORLDXF( b3RecReader* rdr ); +b3Matrix3 b3RecR_MATRIX3( b3RecReader* rdr ); +b3AABB b3RecR_AABB( b3RecReader* rdr ); +b3WorldId b3RecR_WORLDID( b3RecReader* rdr ); +b3BodyId b3RecR_BODYID( b3RecReader* rdr ); +b3ShapeId b3RecR_SHAPEID( b3RecReader* rdr ); +b3JointId b3RecR_JOINTID( b3RecReader* rdr ); +b3Sphere b3RecR_SPHERE( b3RecReader* rdr ); +b3Capsule b3RecR_CAPSULE( b3RecReader* rdr ); +uint32_t b3RecR_GEOMID( b3RecReader* rdr ); +b3Filter b3RecR_FILTER( b3RecReader* rdr ); +b3SurfaceMaterial b3RecR_MATERIAL( b3RecReader* rdr ); +b3MassData b3RecR_MASSDATA( b3RecReader* rdr ); +b3MotionLocks b3RecR_LOCKS( b3RecReader* rdr ); +const char* b3RecR_STR( b3RecReader* rdr ); +b3ExplosionDef b3RecR_EXPLOSIONDEF( b3RecReader* rdr ); +b3BodyDef b3RecR_BODYDEF( b3RecReader* rdr ); +b3ShapeDef b3RecR_SHAPEDEF( b3RecReader* rdr ); +b3ParallelJointDef b3RecR_PARALLELJOINTDEF( b3RecReader* rdr ); +b3DistanceJointDef b3RecR_DISTANCEJOINTDEF( b3RecReader* rdr ); +b3FilterJointDef b3RecR_FILTERJOINTDEF( b3RecReader* rdr ); +b3MotorJointDef b3RecR_MOTORJOINTDEF( b3RecReader* rdr ); +b3PrismaticJointDef b3RecR_PRISMATICJOINTDEF( b3RecReader* rdr ); +b3RevoluteJointDef b3RecR_REVOLUTEJOINTDEF( b3RecReader* rdr ); +b3SphericalJointDef b3RecR_SPHERICALJOINTDEF( b3RecReader* rdr ); +b3WeldJointDef b3RecR_WELDJOINTDEF( b3RecReader* rdr ); +b3WheelJointDef b3RecR_WHEELJOINTDEF( b3RecReader* rdr ); +b3QueryFilter b3RecR_QUERYFILTER( b3RecReader* rdr ); +b3ShapeProxy b3RecR_SHAPEPROXY( b3RecReader* rdr ); +b3TreeStats b3RecR_TREESTATS( b3RecReader* rdr ); +b3RayResult b3RecR_RAYRESULT( b3RecReader* rdr ); +b3PlaneResult b3RecR_PLANERESULT( b3RecReader* rdr ); + +// Grow the reader's hit scratch to at least n entries, preserving contents. n is bounded by the +// file size since every recorded hit consumes at least one byte. +void b3RecEnsureHits( b3RecReader* rdr, int n ); diff --git a/vendor/box3d/src/src/revolute_joint.c b/vendor/box3d/src/src/revolute_joint.c new file mode 100644 index 000000000..7856a83d8 --- /dev/null +++ b/vendor/box3d/src/src/revolute_joint.c @@ -0,0 +1,673 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "joint.h" +#include "math_internal.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +// Point-to-point linear constraint +// C = pB - pA +// Cdot = vB - vA +// = vB + cross(wB, rB) - vA - cross(wA, rA) +// Cdot = J * v +// J = [-E -skew(rA) E skew(rB) ] +// +// K = J * invM * JT +// = [(1/mA + 1/mB) * E - skew(rA) * invIA * skew(rA) - skew(rB) * invIB * skew(rB)] + +// Perpendicularity constraint +// frameA = qA * localFrameA +// frameB = qB * localFrameB +// qRel = conj(frameA) * frameB +// C = [qRel.x; qRel.y] +// qRelDot = 0.5 * conj(frameA) * (wB - wA) * frameB +// Cdot = [qRelDot.x, qRelDot.y] +// Pulling out wB and wA +// sr = qRel.s +// vr = qRel.v +// Jx = 0.5 * rotate(frameA, sr * ex + cross(vr, ex)) +// Jy = 0.5 * rotate(frameA, sr * ey + cross(vr, ey)) + +// Motor constraint +// Cdot = wB - wA +// J = [0 0 -E 0 0 E] +// K = invIA + invIB + +void b3RevoluteJoint_EnableLimit( b3JointId jointId, bool enableLimit ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointEnableLimit, jointId, enableLimit ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + if ( enableLimit != base->revoluteJoint.enableLimit ) + { + base->revoluteJoint.lowerImpulse = 0.0f; + base->revoluteJoint.upperImpulse = 0.0f; + } + base->revoluteJoint.enableLimit = enableLimit; +} + +bool b3RevoluteJoint_IsLimitEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.enableLimit; +} + +float b3RevoluteJoint_GetLowerLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.lowerAngle; +} + +float b3RevoluteJoint_GetUpperLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.upperAngle; +} + +void b3RevoluteJoint_SetLimits( b3JointId jointId, float lowerLimitRadians, float upperLimitRadians ) +{ + B3_ASSERT( b3IsValidFloat( lowerLimitRadians ) && b3IsValidFloat( upperLimitRadians ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointSetLimits, jointId, lowerLimitRadians, upperLimitRadians ); + + float lowerAngle = b3MinFloat( lowerLimitRadians, upperLimitRadians ); + float upperAngle = b3MaxFloat( lowerLimitRadians, upperLimitRadians ); + + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + base->revoluteJoint.lowerAngle = b3ClampFloat( lowerAngle, -0.99f * B3_PI, 0.99f * B3_PI ); + base->revoluteJoint.upperAngle = b3ClampFloat( upperAngle, -0.99f * B3_PI, 0.99f * B3_PI ); +} + +float b3RevoluteJoint_GetAngle( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB ); + + b3Quat quatA = b3MulQuat( transformA.q, base->localFrameA.q ); + b3Quat quatB = b3MulQuat( transformB.q, base->localFrameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the twist angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + return b3GetTwistAngle( relQ ); +} + +void b3RevoluteJoint_EnableSpring( b3JointId jointId, bool enableSpring ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointEnableSpring, jointId, enableSpring ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + if ( enableSpring != base->revoluteJoint.enableSpring ) + { + base->revoluteJoint.springImpulse = 0.0f; + } + base->revoluteJoint.enableSpring = enableSpring; +} + +bool b3RevoluteJoint_IsSpringEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.enableSpring; +} + +void b3RevoluteJoint_SetTargetAngle( b3JointId jointId, float targetRadians ) +{ + B3_ASSERT( b3IsValidFloat( targetRadians ) && -B3_PI <= targetRadians && targetRadians <= B3_PI ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointSetTargetAngle, jointId, targetRadians ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + base->revoluteJoint.targetAngle = targetRadians; +} + +float b3RevoluteJoint_GetTargetAngle( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.targetAngle; +} + +void b3RevoluteJoint_SetSpringHertz( b3JointId jointId, float hertz ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointSetSpringHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + base->revoluteJoint.hertz = hertz; +} + +float b3RevoluteJoint_GetSpringHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.hertz; +} + +void b3RevoluteJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointSetSpringDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + base->revoluteJoint.dampingRatio = dampingRatio; +} + +float b3RevoluteJoint_GetSpringDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.dampingRatio; +} + +void b3RevoluteJoint_EnableMotor( b3JointId jointId, bool enableMotor ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointEnableMotor, jointId, enableMotor ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + if ( enableMotor != base->revoluteJoint.enableMotor ) + { + base->revoluteJoint.motorImpulse = 0.0f; + } + base->revoluteJoint.enableMotor = enableMotor; +} + +bool b3RevoluteJoint_IsMotorEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.enableMotor; +} + +void b3RevoluteJoint_SetMotorSpeed( b3JointId jointId, float motorSpeed ) +{ + B3_ASSERT( b3IsValidFloat( motorSpeed ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointSetMotorSpeed, jointId, motorSpeed ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + base->revoluteJoint.motorSpeed = motorSpeed; +} + +float b3RevoluteJoint_GetMotorSpeed( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.motorSpeed; +} + +void b3RevoluteJoint_SetMaxMotorTorque( b3JointId jointId, float maxForce ) +{ + B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, RevoluteJointSetMaxMotorTorque, jointId, maxForce ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + base->revoluteJoint.maxMotorTorque = maxForce; +} + +float b3RevoluteJoint_GetMaxMotorTorque( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return base->revoluteJoint.maxMotorTorque; +} + +float b3RevoluteJoint_GetMotorTorque( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_revoluteJoint ); + return world->inv_h * base->revoluteJoint.motorImpulse; +} + +b3Vec3 b3GetRevoluteJointForce( b3World* world, b3JointSim* base ) +{ + b3Vec3 force = b3MulSV( world->inv_h, base->revoluteJoint.linearImpulse ); + return force; +} + +b3Vec3 b3GetRevoluteJointTorque( b3World* world, b3JointSim* base ) +{ + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3RevoluteJoint* joint = &base->revoluteJoint; + b3Vec3 axis = b3RotateVector( base->localFrameA.q, b3Vec3_axisZ ); + axis = b3RotateVector( transformA.q, axis ); + + b3Quat relQ = b3InvMulQuat( joint->frameA.q, joint->frameB.q ); + + // These are needed for warm starting + joint->perpAxisX = b3MulSV( + 0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + joint->perpAxisY = b3MulSV( + 0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + + float axialImpulse = joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse; + b3Vec3 angularImpulse = + b3Add( b3MulSV( joint->perpImpulse.x, joint->perpAxisX ), b3MulSV( joint->perpImpulse.y, joint->perpAxisY ) ); + angularImpulse = b3MulAdd( angularImpulse, axialImpulse, joint->rotationAxisZ ); + + // todo add pivot torque + b3Vec3 impulse = b3MulAdd( angularImpulse, + joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse, axis ); + b3Vec3 torque = b3MulSV( world->inv_h, impulse ); + return torque; +} + +void b3PrepareRevoluteJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_revoluteJoint ); + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB ); + + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ); + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3RevoluteJoint* joint = &base->revoluteJoint; + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + // Avoid round-off here as much as possible. + // b3Vec3 pf = (xf.p - c) + rot(xf.q, f.p) + // pf = xf.p - (xf.p + rot(xf.q, lc)) + rot(xf.q, f.p) + // pf = rot(xf.q, f.p - lc) + joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + + { + // Rotation axis is the z-axis of body A. + b3Vec3 rotationAxisZ = b3RotateVector( joint->frameA.q, b3Vec3_axisZ ); + float k = b3Dot( rotationAxisZ, b3MulMV( invInertiaSum, rotationAxisZ ) ); + joint->axialMass = k > 0.0f ? 1.0f / k : 0.0f; + joint->rotationAxisZ = rotationAxisZ; + } + + b3Quat relQ = b3InvMulQuat( joint->frameA.q, joint->frameB.q ); + + { + // These are needed for warm starting + joint->perpAxisX = b3MulSV( + 0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + joint->perpAxisY = b3MulSV( + 0.5f, b3RotateVector( joint->frameA.q, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + } + + joint->springSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h ); + + if ( context->enableWarmStarting == false ) + { + joint->linearImpulse = b3Vec3_zero; + joint->perpImpulse = (b3Vec2){ 0.0f, 0.0f }; + joint->motorImpulse = 0.0f; + joint->springImpulse = 0.0f; + joint->lowerImpulse = 0.0f; + joint->upperImpulse = 0.0f; + } +} + +void b3WarmStartRevoluteJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_revoluteJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3RevoluteJoint* joint = &base->revoluteJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + float axialImpulse = joint->springImpulse + joint->motorImpulse + joint->lowerImpulse - joint->upperImpulse; + b3Vec3 angularImpulse = + b3Add( b3MulSV( joint->perpImpulse.x, joint->perpAxisX ), b3MulSV( joint->perpImpulse.y, joint->perpAxisY ) ); + angularImpulse = b3MulAdd( angularImpulse, axialImpulse, joint->rotationAxisZ ); + + vA = b3MulSub( vA, mA, joint->linearImpulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Add( b3Cross( rA, joint->linearImpulse ), angularImpulse ) ) ); + + vB = b3MulAdd( vB, mB, joint->linearImpulse ); + wB = b3Add( wB, b3MulMV( iB, b3Add( b3Cross( rB, joint->linearImpulse ), angularImpulse ) ) ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3SolveRevoluteJoint( b3JointSim* base, b3StepContext* context, bool useBias ) +{ + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3RevoluteJoint* joint = &base->revoluteJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + bool fixedRotation = base->fixedRotation; + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the rotation angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + // Solve spring + if ( joint->enableSpring && fixedRotation == false ) + { + // Get the substep relative rotation + float targetAngle = joint->targetAngle; + float angle = b3GetTwistAngle( relQ ); + float c = angle - targetAngle; + + float bias = joint->springSoftness.biasRate * c; + float massScale = joint->springSoftness.massScale; + float impulseScale = joint->springSoftness.impulseScale; + float cdot = b3Dot( b3Sub( wB, wA ), joint->rotationAxisZ ); + + float deltaImpulse = -massScale * joint->axialMass * ( cdot + bias ) - impulseScale * joint->springImpulse; + joint->springImpulse += deltaImpulse; + + wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, joint->rotationAxisZ ) ); + wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, joint->rotationAxisZ ) ); + } + + if ( joint->enableMotor && fixedRotation == false ) + { + float cdot = b3Dot( b3Sub( wB, wA ), joint->rotationAxisZ ) - joint->motorSpeed; + + float deltaImpulse = -joint->axialMass * cdot; + float newImpulse = joint->motorImpulse + deltaImpulse; + float maxImpulse = joint->maxMotorTorque * context->h; + newImpulse = b3ClampFloat( newImpulse, -maxImpulse, maxImpulse ); + deltaImpulse = newImpulse - joint->motorImpulse; + joint->motorImpulse = newImpulse; + + wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, joint->rotationAxisZ ) ); + wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, joint->rotationAxisZ ) ); + } + + if ( joint->enableLimit && fixedRotation == false ) + { + float angle = b3GetTwistAngle( relQ ); + + // todo does an updated twist axis help? + + b3Vec3 axis = joint->rotationAxisZ; + + // Lower limit + { + float c = angle - joint->lowerAngle; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + float cdot = b3Dot( b3Sub( wB, wA ), axis ); + float oldImpulse = joint->lowerImpulse; + float deltaImpulse = -massScale * joint->axialMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->lowerImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + deltaImpulse = joint->lowerImpulse - oldImpulse; + + wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, axis ) ); + wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, axis ) ); + } + + // Upper limit + { + float c = joint->upperAngle - angle; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // sign flipped on Cdot + float cdot = b3Dot( b3Sub( wA, wB ), axis ); + float oldImpulse = joint->upperImpulse; + float deltaImpulse = -massScale * joint->axialMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->upperImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + deltaImpulse = joint->upperImpulse - oldImpulse; + + // sign flipped on applied impulse + wA = b3MulAdd( wA, deltaImpulse, b3MulMV( iA, axis ) ); + wB = b3MulSub( wB, deltaImpulse, b3MulMV( iB, axis ) ); + } + } + + // Collinearity constraint + if ( fixedRotation == false ) + { + b3Vec2 bias = { 0.0f, 0.0f }; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( useBias ) + { + b3Vec2 c = { relQ.v.x, relQ.v.y }; + bias = b3MulSV2( base->constraintSoftness.biasRate, c ); + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // Collinearity constraint as 2-by-2 + b3Vec3 perpAxisX = + b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + b3Vec3 perpAxisY = + b3MulSV( 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + joint->perpAxisX = perpAxisX; + joint->perpAxisY = perpAxisY; + + b3Matrix3 invInertiaSum = b3AddMM( iA, iB ); + float kxx = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisX ) ); + float kyy = b3Dot( perpAxisY, b3MulMV( invInertiaSum, perpAxisY ) ); + float kxy = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisY ) ); + + b3Matrix2 k = { { kxx, kxy }, { kxy, kyy } }; + + b3Vec3 wRel = b3Sub( wB, wA ); + b3Vec2 cdot = { b3Dot( wRel, perpAxisX ), b3Dot( wRel, perpAxisY ) }; + b3Vec2 oldImpulse = joint->perpImpulse; + b3Vec2 sol = b3Solve2( k, b3Add2( cdot, bias ) ); + b3Vec2 deltaImpulse = b3Sub2( b3MulSV2( -massScale, sol ), b3MulSV2( impulseScale, oldImpulse ) ); + joint->perpImpulse = b3Add2( joint->perpImpulse, deltaImpulse ); + + b3Vec3 angularImpulse = b3Add( b3MulSV( deltaImpulse.x, perpAxisX ), b3MulSV( deltaImpulse.y, perpAxisY ) ); + wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) ); + wB = b3Add( wB, b3MulMV( iB, angularImpulse ) ); + } + + // Solve point-to-point constraint + { + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 cdot = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, rA ) ); + + b3Vec3 bias = b3Vec3_zero; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias ) + { + b3Vec3 dcA = stateA->deltaPosition; + b3Vec3 dcB = stateB->deltaPosition; + + b3Vec3 separation = b3Add( b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ), joint->deltaCenter ); + + bias = b3MulSV( base->constraintSoftness.biasRate, separation ); + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + //// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + b3Matrix3 sA = b3Skew( rA ); + b3Matrix3 sB = b3Skew( rB ); + b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) ); + b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) ); + b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) ); + k.cx.x += mA + mB; + k.cy.y += mA + mB; + k.cz.z += mA + mB; + + b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) ); + + b3Vec3 impulse = b3Sub( b3MulSV( -massScale, b ), b3MulSV( impulseScale, joint->linearImpulse ) ); + joint->linearImpulse = b3Add( joint->linearImpulse, impulse ); + + vA = b3MulSub( vA, mA, impulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3DrawRevoluteJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ) +{ + b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA ); + + float length1 = 0.1f * scale; + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisX ) ) ), b3_colorRed, + draw->context ); + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisY ) ) ), b3_colorGreen, + draw->context ); + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisZ ) ) ), b3_colorBlue, + draw->context ); + + b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB ); + + b3RevoluteJoint* joint = &base->revoluteJoint; + enum { kSliceCount = 16 }; + + // Twist limit + if ( joint->enableLimit ) + { + b3Quat quatA = frameA.q; + b3Quat quatB = frameB.q; + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the twist angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + const float wedgeRadius = 0.2f * scale; + for ( int index = 0; index < kSliceCount; ++index ) + { + float t1 = (float)( index + 0 ) / kSliceCount; + float alpha1 = ( 1.0f - t1 ) * joint->lowerAngle + t1 * joint->upperAngle; + float t2 = (float)( index + 1 ) / kSliceCount; + float alpha2 = ( 1.0f - t2 ) * joint->lowerAngle + t2 * joint->upperAngle; + + b3Vec3 vertex1 = { wedgeRadius * b3Cos( alpha1 ), wedgeRadius * b3Sin( alpha1 ), 0.0f }; + b3Vec3 vertex2 = { wedgeRadius * b3Cos( alpha2 ), wedgeRadius * b3Sin( alpha2 ), 0.0f }; + + if ( index == 0 ) + { + draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, vertex1 ), b3_colorCyan, draw->context ); + } + + if ( index == kSliceCount - 1 ) + { + draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex2 ), frameA.p, b3_colorCyan, draw->context ); + } + draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex1 ), b3TransformWorldPoint( frameA, vertex2 ), b3_colorCyan, + draw->context ); + } + + float twistAngle = b3GetTwistAngle( relQ ); + b3Vec3 p2 = { wedgeRadius * b3Cos( twistAngle ), wedgeRadius * b3Sin( twistAngle ), 0.0f }; + draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, p2 ), b3_colorYellow, draw->context ); + } +} diff --git a/vendor/box3d/src/src/scheduler.c b/vendor/box3d/src/src/scheduler.c new file mode 100644 index 000000000..7bcde42f1 --- /dev/null +++ b/vendor/box3d/src/src/scheduler.c @@ -0,0 +1,198 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#include "platform.h" +#include "core.h" +#include "scheduler.h" + +#include "box3d/base.h" +#include "box3d/constants.h" + +#include +#include +#include + +enum b3SchedulerTaskStatus +{ + b3_schedulerFree = 0, + b3_schedulerPending = 1, + b3_schedulerClaimed = 2, + b3_schedulerComplete = 3, +}; + +typedef struct b3SchedulerTask +{ + b3TaskCallback* callback; + void* taskContext; + b3AtomicInt status; +} b3SchedulerTask; + +typedef struct b3SchedulerWorkerContext +{ + struct b3Scheduler* scheduler; + int threadIndex; +} b3SchedulerWorkerContext; + +typedef struct b3Scheduler +{ + b3Thread* threads[B3_MAX_WORKERS]; + b3SchedulerWorkerContext workerContexts[B3_MAX_WORKERS]; + + // total workers including main thread + int workerCount; + + // threads created = workerCount - 1 + int threadCount; + + b3SchedulerTask tasks[B3_MAX_TASKS]; + b3AtomicInt nextSlot; + + b3Semaphore* taskSemaphore; + b3AtomicInt shutdown; +} b3Scheduler; + +// Try to claim and execute one pending task. +// Returns true if work was performed, false otherwise. +static bool b3SchedulerExecuteOne( b3Scheduler* scheduler ) +{ + int taskCount = b3AtomicLoadInt( &scheduler->nextSlot ); + for ( int t = 0; t < taskCount; ++t ) + { + b3SchedulerTask* task = scheduler->tasks + t; + if ( b3AtomicLoadInt( &task->status ) != b3_schedulerPending ) + { + continue; + } + + if ( b3AtomicCompareExchangeInt( &task->status, b3_schedulerPending, b3_schedulerClaimed ) == false ) + { + continue; + } + + task->callback( task->taskContext ); + + b3AtomicStoreInt( &task->status, b3_schedulerComplete ); + return true; + } + + return false; +} + +// Background worker thread entry point. +static void b3SchedulerWorkerMain( void* context ) +{ + b3SchedulerWorkerContext* workerContext = context; + b3Scheduler* scheduler = workerContext->scheduler; + + while ( true ) + { + b3WaitSemaphore( scheduler->taskSemaphore ); + + if ( b3AtomicLoadInt( &scheduler->shutdown ) != 0 ) + { + break; + } + + // Claim and execute all available work + while ( b3SchedulerExecuteOne( scheduler ) ) + { + } + } +} + +b3Scheduler* b3CreateScheduler( int workerCount ) +{ + B3_ASSERT( 0 < workerCount && workerCount <= B3_MAX_WORKERS ); + + b3Scheduler* scheduler = b3Alloc( sizeof( b3Scheduler ) ); + memset( scheduler, 0, sizeof( b3Scheduler ) ); + + scheduler->workerCount = workerCount; + int threadCount = workerCount - 1; + scheduler->threadCount = threadCount; + scheduler->taskSemaphore = b3CreateSemaphore( 0 ); + b3AtomicStoreInt( &scheduler->shutdown, 0 ); + b3AtomicStoreInt( &scheduler->nextSlot, 0 ); + + // Background threads use indices 1..workerCount-1. + // Main thread uses index 0. + for ( int i = 0; i < threadCount; ++i ) + { + scheduler->workerContexts[i].scheduler = scheduler; + scheduler->workerContexts[i].threadIndex = i + 1; + + char name[16]; + snprintf( name, sizeof( name ), "box2d_worker_%02d", i + 1 ); + scheduler->threads[i] = b3CreateThread( b3SchedulerWorkerMain, scheduler->workerContexts + i, name ); + } + + return scheduler; +} + +void b3DestroyScheduler( b3Scheduler* scheduler ) +{ + b3AtomicStoreInt( &scheduler->shutdown, 1 ); + + // Wake all background threads so they see the shutdown flag + for ( int i = 0; i < scheduler->threadCount; ++i ) + { + b3SignalSemaphore( scheduler->taskSemaphore ); + } + + for ( int i = 0; i < scheduler->threadCount; ++i ) + { + b3JoinThread( scheduler->threads[i] ); + scheduler->threads[i] = NULL; + } + + b3DestroySemaphore( scheduler->taskSemaphore ); + b3Free( scheduler, sizeof( b3Scheduler ) ); +} + +void b3ResetScheduler( b3Scheduler* scheduler ) +{ + b3AtomicStoreInt( &scheduler->nextSlot, 0 ); +} + +void* b3SchedulerEnqueueTask( b3TaskCallback* task, void* taskContext, void* userContext, const char* name ) +{ + B3_UNUSED( name ); + b3Scheduler* scheduler = userContext; + + int slot = b3AtomicFetchAddInt( &scheduler->nextSlot, 1 ); + B3_ASSERT( slot < B3_MAX_TASKS ); + + b3SchedulerTask* schedulerTask = scheduler->tasks + slot; + schedulerTask->callback = task; + schedulerTask->taskContext = taskContext; + + // Memory fence: status must be published after callback and context are written + b3AtomicStoreInt( &schedulerTask->status, b3_schedulerPending ); + + // One wake per enqueue is enough: at most one worker picks up each task. + b3SignalSemaphore( scheduler->taskSemaphore ); + + return schedulerTask; +} + +void b3SchedulerFinishTask( void* userTask, void* userContext ) +{ + if ( userTask == NULL ) + { + return; + } + + b3Scheduler* scheduler = userContext; + b3SchedulerTask* waitTask = userTask; + + // Main thread helps execute any available work while waiting for the + // target task to complete. This keeps the main thread from idling when + // background threads are busy on other tasks from the same phase. + while ( b3AtomicLoadInt( &waitTask->status ) != b3_schedulerComplete ) + { + if ( b3SchedulerExecuteOne( scheduler ) == false ) + { + b3Yield(); + } + } +} diff --git a/vendor/box3d/src/src/scheduler.h b/vendor/box3d/src/src/scheduler.h new file mode 100644 index 000000000..f9aeff7b2 --- /dev/null +++ b/vendor/box3d/src/src/scheduler.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +typedef void b3TaskCallback( void* taskContext ); +typedef struct b3Scheduler b3Scheduler; + +b3Scheduler* b3CreateScheduler( int workerCount ); +void b3DestroyScheduler( b3Scheduler* scheduler ); +void b3ResetScheduler( b3Scheduler* scheduler ); + +// See b3EnqueueTaskCallback and b3FinishTaskCallback +void* b3SchedulerEnqueueTask( b3TaskCallback* task, void* taskContext, void* userContext, const char* name ); +void b3SchedulerFinishTask( void* userTask, void* userContext ); diff --git a/vendor/box3d/src/src/sensor.c b/vendor/box3d/src/src/sensor.c new file mode 100644 index 000000000..0cd23a460 --- /dev/null +++ b/vendor/box3d/src/src/sensor.c @@ -0,0 +1,452 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "sensor.h" + +#include "body.h" +#include "contact.h" +#include "ctz.h" +#include "physics_world.h" +#include "shape.h" + +#include "box3d/collision.h" + +// for qsort +#include "parallel_for.h" + +#include + +typedef struct b3SensorQueryContext +{ + b3World* world; + b3SensorTaskContext* taskContext; + b3Sensor* sensor; + b3Shape* sensorShape; + b3Transform transform; +} b3SensorQueryContext; + +static bool b3OverlapSensor( b3Shape* sensorShape, b3Transform sensorTransform, b3Shape* visitorShape, + b3Transform visitorTransform ) +{ + b3ShapeType type = sensorShape->type; + + b3ShapeProxy proxy = b3MakeShapeProxy( visitorShape ); + + // Get the visitor shape in the frame of the sensor + b3Transform relativeTransform = b3InvMulTransforms( sensorTransform, visitorTransform ); + + b3Vec3 localPoints[B3_MAX_SHAPE_CAST_POINTS]; + b3ShapeProxy localProxy; + + localProxy.count = b3MinInt( proxy.count, B3_MAX_SHAPE_CAST_POINTS ); + for ( int i = 0; i < localProxy.count; ++i ) + { + localPoints[i] = b3TransformPoint( relativeTransform, proxy.points[i] ); + } + + localProxy.points = localPoints; + localProxy.radius = proxy.radius; + + switch ( type ) + { + case b3_capsuleShape: + return b3OverlapCapsule( &sensorShape->capsule, b3Transform_identity, &localProxy ); + + case b3_compoundShape: + return b3OverlapCompound( sensorShape->compound, b3Transform_identity, &localProxy ); + + case b3_heightShape: + return b3OverlapHeightField( sensorShape->heightField, b3Transform_identity, &localProxy ); + + case b3_hullShape: + return b3OverlapHull( sensorShape->hull, b3Transform_identity, &localProxy ); + + case b3_meshShape: + return b3OverlapMesh( &sensorShape->mesh, b3Transform_identity, &localProxy ); + + case b3_sphereShape: + return b3OverlapSphere( &sensorShape->sphere, b3Transform_identity, &localProxy ); + + default: + B3_ASSERT( false ); + return false; + } +} + +// Sensor shapes need to +// - detect begin and end overlap events +// - events must be reported in deterministic order +// - maintain an active list of overlaps for query + +// Assumption +// - sensors don't detect shapes on the same body + +// Algorithm +// Query all sensors for overlaps +// Check against previous overlaps + +// Data structures +// Each sensor has an double buffered array of overlaps +// These overlaps use a shape reference with index and generation + +static bool b3SensorQueryCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + + b3SensorQueryContext* queryContext = (b3SensorQueryContext*)context; + b3Shape* sensorShape = queryContext->sensorShape; + int sensorShapeId = sensorShape->id; + + if ( shapeId == sensorShapeId ) + { + return true; + } + + b3World* world = queryContext->world; + b3Shape* otherShape = b3Array_Get( world->shapes, shapeId ); + + // Mesh vs mesh is not supported + if ( ( otherShape->type == b3_meshShape || otherShape->type == b3_heightShape ) && + ( sensorShape->type == b3_meshShape || sensorShape->type == b3_heightShape ) ) + { + return true; + } + + // Are sensor events enabled on the other shape? + if ( ( otherShape->flags & b3_enableSensorEvents ) == 0 ) + { + return true; + } + + // Skip shapes on the same body + if ( otherShape->bodyId == sensorShape->bodyId ) + { + return true; + } + + // Check filter + if ( b3ShouldShapesCollide( sensorShape->filter, otherShape->filter ) == false ) + { + return true; + } + + // Custom user filter + if ( ( sensorShape->flags & b3_enableCustomFiltering ) || ( otherShape->flags & b3_enableCustomFiltering ) ) + { + b3CustomFilterFcn* customFilterFcn = queryContext->world->customFilterFcn; + if ( customFilterFcn != NULL ) + { + b3ShapeId idA = { sensorShapeId + 1, world->worldId, sensorShape->generation }; + b3ShapeId idB = { shapeId + 1, world->worldId, otherShape->generation }; + bool shouldCollide = customFilterFcn( idA, idB, queryContext->world->customFilterContext ); + if ( shouldCollide == false ) + { + return true; + } + } + } + + b3Transform otherTransform = b3ToRelativeTransform( b3GetBodyTransform( world, otherShape->bodyId ), b3Pos_zero ); + + bool overlap = b3OverlapSensor( sensorShape, queryContext->transform, otherShape, otherTransform ); + if ( overlap == false ) + { + return true; + } + + // Record the overlap + b3Sensor* sensor = queryContext->sensor; + b3Visitor* shapeRef = b3Array_Emplace( sensor->overlaps2 ); + shapeRef->shapeId = shapeId; + shapeRef->generation = otherShape->generation; + + return true; +} + +static int b3CompareVisitors( const void* a, const void* b ) +{ + const b3Visitor* sa = (const b3Visitor*)a; + const b3Visitor* sb = (const b3Visitor*)b; + + if ( sa->shapeId < sb->shapeId ) + { + return -1; + } + + return 1; +} + +static void b3SensorTask( int startIndex, int endIndex, int workerIndex, void* context ) +{ + b3TracyCZoneNC( sensor_task, "Overlap", b3_colorBrown, true ); + + b3World* world = (b3World*)context; + B3_ASSERT( (int)workerIndex < world->workerCount ); + b3SensorTaskContext* taskContext = world->sensorTaskContexts.data + workerIndex; + + B3_ASSERT( startIndex < endIndex ); + + b3DynamicTree* trees = world->broadPhase.trees; + for ( int sensorIndex = startIndex; sensorIndex < endIndex; ++sensorIndex ) + { + b3Sensor* sensor = b3Array_Get( world->sensors, sensorIndex ); + b3Shape* sensorShape = b3Array_Get( world->shapes, sensor->shapeId ); + + // Swap overlap arrays + b3Array( b3Visitor ) temp = sensor->overlaps1; + sensor->overlaps1 = sensor->overlaps2; + sensor->overlaps2 = temp; + b3Array_Clear( sensor->overlaps2 ); + + // Append sensor hits + b3Array_Append( sensor->overlaps2, sensor->hits.data, sensor->hits.count ); + + // Clear the hits + b3Array_Clear( sensor->hits ); + + b3Body* body = b3Array_Get( world->bodies, sensorShape->bodyId ); + if ( body->setIndex == b3_disabledSet || ( sensorShape->flags & b3_enableSensorEvents ) == 0 ) + { + if ( sensor->overlaps1.count != 0 ) + { + // This sensor is dropping all overlaps because it has been disabled. + b3SetBit( &taskContext->eventBits, sensorIndex ); + } + continue; + } + + b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), b3Pos_zero ); + + b3SensorQueryContext queryContext = { + .world = world, + .taskContext = taskContext, + .sensor = sensor, + .sensorShape = sensorShape, + .transform = transform, + }; + + B3_ASSERT( sensorShape->sensorIndex == sensorIndex ); + b3AABB queryBounds = sensorShape->aabb; + + // Query all trees + b3DynamicTree_Query( trees + 0, queryBounds, sensorShape->filter.maskBits, false, b3SensorQueryCallback, &queryContext ); + b3DynamicTree_Query( trees + 1, queryBounds, sensorShape->filter.maskBits, false, b3SensorQueryCallback, &queryContext ); + b3DynamicTree_Query( trees + 2, queryBounds, sensorShape->filter.maskBits, false, b3SensorQueryCallback, &queryContext ); + + // Sort the overlaps to enable finding begin and end events. + qsort( sensor->overlaps2.data, sensor->overlaps2.count, sizeof( b3Visitor ), b3CompareVisitors ); + + // Remove duplicates from overlaps2 (sorted). Duplicates are possible due to the hit events appended earlier. + int uniqueCount = 0; + int overlapCount = sensor->overlaps2.count; + b3Visitor* overlapData = sensor->overlaps2.data; + for ( int i = 0; i < overlapCount; ++i ) + { + if ( uniqueCount == 0 || overlapData[i].shapeId != overlapData[uniqueCount - 1].shapeId ) + { + overlapData[uniqueCount] = overlapData[i]; + uniqueCount += 1; + } + } + sensor->overlaps2.count = uniqueCount; + + int count1 = sensor->overlaps1.count; + int count2 = sensor->overlaps2.count; + if ( count1 != count2 ) + { + // something changed + b3SetBit( &taskContext->eventBits, sensorIndex ); + } + else + { + for ( int i = 0; i < count1; ++i ) + { + b3Visitor* s1 = sensor->overlaps1.data + i; + b3Visitor* s2 = sensor->overlaps2.data + i; + + if ( s1->shapeId != s2->shapeId || s1->generation != s2->generation ) + { + // something changed + b3SetBit( &taskContext->eventBits, sensorIndex ); + break; + } + } + } + } + + b3TracyCZoneEnd( sensor_task ); +} + +void b3OverlapSensors( b3World* world ) +{ + int sensorCount = world->sensors.count; + if ( sensorCount == 0 ) + { + return; + } + + B3_ASSERT( world->workerCount > 0 ); + + b3TracyCZoneNC( overlap_sensors, "Sensors", b3_colorMediumPurple, true ); + + for ( int i = 0; i < world->workerCount; ++i ) + { + b3SetBitCountAndClear( &world->sensorTaskContexts.data[i].eventBits, sensorCount ); + } + + // Parallel-for sensors overlaps + int minRange = 16; + b3ParallelFor( world, b3SensorTask, sensorCount, minRange, world, "sensors" ); + + b3TracyCZoneNC( sensor_state, "Events", b3_colorLightSlateGray, true ); + + b3BitSet* bitSet = &world->sensorTaskContexts.data[0].eventBits; + for ( int i = 1; i < world->workerCount; ++i ) + { + b3InPlaceUnion( bitSet, &world->sensorTaskContexts.data[i].eventBits ); + } + + // Iterate sensors bits and publish events + // Process sensor state changes. Iterate over set bits + uint64_t* bits = bitSet->bits; + uint32_t blockCount = bitSet->blockCount; + + for ( uint32_t k = 0; k < blockCount; ++k ) + { + uint64_t word = bits[k]; + while ( word != 0 ) + { + uint32_t ctz = b3CTZ64( word ); + int sensorIndex = (int)( 64 * k + ctz ); + + b3Sensor* sensor = b3Array_Get( world->sensors, sensorIndex ); + b3Shape* sensorShape = b3Array_Get( world->shapes, sensor->shapeId ); + b3ShapeId sensorId = { sensor->shapeId + 1, world->worldId, sensorShape->generation }; + + int count1 = sensor->overlaps1.count; + int count2 = sensor->overlaps2.count; + const b3Visitor* refs1 = sensor->overlaps1.data; + const b3Visitor* refs2 = sensor->overlaps2.data; + + // overlaps1 can have overlaps that end + // overlaps2 can have overlaps that begin + int index1 = 0, index2 = 0; + while ( index1 < count1 && index2 < count2 ) + { + const b3Visitor* r1 = refs1 + index1; + const b3Visitor* r2 = refs2 + index2; + if ( r1->shapeId == r2->shapeId ) + { + if ( r1->generation < r2->generation ) + { + // end + b3ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation }; + b3SensorEndTouchEvent event = { + .sensorShapeId = sensorId, + .visitorShapeId = visitorId, + }; + b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event ); + index1 += 1; + } + else if ( r1->generation > r2->generation ) + { + // begin + b3ShapeId visitorId = { r2->shapeId + 1, world->worldId, r2->generation }; + b3SensorBeginTouchEvent event = { sensorId, visitorId }; + b3Array_Push( world->sensorBeginEvents, event ); + index2 += 1; + } + else + { + // persisted + index1 += 1; + index2 += 1; + } + } + else if ( r1->shapeId < r2->shapeId ) + { + // end + b3ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation }; + b3SensorEndTouchEvent event = { sensorId, visitorId }; + b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event ); + index1 += 1; + } + else + { + // begin + b3ShapeId visitorId = { r2->shapeId + 1, world->worldId, r2->generation }; + b3SensorBeginTouchEvent event = { sensorId, visitorId }; + b3Array_Push( world->sensorBeginEvents, event ); + index2 += 1; + } + } + + while ( index1 < count1 ) + { + // end + const b3Visitor* r1 = refs1 + index1; + b3ShapeId visitorId = { r1->shapeId + 1, world->worldId, r1->generation }; + b3SensorEndTouchEvent event = { sensorId, visitorId }; + b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event ); + index1 += 1; + } + + while ( index2 < count2 ) + { + // begin + const b3Visitor* r2 = refs2 + index2; + b3ShapeId visitorId = { r2->shapeId + 1, world->worldId, r2->generation }; + b3SensorBeginTouchEvent event = { sensorId, visitorId }; + b3Array_Push( world->sensorBeginEvents, event ); + index2 += 1; + } + + // Clear the smallest set bit + word = word & ( word - 1 ); + } + } + + b3TracyCZoneEnd( sensor_state ); + b3TracyCZoneEnd( overlap_sensors ); +} + +void b3DestroySensor( b3World* world, b3Shape* sensorShape ) +{ + b3Sensor* sensor = b3Array_Get( world->sensors, sensorShape->sensorIndex ); + for ( int i = 0; i < sensor->overlaps2.count; ++i ) + { + b3Visitor* ref = sensor->overlaps2.data + i; + b3SensorEndTouchEvent event = { + .sensorShapeId = + { + .index1 = sensorShape->id + 1, + .world0 = world->worldId, + .generation = sensorShape->generation, + }, + .visitorShapeId = + { + .index1 = ref->shapeId + 1, + .world0 = world->worldId, + .generation = ref->generation, + }, + }; + + b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event ); + } + + // Destroy sensor + b3Array_Destroy( sensor->hits ); + b3Array_Destroy( sensor->overlaps1 ); + b3Array_Destroy( sensor->overlaps2 ); + + int movedIndex = b3Array_RemoveSwap( world->sensors, sensorShape->sensorIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fixup moved sensor + b3Sensor* movedSensor = b3Array_Get( world->sensors, sensorShape->sensorIndex ); + b3Shape* otherSensorShape = b3Array_Get( world->shapes, movedSensor->shapeId ); + otherSensorShape->sensorIndex = sensorShape->sensorIndex; + } +} diff --git a/vendor/box3d/src/src/sensor.h b/vendor/box3d/src/src/sensor.h new file mode 100644 index 000000000..1a17366b7 --- /dev/null +++ b/vendor/box3d/src/src/sensor.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "bitset.h" +#include "container.h" + +typedef struct b3Shape b3Shape; +typedef struct b3World b3World; + +// Used to track shapes that hit sensors using time of impact +typedef struct b3SensorHit +{ + int sensorId; + int visitorId; +} b3SensorHit; + +typedef struct b3Visitor +{ + int shapeId; + uint16_t generation; +} b3Visitor; + +b3DeclareArray( b3Visitor ); + +typedef struct b3Sensor +{ + b3Array( b3Visitor ) hits; + b3Array( b3Visitor ) overlaps1; + b3Array( b3Visitor ) overlaps2; + int shapeId; +} b3Sensor; + +typedef struct b3SensorTaskContext +{ + b3BitSet eventBits; +} b3SensorTaskContext; + +void b3OverlapSensors( b3World* world ); +void b3DestroySensor( b3World* world, b3Shape* sensorShape ); diff --git a/vendor/box3d/src/src/shape.c b/vendor/box3d/src/src/shape.c new file mode 100644 index 000000000..e4d9d9e0c --- /dev/null +++ b/vendor/box3d/src/src/shape.c @@ -0,0 +1,2393 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "shape.h" + +#include "body.h" +#include "broad_phase.h" +#include "contact.h" +#include "physics_world.h" +#include "recording.h" +#include "sensor.h" + +// needed for dll export +#include "aabb.h" +#include "compound.h" + +#include "box3d/box3d.h" + +static b3Shape* b3GetShape( b3World* world, b3ShapeId shapeId ) +{ + int id = shapeId.index1 - 1; + b3Shape* shape = b3Array_Get( world->shapes, id ); + B3_ASSERT( shape->id == id && shape->generation == shapeId.generation ); + return shape; +} + +static float b3ComputeShapeMargin( b3Shape* shape ) +{ + float margin = 0.0f; + + switch ( shape->type ) + { + case b3_sphereShape: + { + margin = shape->sphere.radius; + } + break; + + case b3_capsuleShape: + { + margin = 0.5f * b3Distance( shape->capsule.center2, shape->capsule.center1 ) + shape->capsule.radius; + } + break; + + case b3_hullShape: + { + const b3HullData* hull = shape->hull; + const b3Vec3* points = b3GetHullPoints( hull ); + float maxExtentSqr = 0.0f; + int count = hull->vertexCount; + for ( int i = 0; i < count; ++i ) + { + float distSqr = b3DistanceSquared( points[i], hull->center ); + maxExtentSqr = b3MaxFloat( maxExtentSqr, distSqr ); + } + margin = sqrtf( maxExtentSqr ); + } + break; + + case b3_meshShape: + case b3_heightShape: + case b3_compoundShape: + { + // Static-only shapes: broadphase uses speculative distance for static + // proxies, so the per-shape margin is never consumed in practice. + // Return the cap so any incidental use is generous. + return B3_MAX_AABB_MARGIN; + } + + default: + B3_VALIDATE( false ); + return B3_MAX_AABB_MARGIN; + } + + return b3MinFloat( B3_MAX_AABB_MARGIN, B3_AABB_MARGIN_FRACTION * margin ); +} + +static void b3UpdateShapeAABBs( b3Shape* shape, b3WorldTransform transform, b3BodyType proxyType ) +{ + // Compute a bounding box with a speculative margin + const float speculativeDistance = B3_SPECULATIVE_DISTANCE; + const float aabbMargin = shape->aabbMargin; + + b3AABB aabb = b3ComputeFatShapeAABB( shape, transform, speculativeDistance ); + shape->aabb = aabb; + + // Smaller margin for static bodies. Cannot be zero due to TOI tolerance. + float margin = proxyType == b3_staticBody ? speculativeDistance : aabbMargin; + b3AABB fatAABB; + fatAABB.lowerBound.x = aabb.lowerBound.x - margin; + fatAABB.lowerBound.y = aabb.lowerBound.y - margin; + fatAABB.lowerBound.z = aabb.lowerBound.z - margin; + fatAABB.upperBound.x = aabb.upperBound.x + margin; + fatAABB.upperBound.y = aabb.upperBound.y + margin; + fatAABB.upperBound.z = aabb.upperBound.z + margin; + shape->fatAABB = fatAABB; +} + +static b3Shape* b3CreateShapeInternal( b3World* world, b3Body* body, b3WorldTransform bodyTransform, const b3ShapeDef* def, + const void* geometry, b3ShapeType shapeType, b3Transform shapeTransform, b3Vec3 scale, + bool haveShapeTransform ) +{ + int shapeId = b3AllocId( &world->shapeIdPool ); + + if ( shapeId == world->shapes.count ) + { + b3Array_Push( world->shapes, (b3Shape){ 0 } ); + } + else + { + B3_ASSERT( world->shapes.data[shapeId].id == B3_NULL_INDEX ); + } + + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + switch ( shapeType ) + { + case b3_capsuleShape: + shape->capsule = *(b3Capsule*)geometry; + break; + + case b3_compoundShape: + // Compounds must be a static and not a sensor + B3_ASSERT( body->type == b3_staticBody ); + B3_ASSERT( def->isSensor == false ); + shape->compound = (b3CompoundData*)geometry; + break; + + case b3_sphereShape: + shape->sphere = *(b3Sphere*)geometry; + break; + + case b3_hullShape: + if ( haveShapeTransform ) + { + // The transform and non-uniform scale are baked into fresh data, then shared. + b3HullData* baked = b3CloneAndTransformHull( (b3HullData*)geometry, shapeTransform, scale ); + if ( baked == NULL ) + { + // This can fail to produce a valid hull in extreme cases + b3FreeId( &world->shapeIdPool, shapeId ); + shape->id = B3_NULL_INDEX; + return NULL; + } + + shape->hull = b3AddOwnedHullToDatabase( world, baked ); + } + else + { + shape->hull = b3AddHullToDatabase( world, (const b3HullData*)geometry ); + } + break; + + case b3_meshShape: + { + shape->mesh.data = (b3MeshData*)geometry; + shape->mesh.scale = b3SafeScale( scale ); + } + break; + + case b3_heightShape: + shape->heightField = (b3HeightFieldData*)geometry; + break; + + default: + B3_ASSERT( false ); + break; + } + + shape->id = shapeId; + shape->bodyId = body->id; + shape->type = shapeType; + shape->density = def->density; + shape->explosionScale = def->explosionScale; + shape->filter = def->filter; + shape->userData = def->userData; + shape->userShape = NULL; + shape->flags = 0; + shape->flags |= def->enableSensorEvents ? b3_enableSensorEvents : 0; + shape->flags |= def->enableContactEvents ? b3_enableContactEvents : 0; + shape->flags |= def->enableCustomFiltering ? b3_enableCustomFiltering : 0; + shape->flags |= def->enableHitEvents ? b3_enableHitEvents : 0; + shape->flags |= def->enablePreSolveEvents ? b3_enablePreSolveEvents : 0; + shape->flags |= def->enableSpeculativeContact ? b3_enableSpeculative : 0; + shape->proxyKey = B3_NULL_INDEX; + shape->localCentroid = b3GetShapeCentroid( shape ); + shape->aabbMargin = b3ComputeShapeMargin( shape ); + shape->aabb = (b3AABB){ b3Vec3_zero, b3Vec3_zero }; + shape->fatAABB = (b3AABB){ b3Vec3_zero, b3Vec3_zero }; + shape->nameId = b3AddName( &world->names, def->name ); + shape->generation += 1; + + if ( shape->type == b3_compoundShape ) + { + // Own a copy of the compound materials so every shape frees its array the same way. Compounds + // are few, so the copy is cheap and avoids aliasing the geometry blob. + int materialCount = shape->compound->materialCount; + shape->materialCount = materialCount; + shape->materials = b3Alloc( materialCount * sizeof( b3SurfaceMaterial ) ); + memcpy( shape->materials, b3GetCompoundMaterials( shape->compound ), materialCount * sizeof( b3SurfaceMaterial ) ); + } + else if ( def->materialCount > 1 && def->materials != NULL ) + { + // Per triangle materials need a heap array. + shape->materialCount = def->materialCount; + shape->materials = b3Alloc( def->materialCount * sizeof( b3SurfaceMaterial ) ); + memcpy( shape->materials, def->materials, def->materialCount * sizeof( b3SurfaceMaterial ) ); + } + else + { + // The common case is one material, stored inline with no allocation. + shape->material = ( def->materialCount == 1 && def->materials != NULL ) ? def->materials[0] : def->baseMaterial; + shape->materialCount = 1; + shape->materials = NULL; + } + + if ( body->setIndex != b3_disabledSet ) + { + b3BodyType proxyType = body->type; + bool forcePairCreation = def->invokeContactCreation && shape->type != b3_compoundShape; + b3CreateShapeProxy( shape, &world->broadPhase, proxyType, bodyTransform, forcePairCreation ); + } + + // Add to shape doubly linked list + if ( body->headShapeId != B3_NULL_INDEX ) + { + b3Shape* headShape = b3Array_Get( world->shapes, body->headShapeId ); + headShape->prevShapeId = shapeId; + } + + shape->prevShapeId = B3_NULL_INDEX; + shape->nextShapeId = body->headShapeId; + body->headShapeId = shapeId; + body->shapeCount += 1; + + if ( def->isSensor ) + { + shape->sensorIndex = world->sensors.count; + b3Sensor* sensor = b3Array_Emplace( world->sensors ); + b3Array_CreateN( sensor->hits, 4 ); + b3Array_CreateN( sensor->overlaps1, 16 ); + b3Array_CreateN( sensor->overlaps2, 16 ); + sensor->shapeId = shapeId; + } + else + { + shape->sensorIndex = B3_NULL_INDEX; + } + + b3ValidateSolverSets( world ); + + return shape; +} + +static b3ShapeId b3CreateShape( b3BodyId bodyId, const b3ShapeDef* def, const void* geometry, b3ShapeType shapeType, + b3Transform transform, b3Vec3 scale, bool haveTransform ) +{ + B3_CHECK_DEF( def ); + B3_ASSERT( b3IsValidFloat( def->density ) && def->density >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->baseMaterial.friction ) && def->baseMaterial.friction >= 0.0f ); + B3_ASSERT( b3IsValidFloat( def->baseMaterial.restitution ) && def->baseMaterial.restitution >= 0.0f ); + + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world == NULL ) + { + return (b3ShapeId){ 0 }; + } + + if ( world->shapes.count == B3_MAX_SHAPES && world->shapeIdPool.freeArray.count == 0 ) + { + B3_ASSERT( false ); + return b3_nullShapeId; + } + + b3Body* body = b3GetBodyFullId( world, bodyId ); + if ( body->type != b3_staticBody && ( shapeType == b3_compoundShape || shapeType == b3_heightShape ) ) + { + // Compound and height shapes must be on static bodies. + return b3_nullShapeId; + } + + world->locked = true; + + b3WorldTransform bodyTransform = b3GetBodyTransformQuick( world, body ); + + b3Shape* shape = + b3CreateShapeInternal( world, body, bodyTransform, def, geometry, shapeType, transform, scale, haveTransform ); + + if ( shape == NULL ) + { + world->locked = false; + return b3_nullShapeId; + } + + if ( def->updateBodyMass == true ) + { + b3UpdateBodyMassData( world, body ); + } + else if ( ( body->flags & b3_dirtyMass ) == 0 ) + { + body->flags |= b3_dirtyMass; + b3SyncBodyFlags( world, body ); + } + + b3ValidateSolverSets( world ); + + b3ShapeId id = { shape->id + 1, bodyId.world0, shape->generation }; + + world->locked = false; + + return id; +} + +b3ShapeId b3CreateSphereShape( b3BodyId bodyId, const b3ShapeDef* def, const b3Sphere* sphere ) +{ + b3ShapeId shapeId = b3CreateShape( bodyId, def, sphere, b3_sphereShape, b3Transform_identity, b3Vec3_one, false ); + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL ) + { + B3_REC_CREATE( world, CreateSphereShape, shapeId, bodyId, *def, *sphere ); + } + } + return shapeId; +} + +b3ShapeId b3CreateCapsuleShape( b3BodyId bodyId, const b3ShapeDef* def, const b3Capsule* capsule ) +{ + float lengthSqr = b3DistanceSquared( capsule->center1, capsule->center2 ); + b3ShapeId shapeId; + if ( lengthSqr <= B3_LINEAR_SLOP * B3_LINEAR_SLOP ) + { + b3Sphere sphere = { b3Lerp( capsule->center1, capsule->center2, 0.5f ), capsule->radius }; + shapeId = b3CreateShape( bodyId, def, &sphere, b3_sphereShape, b3Transform_identity, b3Vec3_one, false ); + } + else + { + shapeId = b3CreateShape( bodyId, def, capsule, b3_capsuleShape, b3Transform_identity, b3Vec3_one, false ); + } + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL ) + { + B3_REC_CREATE( world, CreateCapsuleShape, shapeId, bodyId, *def, *capsule ); + } + } + return shapeId; +} + +b3ShapeId b3CreateHullShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HullData* hull ) +{ + B3_VALIDATE( b3IsValidHull( hull ) ); + B3_VALIDATE( hull->hash != 0 ); + b3ShapeId shapeId = b3CreateShape( bodyId, def, hull, b3_hullShape, b3Transform_identity, b3Vec3_one, false ); + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL && world->recording != NULL ) + { + uint32_t geometryId = b3RecInternHull( world->recording, hull ); + b3RecArgs_CreateHullShape createArgs = { bodyId, *def, geometryId }; + b3RecWriteRet_CreateHullShape( world->recording, &createArgs, shapeId ); + } + } + return shapeId; +} + +b3ShapeId b3CreateTransformedHullShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HullData* hull, b3Transform transform, + b3Vec3 scale ) +{ + B3_VALIDATE( b3IsValidHull( hull ) ); + b3ShapeId shapeId = b3CreateShape( bodyId, def, hull, b3_hullShape, transform, scale, true ); + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL && world->recording != NULL ) + { + // The transform and scale are baked into fresh hull data at create time. Record the baked hull + // as a plain hull shape so replay rebuilds identical geometry with no rebake, and the keyframe + // registry, which interns the live baked hull, stays seeded. + b3Shape* shape = b3Array_Get( world->shapes, shapeId.index1 - 1 ); + uint32_t geometryId = b3RecInternHull( world->recording, shape->hull ); + b3RecArgs_CreateHullShape createArgs = { bodyId, *def, geometryId }; + b3RecWriteRet_CreateHullShape( world->recording, &createArgs, shapeId ); + } + } + return shapeId; +} + +b3ShapeId b3CreateMeshShape( b3BodyId bodyId, const b3ShapeDef* def, const b3MeshData* mesh, b3Vec3 scale ) +{ + B3_VALIDATE( b3IsValidMesh( mesh ) ); + B3_VALIDATE( mesh->hash != 0 ); + b3ShapeId shapeId = b3CreateShape( bodyId, def, mesh, b3_meshShape, b3Transform_identity, scale, true ); + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL && world->recording != NULL ) + { + uint32_t geometryId = b3RecInternMesh( world->recording, mesh ); + b3RecArgs_CreateMeshShape createArgs = { bodyId, *def, geometryId, scale }; + b3RecWriteRet_CreateMeshShape( world->recording, &createArgs, shapeId ); + } + } + return shapeId; +} + +b3ShapeId b3CreateHeightFieldShape( b3BodyId bodyId, const b3ShapeDef* def, const b3HeightFieldData* heightField ) +{ + B3_VALIDATE( heightField->hash != 0 ); + b3ShapeId shapeId = b3CreateShape( bodyId, def, heightField, b3_heightShape, b3Transform_identity, b3Vec3_one, false ); + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL && world->recording != NULL ) + { + uint32_t geometryId = b3RecInternHeightField( world->recording, heightField ); + b3RecArgs_CreateHeightFieldShape createArgs = { bodyId, *def, geometryId }; + b3RecWriteRet_CreateHeightFieldShape( world->recording, &createArgs, shapeId ); + } + } + return shapeId; +} + +b3ShapeId b3CreateCompoundShape( b3BodyId bodyId, b3ShapeDef* def, const b3CompoundData* compound ) +{ + b3ShapeId shapeId = b3CreateShape( bodyId, def, compound, b3_compoundShape, b3Transform_identity, b3Vec3_one, false ); + if ( shapeId.index1 != 0 ) + { + b3World* world = b3GetUnlockedWorld( bodyId.world0 ); + if ( world != NULL && world->recording != NULL ) + { + uint32_t geometryId = b3RecInternCompound( world->recording, compound ); + b3RecArgs_CreateCompoundShape createArgs = { bodyId, *def, geometryId }; + b3RecWriteRet_CreateCompoundShape( world->recording, &createArgs, shapeId ); + } + } + return shapeId; +} + +// Destroy a shape on a body. This doesn't need to be called when destroying a body. +static void b3DestroyShapeInternal( b3World* world, b3Shape* shape, b3Body* body, bool wakeBodies ) +{ + int shapeId = shape->id; + + // Remove the shape from the body's doubly linked list. + if ( shape->prevShapeId != B3_NULL_INDEX ) + { + b3Shape* prevShape = b3Array_Get( world->shapes, shape->prevShapeId ); + prevShape->nextShapeId = shape->nextShapeId; + } + + if ( shape->nextShapeId != B3_NULL_INDEX ) + { + b3Shape* nextShape = b3Array_Get( world->shapes, shape->nextShapeId ); + nextShape->prevShapeId = shape->prevShapeId; + } + + if ( shapeId == body->headShapeId ) + { + body->headShapeId = shape->nextShapeId; + } + + body->shapeCount -= 1; + + // Remove from broad-phase. + b3DestroyShapeProxy( shape, &world->broadPhase ); + + // Destroy any contacts associated with the shape. + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + contactKey = contact->edges[edgeIndex].nextKey; + + if ( contact->shapeIdA == shapeId || contact->shapeIdB == shapeId ) + { + b3DestroyContact( world, contact, wakeBodies ); + } + } + + if ( shape->sensorIndex != B3_NULL_INDEX ) + { + b3Sensor* sensor = b3Array_Get( world->sensors, shape->sensorIndex ); + for ( int i = 0; i < sensor->overlaps2.count; ++i ) + { + b3Visitor* ref = sensor->overlaps2.data + i; + b3SensorEndTouchEvent event = { + .sensorShapeId = + { + .index1 = shapeId + 1, + .world0 = world->worldId, + .generation = shape->generation, + }, + .visitorShapeId = + { + .index1 = ref->shapeId + 1, + .world0 = world->worldId, + .generation = ref->generation, + }, + }; + + b3Array_Push( world->sensorEndEvents[world->endEventArrayIndex], event ); + } + + // Destroy sensor + b3Array_Destroy( sensor->hits ); + b3Array_Destroy( sensor->overlaps1 ); + b3Array_Destroy( sensor->overlaps2 ); + + int movedIndex = b3Array_RemoveSwap( world->sensors, shape->sensorIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fixup moved sensor + b3Sensor* movedSensor = b3Array_Get( world->sensors, shape->sensorIndex ); + b3Shape* otherSensorShape = b3Array_Get( world->shapes, movedSensor->shapeId ); + otherSensorShape->sensorIndex = shape->sensorIndex; + } + } + + // Destroy every shape member from b3Alloc + b3DestroyShapeAllocations( world, shape ); + + // Return shape to free list. + b3FreeId( &world->shapeIdPool, shapeId ); + shape->id = B3_NULL_INDEX; + + b3ValidateSolverSets( world ); +} + +void b3DestroyShape( b3ShapeId shapeId, bool updateBodyMass ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, DestroyShape, shapeId, updateBodyMass ); + + world->locked = true; + + b3Shape* shape = b3GetShape( world, shapeId ); + + // need to wake bodies because this might be a static body + bool wakeBodies = true; + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3DestroyShapeInternal( world, shape, body, wakeBodies ); + + if ( updateBodyMass == true ) + { + b3UpdateBodyMassData( world, body ); + } + + world->locked = false; +} + +b3AABB b3ComputeShapeAABB( const b3Shape* shape, b3Transform transform ) +{ + switch ( shape->type ) + { + case b3_capsuleShape: + return b3ComputeCapsuleAABB( &shape->capsule, transform ); + + case b3_compoundShape: + return b3ComputeCompoundAABB( shape->compound, transform ); + + case b3_heightShape: + return b3ComputeHeightFieldAABB( shape->heightField, transform ); + + case b3_hullShape: + return b3ComputeHullAABB( shape->hull, transform ); + + case b3_meshShape: + return b3ComputeMeshAABB( shape->mesh.data, transform, shape->mesh.scale ); + + case b3_sphereShape: + return b3ComputeSphereAABB( &shape->sphere, transform ); + + default: + { + B3_ASSERT( false ); + b3AABB empty = { transform.p, transform.p }; + return empty; + } + } +} + +b3AABB b3ComputeFatShapeAABB( const b3Shape* shape, b3WorldTransform transform, float extra ) +{ + b3Vec3 r = { extra, extra, extra }; +#if defined( BOX3D_DOUBLE_PRECISION ) + // Build the box in the body local frame, inflate, then translate by the double origin and + // round outward. Inflating before the single rounding matters far from the origin where the + // float margin would otherwise vanish. + b3Transform rotation = { b3Vec3_zero, transform.q }; + b3AABB localBox = b3ComputeShapeAABB( shape, rotation ); + localBox.lowerBound = b3Sub( localBox.lowerBound, r ); + localBox.upperBound = b3Add( localBox.upperBound, r ); + return b3OffsetAABB( localBox, transform.p ); +#else + b3AABB aabb = b3ComputeShapeAABB( shape, transform ); + aabb.lowerBound = b3Sub( aabb.lowerBound, r ); + aabb.upperBound = b3Add( aabb.upperBound, r ); + return aabb; +#endif +} + +b3AABB b3ComputeSweptShapeAABB( const b3Shape* shape, const b3Sweep* sweep, float time ) +{ + B3_ASSERT( 0.0f <= time && time <= 1.0f ); + b3Transform xf1 = { b3Sub( sweep->c1, b3RotateVector( sweep->q1, sweep->localCenter ) ), sweep->q1 }; + b3Transform xf2 = b3GetSweepTransform( sweep, time ); + + switch ( shape->type ) + { + case b3_capsuleShape: + return b3ComputeSweptCapsuleAABB( &shape->capsule, xf1, xf2 ); + + case b3_hullShape: + return b3ComputeSweptHullAABB( shape->hull, xf1, xf2 ); + + case b3_sphereShape: + return b3ComputeSweptSphereAABB( &shape->sphere, xf1, xf2 ); + + default: + B3_ASSERT( false ); + return (b3AABB){ xf1.p, xf1.p }; + } +} + +b3Vec3 b3GetShapeCentroid( const b3Shape* shape ) +{ + switch ( shape->type ) + { + case b3_capsuleShape: + return b3Lerp( shape->capsule.center1, shape->capsule.center2, 0.5f ); + case b3_compoundShape: + { + b3AABB aabb = b3ComputeCompoundAABB( shape->compound, b3Transform_identity ); + return b3AABB_Center( aabb ); + } + case b3_sphereShape: + return shape->sphere.center; + case b3_hullShape: + return shape->hull->center; + case b3_meshShape: + { + b3AABB aabb = b3ComputeMeshAABB( shape->mesh.data, b3Transform_identity, shape->mesh.scale ); + return b3AABB_Center( aabb ); + } + case b3_heightShape: + { + b3AABB aabb = b3ComputeHeightFieldAABB( shape->heightField, b3Transform_identity ); + return b3AABB_Center( aabb ); + } + default: + return b3Vec3_zero; + } +} + +float b3GetShapeArea( const b3Shape* shape ) +{ + // todo_erin fix these + switch ( shape->type ) + { + case b3_capsuleShape: + return 2.0f * b3Length( b3Sub( shape->capsule.center1, shape->capsule.center2 ) ) + + 2.0f * B3_PI * shape->capsule.radius; + + case b3_hullShape: + return shape->hull->surfaceArea; + + case b3_sphereShape: + return 2.0f * B3_PI * shape->sphere.radius; + + default: + return 0.0f; + } +} + +// This projects the shape surface area onto a plane +float b3GetShapeProjectedArea( const b3Shape* shape, b3Vec3 planeNormal ) +{ + switch ( shape->type ) + { + case b3_capsuleShape: + { + float radius = shape->capsule.radius; + b3Vec3 axis = b3Sub( shape->capsule.center2, shape->capsule.center1 ); + float projectedLength = b3Length( b3Cross( axis, planeNormal ) ); + float cylinderArea = 2.0f * radius * projectedLength; + float sphereArea = B3_PI * radius * radius; + return sphereArea + cylinderArea; + } + + case b3_hullShape: + return b3ComputeHullProjectedArea( shape->hull, planeNormal ); + + case b3_sphereShape: + return B3_PI * shape->sphere.radius * shape->sphere.radius; + + default: + return 0.0f; + } +} + +b3MassData b3ComputeShapeMass( const b3Shape* shape ) +{ + switch ( shape->type ) + { + case b3_capsuleShape: + return b3ComputeCapsuleMass( &shape->capsule, shape->density ); + + case b3_hullShape: + return b3ComputeHullMass( shape->hull, shape->density ); + + case b3_sphereShape: + return b3ComputeSphereMass( &shape->sphere, shape->density ); + + default: + return (b3MassData){ 0 }; + } +} + +b3ShapeExtent b3ComputeShapeExtent( const b3Shape* shape, b3Vec3 localCenter ) +{ + b3ShapeExtent extent = { 0 }; + + switch ( shape->type ) + { + case b3_capsuleShape: + { + float radius = shape->capsule.radius; + extent.minExtent = radius; + b3Vec3 c1 = b3Sub( shape->capsule.center1, localCenter ); + b3Vec3 c2 = b3Sub( shape->capsule.center2, localCenter ); + b3Vec3 r = { radius, radius, radius }; + extent.maxExtent = b3Add( b3Max( c1, c2 ), r ); + } + break; + + case b3_compoundShape: + { + // This is shouldn't be needed but here for completeness + b3AABB aabb = b3ComputeCompoundAABB( shape->compound, b3Transform_identity ); + float r1 = b3Length( b3Sub( aabb.lowerBound, localCenter ) ); + float r2 = b3Length( b3Sub( aabb.upperBound, localCenter ) ); + extent.minExtent = b3MinFloat( r1, r2 ); + b3Vec3 p = b3FarthestPointOnAABB( aabb, localCenter ); + extent.maxExtent = b3Abs( b3Sub( p, localCenter ) ); + } + break; + + case b3_sphereShape: + { + float radius = shape->sphere.radius; + extent.minExtent = radius; + b3Vec3 r = { radius, radius, radius }; + b3Vec3 p = b3Add( b3Sub( shape->sphere.center, localCenter ), r ); + extent.maxExtent = b3Abs( b3Sub( p, localCenter ) ); + } + break; + + case b3_hullShape: + extent = b3ComputeHullExtent( shape->hull, localCenter ); + break; + + case b3_meshShape: + { + // This is needed for kinematic mesh sleeping + b3AABB aabb = b3ComputeMeshAABB( shape->mesh.data, b3Transform_identity, shape->mesh.scale ); + float r1 = b3Length( b3Sub( aabb.lowerBound, localCenter ) ); + float r2 = b3Length( b3Sub( aabb.upperBound, localCenter ) ); + extent.minExtent = b3MinFloat( r1, r2 ); + b3Vec3 p = b3FarthestPointOnAABB( aabb, localCenter ); + extent.maxExtent = b3Abs( p ); + } + break; + + default: + break; + } + + return extent; +} + +b3CastOutput b3RayCastShape( const b3Shape* shape, b3Transform transform, const b3RayCastInput* input ) +{ + b3RayCastInput localInput = *input; + localInput.origin = b3InvTransformPoint( transform, input->origin ); + localInput.translation = b3InvRotateVector( transform.q, input->translation ); + + b3CastOutput output = { 0 }; + switch ( shape->type ) + { + case b3_capsuleShape: + output = b3RayCastCapsule( &shape->capsule, &localInput ); + break; + case b3_compoundShape: + output = b3RayCastCompound( shape->compound, &localInput ); + break; + case b3_sphereShape: + output = b3RayCastSphere( &shape->sphere, &localInput ); + break; + case b3_hullShape: + output = b3RayCastHull( shape->hull, &localInput ); + break; + case b3_meshShape: + output = b3RayCastMesh( &shape->mesh, &localInput ); + break; + case b3_heightShape: + output = b3RayCastHeightField( shape->heightField, &localInput ); + break; + default: + return output; + } + + output.point = b3TransformPoint( transform, output.point ); + output.normal = b3RotateVector( transform.q, output.normal ); + return output; +} + +b3CastOutput b3ShapeCastShape( const b3Shape* shape, b3Transform transform, const b3ShapeCastInput* input ) +{ + b3ShapeCastInput localInput = *input; + b3Vec3 localPoints[B3_MAX_SHAPE_CAST_POINTS]; + + localInput.proxy.count = b3MinInt( input->proxy.count, B3_MAX_SHAPE_CAST_POINTS ); + for ( int i = 0; i < localInput.proxy.count; ++i ) + { + localPoints[i] = b3InvTransformPoint( transform, input->proxy.points[i] ); + } + + localInput.proxy.points = localPoints; + localInput.translation = b3InvRotateVector( transform.q, input->translation ); + + b3CastOutput output = { 0 }; + switch ( shape->type ) + { + case b3_capsuleShape: + output = b3ShapeCastCapsule( &shape->capsule, &localInput ); + break; + + case b3_compoundShape: + output = b3ShapeCastCompound( shape->compound, &localInput ); + break; + + case b3_heightShape: + output = b3ShapeCastHeightField( shape->heightField, &localInput ); + break; + + case b3_hullShape: + output = b3ShapeCastHull( shape->hull, &localInput ); + break; + + case b3_meshShape: + output = b3ShapeCastMesh( &shape->mesh, &localInput ); + break; + + case b3_sphereShape: + output = b3ShapeCastSphere( &shape->sphere, &localInput ); + break; + default: + return output; + } + + output.point = b3TransformPoint( transform, output.point ); + output.normal = b3RotateVector( transform.q, output.normal ); + return output; +} + +bool b3OverlapShape( const b3Shape* shape, b3Transform transform, const b3ShapeProxy* proxy ) +{ + b3ShapeType type = shape->type; + switch ( type ) + { + case b3_capsuleShape: + return b3OverlapCapsule( &shape->capsule, transform, proxy ); + + case b3_compoundShape: + return b3OverlapCompound( shape->compound, transform, proxy ); + + case b3_heightShape: + return b3OverlapHeightField( shape->heightField, transform, proxy ); + + case b3_hullShape: + return b3OverlapHull( shape->hull, transform, proxy ); + + case b3_meshShape: + return b3OverlapMesh( &shape->mesh, transform, proxy ); + + case b3_sphereShape: + return b3OverlapSphere( &shape->sphere, transform, proxy ); + + default: + B3_ASSERT( false ); + return false; + } + +#if 0 + b3Vec3 localPoints[B3_MAX_SHAPE_CAST_POINTS]; + b3ShapeProxy localProxy; + + b3Transform invTransform = b3InvertTransform( transform ); + b3Matrix3 R = b3MakeMatrixFromQuat( invTransform.q ); + + localProxy.count = b3MinInt( proxy->count, B3_MAX_SHAPE_CAST_POINTS ); + for ( int i = 0; i < localProxy.count; ++i ) + { + localPoints[i] = b3Add( b3MulMV( R, proxy->points[i] ), invTransform.p ); + } + + localProxy.points = localPoints; + localProxy.radius = proxy->radius; + + if ( type == b3_meshShape ) + { + return b3OverlapMesh( &localProxy, shape->mesh.data, shape->mesh.scale ); + } + + B3_ASSERT( type == b3_heightShape ); + + return b3OverlapHeightField( &localProxy, shape->heightField ); +#endif +} + +int b3CollideMover( b3PlaneResult* planes, int planeCapacity, const b3Shape* shape, b3Transform transform, + const b3Capsule* mover ) +{ + if ( planeCapacity == 0 ) + { + return 0; + } + + b3Capsule localMover; + localMover.center1 = b3InvTransformPoint( transform, mover->center1 ); + localMover.center2 = b3InvTransformPoint( transform, mover->center2 ); + localMover.radius = mover->radius; + + int planeCount = 0; + switch ( shape->type ) + { + case b3_capsuleShape: + planeCount = b3CollideMoverAndCapsule( planes, &shape->capsule, &localMover ); + break; + + case b3_compoundShape: + planeCount = b3CollideMoverAndCompound( planes, planeCapacity, shape->compound, &localMover ); + break; + + case b3_sphereShape: + planeCount = b3CollideMoverAndSphere( planes, &shape->sphere, &localMover ); + break; + + case b3_hullShape: + planeCount = b3CollideMoverAndHull( planes, shape->hull, &localMover ); + break; + + case b3_meshShape: + planeCount = b3CollideMoverAndMesh( planes, planeCapacity, &shape->mesh, &localMover ); + break; + + case b3_heightShape: + planeCount = b3CollideMoverAndHeightField( planes, planeCapacity, shape->heightField, &localMover ); + break; + + default: + B3_ASSERT( false ); + break; + } + + for ( int i = 0; i < planeCount; ++i ) + { + planes[i].plane.normal = b3RotateVector( transform.q, planes[i].plane.normal ); + planes[i].point = b3TransformPoint( transform, planes[i].point ); + } + + return planeCount; +} + +void b3CreateShapeProxy( b3Shape* shape, b3BroadPhase* bp, b3BodyType type, b3WorldTransform transform, bool forcePairCreation ) +{ + B3_ASSERT( shape->proxyKey == B3_NULL_INDEX ); + + b3UpdateShapeAABBs( shape, transform, type ); + + // Create proxies in the broad-phase. + shape->proxyKey = + b3BroadPhase_CreateProxy( bp, type, shape->fatAABB, shape->filter.categoryBits, shape->id, forcePairCreation ); + B3_ASSERT( B3_PROXY_TYPE( shape->proxyKey ) < b3_bodyTypeCount ); +} + +void b3DestroyShapeProxy( b3Shape* shape, b3BroadPhase* bp ) +{ + if ( shape->proxyKey != B3_NULL_INDEX ) + { + b3BroadPhase_DestroyProxy( bp, shape->proxyKey ); + shape->proxyKey = B3_NULL_INDEX; + } +} + +static void b3DestroyShapeAllocationForShapeChange( b3World* world, b3Shape* shape ) +{ + b3ShapeType type = shape->type; + switch ( type ) + { + case b3_hullShape: + b3RemoveHullFromDatabase( world, shape->hull ); + shape->hull = NULL; + break; + + default: + break; + } + + if ( shape->userShape != NULL ) + { + world->destroyDebugShape( shape->userShape, world->userDebugShapeContext ); + shape->userShape = NULL; + } +} + +void b3DestroyShapeAllocations( b3World* world, b3Shape* shape ) +{ + b3DestroyShapeAllocationForShapeChange( world, shape ); + + if ( shape->materials != NULL ) + { + B3_ASSERT( shape->materialCount > 0 ); + b3Free( shape->materials, shape->materialCount * sizeof( b3SurfaceMaterial ) ); + shape->materials = NULL; + shape->materialCount = 0; + } + + // Name is stored inline. Sensor data is destroyed elsewhere +} + +b3ShapeProxy b3MakeShapeProxy( const b3Shape* shape ) +{ + switch ( shape->type ) + { + case b3_capsuleShape: + return (b3ShapeProxy){ &shape->capsule.center1, 2, shape->capsule.radius }; + + case b3_sphereShape: + return (b3ShapeProxy){ &shape->sphere.center, 1, shape->sphere.radius }; + + case b3_hullShape: + { + const b3HullData* hull = shape->hull; + const b3Vec3* points = b3GetHullPoints( hull ); + return (b3ShapeProxy){ points, hull->vertexCount, 0.0f }; + } + + default: + { + B3_ASSERT( false ); + return (b3ShapeProxy){ 0 }; + } + } +} + +b3ShapeProxy b3MakeLocalProxy( const b3ShapeProxy* proxy, b3Transform transform, b3Vec3* buffer ) +{ + b3Transform invTransform = b3InvertTransform( transform ); + b3Matrix3 R = b3MakeMatrixFromQuat( invTransform.q ); + + int count = b3MinInt( proxy->count, B3_MAX_SHAPE_CAST_POINTS ); + for ( int i = 0; i < count; ++i ) + { + buffer[i] = b3Add( b3MulMV( R, proxy->points[i] ), invTransform.p ); + } + + return (b3ShapeProxy){ + .points = buffer, + .count = count, + .radius = proxy->radius, + }; +} + +b3AABB b3ComputeProxyAABB( const b3ShapeProxy* proxy ) +{ + const b3Vec3* points = proxy->points; + b3AABB aabb = { + .lowerBound = points[0], + .upperBound = points[0], + }; + + for ( int i = 1; i < proxy->count; ++i ) + { + aabb.lowerBound = b3Min( aabb.lowerBound, points[i] ); + aabb.upperBound = b3Max( aabb.upperBound, points[i] ); + } + + b3Vec3 r = { proxy->radius, proxy->radius, proxy->radius }; + aabb.lowerBound = b3Sub( aabb.lowerBound, r ); + aabb.upperBound = b3Add( aabb.upperBound, r ); + return aabb; +} + +b3BodyId b3Shape_GetBody( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return b3MakeBodyId( world, shape->bodyId ); +} + +b3WorldId b3Shape_GetWorld( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + return (b3WorldId){ (uint16_t)( shapeId.world0 + 1 ), world->generation }; +} + +void b3Shape_SetUserData( b3ShapeId shapeId, void* userData ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + shape->userData = userData; +} + +void* b3Shape_GetUserData( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->userData; +} + +void b3Shape_SetName( b3ShapeId shapeId, const char* name ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + + B3_REC( world, ShapeSetName, shapeId, name ); + + b3Shape* shape = b3GetShape( world, shapeId ); + shape->nameId = b3AddName( &world->names, name ); +} + +const char* b3Shape_GetName( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return b3FindNameWithDefault( &world->names, shape->nameId, "" ); +} + +bool b3Shape_IsSensor( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->sensorIndex != B3_NULL_INDEX; +} + +// todo no tests +b3WorldCastOutput b3Shape_RayCast( b3ShapeId shapeId, b3Pos origin, b3Vec3 translation ) +{ + B3_ASSERT( b3IsValidPosition( origin ) ); + B3_ASSERT( b3IsValidVec3( translation ) ); + + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + + // Re-center on the origin so the cast runs in float precision far from the world origin + b3Transform transform = b3ToRelativeTransform( b3GetBodyTransform( world, shape->bodyId ), origin ); + + // The ray starts at the origin, so its origin in the re-centered frame is zero + b3RayCastInput input = { b3Vec3_zero, translation, 1.0f }; + + // Lift the re-centered float result back to a world position + b3CastOutput local = b3RayCastShape( shape, transform, &input ); + b3WorldCastOutput output; + output.normal = local.normal; + output.point = b3OffsetPos( origin, local.point ); + output.fraction = local.fraction; + output.iterations = local.iterations; + output.triangleIndex = local.triangleIndex; + output.childIndex = local.childIndex; + output.materialIndex = local.materialIndex; + output.hit = local.hit; + + return output; +} + +void b3Shape_SetDensity( b3ShapeId shapeId, float density, bool updateBodyMass ) +{ + B3_ASSERT( b3IsValidFloat( density ) && density >= 0.0f ); + + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeSetDensity, shapeId, density, updateBodyMass ); + + b3Shape* shape = b3GetShape( world, shapeId ); + if ( density == shape->density ) + { + // early return to avoid expensive function + return; + } + + shape->density = density; + + if ( updateBodyMass == true ) + { + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + b3UpdateBodyMassData( world, body ); + } +} + +float b3Shape_GetDensity( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->density; +} + +void b3Shape_SetFriction( b3ShapeId shapeId, float friction ) +{ + B3_ASSERT( b3IsValidFloat( friction ) && friction >= 0.0f ); + b3World* world = b3GetWorld( shapeId.world0 ); + B3_REC( world, ShapeSetFriction, shapeId, friction ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type != b3_compoundShape ); + b3GetShapeMaterials( shape )[0].friction = friction; +} + +float b3Shape_GetFriction( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return b3GetShapeMaterials( shape )[0].friction; +} + +void b3Shape_SetRestitution( b3ShapeId shapeId, float restitution ) +{ + B3_ASSERT( b3IsValidFloat( restitution ) && restitution >= 0.0f ); + b3World* world = b3GetWorld( shapeId.world0 ); + B3_REC( world, ShapeSetRestitution, shapeId, restitution ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type != b3_compoundShape ); + b3GetShapeMaterials( shape )[0].restitution = restitution; +} + +float b3Shape_GetRestitution( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return b3GetShapeMaterials( shape )[0].restitution; +} + +void b3Shape_SetSurfaceMaterial( b3ShapeId shapeId, b3SurfaceMaterial surfaceMaterial ) +{ + B3_ASSERT( b3IsValidFloat( surfaceMaterial.friction ) && surfaceMaterial.friction >= 0.0f ); + B3_ASSERT( b3IsValidFloat( surfaceMaterial.restitution ) && surfaceMaterial.restitution >= 0.0f ); + B3_ASSERT( b3IsValidFloat( surfaceMaterial.rollingResistance ) && surfaceMaterial.rollingResistance >= 0.0f ); + B3_ASSERT( b3IsValidVec3( surfaceMaterial.tangentVelocity ) ); + + b3World* world = b3GetWorld( shapeId.world0 ); + B3_REC( world, ShapeSetSurfaceMaterial, shapeId, surfaceMaterial ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type != b3_compoundShape ); + b3GetShapeMaterials( shape )[0] = surfaceMaterial; +} + +b3SurfaceMaterial b3Shape_GetSurfaceMaterial( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return b3GetShapeMaterials( shape )[0]; +} + +int b3Shape_GetMeshMaterialCount( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->materialCount; +} + +void b3Shape_SetMeshMaterial( b3ShapeId shapeId, b3SurfaceMaterial surfaceMaterial, int index ) +{ + B3_ASSERT( b3IsValidFloat( surfaceMaterial.friction ) && surfaceMaterial.friction >= 0.0f ); + B3_ASSERT( b3IsValidFloat( surfaceMaterial.restitution ) && surfaceMaterial.restitution >= 0.0f ); + B3_ASSERT( b3IsValidFloat( surfaceMaterial.rollingResistance ) && surfaceMaterial.rollingResistance >= 0.0f ); + B3_ASSERT( b3IsValidVec3( surfaceMaterial.tangentVelocity ) ); + + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + + B3_ASSERT( 0 <= index && index < shape->materialCount ); + B3_ASSERT( shape->type != b3_compoundShape ); + b3GetShapeMaterials( shape )[index] = surfaceMaterial; +} + +b3SurfaceMaterial b3Shape_GetMeshSurfaceMaterial( b3ShapeId shapeId, int index ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( 0 <= index && index < shape->materialCount ); + return b3GetShapeMaterials( shape )[index]; +} + +b3Filter b3Shape_GetFilter( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->filter; +} + +static void b3ResetProxy( b3World* world, b3Shape* shape, bool wakeBodies, bool destroyProxy ) +{ + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + + int shapeId = shape->id; + + // destroy all contacts associated with this shape + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + contactKey = contact->edges[edgeIndex].nextKey; + + if ( contact->shapeIdA == shapeId || contact->shapeIdB == shapeId ) + { + b3DestroyContact( world, contact, wakeBodies ); + } + } + + b3WorldTransform transform = b3GetBodyTransformQuick( world, body ); + if ( shape->proxyKey != B3_NULL_INDEX ) + { + b3BodyType proxyType = B3_PROXY_TYPE( shape->proxyKey ); + b3UpdateShapeAABBs( shape, transform, proxyType ); + + if ( destroyProxy ) + { + b3BroadPhase_DestroyProxy( &world->broadPhase, shape->proxyKey ); + + bool forcePairCreation = true; + shape->proxyKey = b3BroadPhase_CreateProxy( &world->broadPhase, proxyType, shape->fatAABB, shape->filter.categoryBits, + shapeId, forcePairCreation ); + } + else + { + b3BroadPhase_MoveProxy( &world->broadPhase, shape->proxyKey, shape->fatAABB ); + } + } + else + { + b3BodyType proxyType = body->type; + b3UpdateShapeAABBs( shape, transform, proxyType ); + } + + b3ValidateSolverSets( world ); +} + +void b3Shape_SetFilter( b3ShapeId shapeId, b3Filter filter, bool invokeContacts ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeSetFilter, shapeId, filter, invokeContacts ); + + b3Shape* shape = b3GetShape( world, shapeId ); + if ( filter.maskBits == shape->filter.maskBits && filter.categoryBits == shape->filter.categoryBits && + filter.groupIndex == shape->filter.groupIndex ) + { + return; + } + + shape->filter = filter; + + if ( invokeContacts ) + { + world->locked = true; + bool wakeBodies = true; + + // If the category bits change, I need to destroy the proxy because it affects the tree sorting. + bool destroyProxy = filter.categoryBits == shape->filter.categoryBits; + + // need to wake bodies because a filter change may destroy contacts + b3ResetProxy( world, shape, wakeBodies, destroyProxy ); + world->locked = false; + } + + // note: this does not immediately update sensor overlaps. Instead sensor + // overlaps are updated the next time step +} + +void b3Shape_EnableSensorEvents( b3ShapeId shapeId, bool flag ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeEnableSensorEvents, shapeId, flag ); + + b3Shape* shape = b3GetShape( world, shapeId ); + shape->flags = flag ? shape->flags | b3_enableSensorEvents : shape->flags & ~b3_enableSensorEvents; +} + +bool b3Shape_AreSensorEventsEnabled( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->flags & b3_enableSensorEvents; +} + +void b3Shape_EnableContactEvents( b3ShapeId shapeId, bool flag ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeEnableContactEvents, shapeId, flag ); + + b3Shape* shape = b3GetShape( world, shapeId ); + shape->flags = flag ? shape->flags | b3_enableContactEvents : shape->flags & ~b3_enableContactEvents; +} + +bool b3Shape_AreContactEventsEnabled( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->flags & b3_enableContactEvents; +} + +void b3Shape_EnablePreSolveEvents( b3ShapeId shapeId, bool flag ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeEnablePreSolveEvents, shapeId, flag ); + + b3Shape* shape = b3GetShape( world, shapeId ); + shape->flags = flag ? shape->flags | b3_enablePreSolveEvents : shape->flags & ~b3_enablePreSolveEvents; +} + +bool b3Shape_ArePreSolveEventsEnabled( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->flags & b3_enablePreSolveEvents; +} + +void b3Shape_EnableHitEvents( b3ShapeId shapeId, bool flag ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeEnableHitEvents, shapeId, flag ); + + b3Shape* shape = b3GetShape( world, shapeId ); + shape->flags = flag ? shape->flags | b3_enableHitEvents : shape->flags & ~b3_enableHitEvents; +} + +bool b3Shape_AreHitEventsEnabled( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->flags & b3_enableHitEvents; +} + +b3ShapeType b3Shape_GetType( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->type; +} + +b3Sphere b3Shape_GetSphere( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type == b3_sphereShape ); + return shape->sphere; +} + +b3Capsule b3Shape_GetCapsule( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type == b3_capsuleShape ); + return shape->capsule; +} + +const b3HullData* b3Shape_GetHull( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type == b3_hullShape ); + return shape->hull; +} + +b3Mesh b3Shape_GetMesh( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type == b3_meshShape ); + return shape->mesh; +} + +const b3HeightFieldData* b3Shape_GetHeightField( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + b3Shape* shape = b3GetShape( world, shapeId ); + B3_ASSERT( shape->type == b3_heightShape ); + return shape->heightField; +} + +void b3Shape_SetSphere( b3ShapeId shapeId, const b3Sphere* sphere ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeSetSphere, shapeId, *sphere ); + + world->locked = true; + + b3Shape* shape = b3GetShape( world, shapeId ); + + b3DestroyShapeAllocationForShapeChange( world, shape ); + + shape->sphere = *sphere; + shape->type = b3_sphereShape; + shape->aabbMargin = b3ComputeShapeMargin( shape ); + + // need to wake bodies so they can react to the shape change + bool wakeBodies = true; + bool destroyProxy = true; + b3ResetProxy( world, shape, wakeBodies, destroyProxy ); + + world->locked = false; +} + +void b3Shape_SetCapsule( b3ShapeId shapeId, const b3Capsule* capsule ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeSetCapsule, shapeId, *capsule ); + + world->locked = true; + + b3Shape* shape = b3GetShape( world, shapeId ); + + b3DestroyShapeAllocationForShapeChange( world, shape ); + + shape->capsule = *capsule; + shape->type = b3_capsuleShape; + shape->aabbMargin = b3ComputeShapeMargin( shape ); + + // need to wake bodies so they can react to the shape change + bool wakeBodies = true; + bool destroyProxy = true; + b3ResetProxy( world, shape, wakeBodies, destroyProxy ); + + world->locked = false; +} + +void b3Shape_SetHull( b3ShapeId shapeId, const b3HullData* hull ) +{ + B3_VALIDATE( b3IsValidHull( hull ) ); + B3_VALIDATE( hull->hash != 0 ); + + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + world->locked = true; + + b3Shape* shape = b3GetShape( world, shapeId ); + + // Acquire the new hull before releasing the old so the input may safely alias + // the shape's current shared data. + const b3HullData* data = b3AddHullToDatabase( world, hull ); + + // Same shared hull, avoid destroying contacts and recreating the proxy + if ( shape->type == b3_hullShape && data == shape->hull ) + { + b3RemoveHullFromDatabase( world, data ); + world->locked = false; + return; + } + + b3DestroyShapeAllocationForShapeChange( world, shape ); + + shape->hull = data; + shape->type = b3_hullShape; + shape->aabbMargin = b3ComputeShapeMargin( shape ); + + // need to wake bodies so they can react to the shape change + bool wakeBodies = true; + bool destroyProxy = true; + b3ResetProxy( world, shape, wakeBodies, destroyProxy ); + + world->locked = false; +} + +void b3Shape_SetMesh( b3ShapeId shapeId, const b3MeshData* meshData, b3Vec3 scale ) +{ + B3_ASSERT( b3IsValidVec3( scale ) ); + B3_ASSERT( meshData != NULL && b3IsValidMesh( meshData ) ); + + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + world->locked = true; + + b3Shape* shape = b3GetShape( world, shapeId ); + + b3DestroyShapeAllocationForShapeChange( world, shape ); + + shape->mesh.data = meshData; + shape->mesh.scale = b3SafeScale( scale ); + shape->type = b3_meshShape; + shape->aabbMargin = b3ComputeShapeMargin( shape ); + + // need to wake bodies so they can react to the shape change + bool wakeBodies = true; + bool destroyProxy = true; + b3ResetProxy( world, shape, wakeBodies, destroyProxy ); + + world->locked = false; +} + +int b3Shape_GetContactCapacity( b3ShapeId shapeId ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return 0; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + if ( shape->sensorIndex != B3_NULL_INDEX ) + { + return 0; + } + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + + // Conservative and fast + return body->contactCount; +} + +int b3Shape_GetContactData( b3ShapeId shapeId, b3ContactData* contactData, int capacity ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return 0; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + if ( shape->sensorIndex != B3_NULL_INDEX ) + { + return 0; + } + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + int contactKey = body->headContactKey; + int index = 0; + while ( contactKey != B3_NULL_INDEX && index < capacity ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + + // Does contact involve this shape and is it touching? + if ( ( contact->shapeIdA == shapeId.index1 - 1 || contact->shapeIdB == shapeId.index1 - 1 ) && + ( contact->flags & b3_contactTouchingFlag ) != 0 ) + { + b3Shape* shapeA = world->shapes.data + contact->shapeIdA; + b3Shape* shapeB = world->shapes.data + contact->shapeIdB; + + contactData[index].contactId = (b3ContactId){ contact->contactId + 1, shapeId.world0, 0, contact->generation }; + contactData[index].shapeIdA = (b3ShapeId){ shapeA->id + 1, shapeId.world0, shapeA->generation }; + contactData[index].shapeIdB = (b3ShapeId){ shapeB->id + 1, shapeId.world0, shapeB->generation }; + contactData[index].manifolds = contact->manifolds; + contactData[index].manifoldCount = contact->manifoldCount; + index += 1; + } + + contactKey = contact->edges[edgeIndex].nextKey; + } + + B3_ASSERT( index <= capacity ); + + return index; +} + +int b3Shape_GetSensorCapacity( b3ShapeId shapeId ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return 0; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + if ( shape->sensorIndex == B3_NULL_INDEX ) + { + return 0; + } + + b3Sensor* sensor = b3Array_Get( world->sensors, shape->sensorIndex ); + return sensor->overlaps2.count; +} + +int b3Shape_GetSensorData( b3ShapeId shapeId, b3ShapeId* visitorIds, int capacity ) +{ + b3World* world = b3GetUnlockedWorld( shapeId.world0 ); + if ( world == NULL ) + { + return 0; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + if ( shape->sensorIndex == B3_NULL_INDEX ) + { + return 0; + } + + b3Sensor* sensor = b3Array_Get( world->sensors, shape->sensorIndex ); + + int count = b3MinInt( sensor->overlaps2.count, capacity ); + b3Visitor* refs = sensor->overlaps2.data; + for ( int i = 0; i < count; ++i ) + { + b3ShapeId visitorId = { + .index1 = refs[i].shapeId + 1, + .world0 = shapeId.world0, + .generation = refs[i].generation, + }; + + visitorIds[i] = visitorId; + } + + return count; +} + +b3AABB b3Shape_GetAABB( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + if ( world == NULL ) + { + return (b3AABB){ 0 }; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + return shape->aabb; +} + +b3MassData b3Shape_ComputeMassData( b3ShapeId shapeId ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + if ( world == NULL ) + { + return (b3MassData){ 0 }; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + return b3ComputeShapeMass( shape ); +} + +b3Vec3 b3Shape_GetClosestPoint( b3ShapeId shapeId, b3Vec3 target ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + if ( world == NULL ) + { + return b3Vec3_zero; + } + + b3Shape* shape = b3GetShape( world, shapeId ); + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + // Low level closest point query is a documented float carve-out far from the origin + b3Transform transform = b3ToRelativeTransform( b3GetBodyTransformQuick( world, body ), b3Pos_zero ); + + b3DistanceInput input; + input.proxyA = b3MakeShapeProxy( shape ); + input.proxyB = (b3ShapeProxy){ &target, 1, 0.0f }; + input.transform = b3InvMulTransforms( transform, b3Transform_identity ); + input.useRadii = true; + + b3SimplexCache cache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + + // Witness point comes back in frame A, lift it back to the query frame + return b3TransformPoint( transform, output.pointA ); +} + +#define B3_DEBUG_WIND 0 + +// https://en.wikipedia.org/wiki/Density_of_air +// https://www.engineeringtoolbox.com/wind-load-d_1775.html +// force = 0.5 * air_density * velocity^2 * area +// https://en.wikipedia.org/wiki/Lift_(force) +void b3Shape_ApplyWind( b3ShapeId shapeId, b3Vec3 wind, float drag, float lift, float maxSpeed, bool wake ) +{ + b3World* world = b3GetWorld( shapeId.world0 ); + if ( world == NULL ) + { + return; + } + + B3_REC( world, ShapeApplyWind, shapeId, wind, drag, lift, maxSpeed, wake ); + + b3Shape* shape = b3GetShape( world, shapeId ); + + b3ShapeType shapeType = shape->type; + if ( shapeType != b3_sphereShape && shapeType != b3_capsuleShape && shapeType != b3_hullShape ) + { + return; + } + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + + if ( body->type != b3_dynamicBody ) + { + return; + } + + if ( body->setIndex == b3_disabledSet ) + { + return; + } + + if ( body->setIndex >= b3_firstSleepingSet && wake == false ) + { + return; + } + + b3BodySim* sim = b3GetBodySim( world, body ); + + if ( body->setIndex != b3_awakeSet ) + { + // Must wake for state to exist + b3WakeBodyWithLock( world, body ); + } + + B3_ASSERT( body->setIndex == b3_awakeSet ); + + b3BodyState* state = b3GetBodyState( world, body ); + // Only the rotation is used below, so the demoted world transform is exact + b3Transform transform = b3ToRelativeTransform( sim->transform, b3Pos_zero ); + + float lengthUnits = b3GetLengthUnitsPerMeter(); + float volumeUnits = lengthUnits * lengthUnits * lengthUnits; + + float airDensity = 1.2250f / ( volumeUnits ); + + b3Vec3 force = { 0 }; + b3Vec3 torque = { 0 }; + + switch ( shape->type ) + { + case b3_sphereShape: + { + float radius = shape->sphere.radius; + b3Vec3 centroid = shape->localCentroid; + b3Vec3 lever = b3RotateVector( transform.q, b3Sub( centroid, sim->localCenter ) ); + b3Vec3 shapeVelocity = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, lever ) ); + b3Vec3 relativeVelocity = b3MulSub( wind, drag, shapeVelocity ); + float speed; + b3Vec3 direction = b3GetLengthAndNormalize( &speed, relativeVelocity ); + speed = b3MinFloat( speed, maxSpeed ); + float projectedArea = B3_PI * radius * radius; + force = b3MulSV( 0.5f * airDensity * projectedArea * speed * speed, direction ); + torque = b3Cross( lever, force ); + } + break; + + case b3_capsuleShape: + { + b3Vec3 centroid = shape->localCentroid; + b3Vec3 lever = b3RotateVector( transform.q, b3Sub( centroid, sim->localCenter ) ); + b3Vec3 shapeVelocity = b3Add( state->linearVelocity, b3Cross( state->angularVelocity, lever ) ); + b3Vec3 relativeVelocity = b3MulSub( wind, drag, shapeVelocity ); + float speed; + b3Vec3 direction = b3GetLengthAndNormalize( &speed, relativeVelocity ); + speed = b3MinFloat( speed, maxSpeed ); + + b3Vec3 d = b3Sub( shape->capsule.center2, shape->capsule.center1 ); + d = b3RotateVector( transform.q, d ); + + float radius = shape->capsule.radius; + float projectedArea = B3_PI * radius * radius + 2.0f * radius * b3Length( b3Cross( d, direction ) ); + + // Normal that opposes the wind + b3Vec3 e = b3Normalize( d ); + b3Vec3 normal = b3Sub( b3MulSV( b3Dot( direction, e ), e ), direction ); + + // portion of wind that is perpendicular to surface + b3Vec3 liftDirection = b3Cross( b3Cross( normal, direction ), direction ); + + float forceMagnitude = 0.5f * airDensity * projectedArea * speed * speed; + force = b3MulSV( forceMagnitude, b3MulAdd( direction, lift, liftDirection ) ); + + b3Vec3 edgeLever = b3MulAdd( lever, radius, normal ); + torque = b3Cross( edgeLever, force ); + } + break; + + case b3_hullShape: + { + b3Matrix3 matrix = b3MakeMatrixFromQuat( transform.q ); + + int faceCount = shape->hull->faceCount; + const b3Vec3* points = b3GetHullPoints( shape->hull ); + const b3HullFace* faces = b3GetHullFaces( shape->hull ); + const b3HullHalfEdge* edges = b3GetHullEdges( shape->hull ); + const b3Plane* planes = b3GetHullPlanes( shape->hull ); + + b3Vec3 linearVelocity = state->linearVelocity; + b3Vec3 angularVelocity = state->angularVelocity; + b3Vec3 localCenterOfMass = sim->localCenter; + + for ( int i = 0; i < faceCount; ++i ) + { + const b3HullFace* face = faces + i; + const b3HullHalfEdge* edge1 = edges + face->edge; + const b3HullHalfEdge* edge2 = edges + edge1->next; + const b3HullHalfEdge* edge3 = edges + edge2->next; + + B3_ASSERT( edge1 != edge3 ); + B3_ASSERT( edge1->origin < shape->hull->vertexCount ); + B3_ASSERT( edge2->origin < shape->hull->vertexCount ); + + b3Vec3 localPoint1 = points[edge1->origin]; + b3Vec3 localPoint2 = points[edge2->origin]; + b3Vec3 v1 = b3MulMV( matrix, localPoint1 ); + b3Vec3 v2 = b3MulMV( matrix, localPoint2 ); + b3Vec3 normal = b3MulMV( matrix, planes[i].normal ); + + do + { + B3_ASSERT( edge3->origin < shape->hull->vertexCount ); + b3Vec3 localPoint3 = points[edge3->origin]; + b3Vec3 v3 = b3MulMV( matrix, localPoint3 ); + + // Triangle center + b3Vec3 localCenter = b3MulSV( 0.333333f, b3Add( localPoint1, b3Add( localPoint2, localPoint3 ) ) ); + + // Lever arm from center of mass to triangle center in world space + b3Vec3 lever = b3MulMV( matrix, b3Sub( localCenter, localCenterOfMass ) ); + + // Velocity of the triangle center in world space + b3Vec3 centerVelocity = b3Add( linearVelocity, b3Cross( angularVelocity, lever ) ); + + b3Vec3 relativeVelocity = b3MulSub( wind, drag, centerVelocity ); + float speed; + b3Vec3 direction = b3GetLengthAndNormalize( &speed, relativeVelocity ); + + // Check for back-side + if ( b3Dot( normal, direction ) < -FLT_EPSILON ) + { + float projectedArea = -0.5f * b3Dot( b3Cross( b3Sub( v2, v1 ), b3Sub( v3, v1 ) ), direction ); + B3_VALIDATE( projectedArea >= -FLT_EPSILON ); + + b3Vec3 liftDirection = b3Cross( b3Cross( normal, direction ), direction ); + + speed = b3MinFloat( speed, maxSpeed ); + + float forceMagnitude = 0.5f * airDensity * projectedArea * speed * speed; + b3Vec3 deltaForce = b3MulSV( forceMagnitude, b3MulAdd( direction, lift, liftDirection ) ); + b3Vec3 deltaTorque = b3Cross( lever, deltaForce ); + + force = b3Add( force, deltaForce ); + torque = b3Add( torque, deltaTorque ); + +#if B3_DEBUG_WIND + int lineIndex = world->taskContexts.data[0].lineCount; + if ( lineIndex < B3_DEBUG_LINE_CAPACITY ) + { + b3DebugLine* line = world->taskContexts.data[0].lines + lineIndex; + line->p1 = b3OffsetPos( sim->transform.p, b3MulMV( matrix, localCenter ) ); + line->p2 = b3OffsetPos( line->p1, deltaForce ); + line->label = i; + line->color = b3_colorBlanchedAlmond; + world->taskContexts.data[0].lineCount += 1; + } +#endif + } + + edge2 = edge3; + edge3 = edges + edge3->next; + v2 = v3; + localPoint2 = localPoint3; + } + while ( edge1 != edge3 ); + } + } + break; + + default: + break; + } + + sim->force = b3Add( sim->force, force ); + sim->torque = b3Add( sim->torque, torque ); +} + +typedef struct b3MeshImpactContext +{ + b3TOIInput toiInput; + b3TOIOutput toiOutput; + // Centroid of shape in body B local space + b3Vec3 localCentroidB; + // Centroid of shape at beginning and end of sweep in mesh local space. Used for early out. + b3Vec3 meshLocalCentroidB1, meshLocalCentroidB2; + float fallbackRadius; + bool isSensor; + + int visitCount; +} b3MeshImpactContext; + +static bool b3MeshTimeOfImpactFcn( b3Vec3 a, b3Vec3 b, b3Vec3 c, int triangleIndex, void* context ) +{ + B3_UNUSED( triangleIndex ); + + b3MeshImpactContext* toiContext = context; + + toiContext->visitCount += 1; + + // Early out for parallel movement + b3Vec3 c1 = toiContext->meshLocalCentroidB1; + b3Vec3 c2 = toiContext->meshLocalCentroidB2; + + b3Vec3 n = b3Normalize( b3Cross( b3Sub( b, a ), b3Sub( c, a ) ) ); + float offset1 = b3Dot( n, b3Sub( c1, a ) ); + float offset2 = b3Dot( n, b3Sub( c2, a ) ); + + if ( offset1 < 0.0f ) + { + // Started behind or finished in front + return true; + } + + if ( toiContext->isSensor == false && offset1 - offset2 < toiContext->fallbackRadius && offset2 > toiContext->fallbackRadius ) + { + // Finished in front + return true; + } + + b3Vec3 triangle[3] = { a, b, c }; + toiContext->toiInput.proxyA.points = triangle; + toiContext->toiInput.proxyA.count = 3; + + b3TOIOutput output = b3TimeOfImpact( &toiContext->toiInput ); + + // It is possible for a hit at fraction == 0 + + if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction ) + { + toiContext->toiOutput = output; + toiContext->toiInput.maxFraction = output.fraction; + } + else if ( 0.0f == output.fraction ) + { + // fallback to TOI of a small circle around the fast shape centroid + b3TOIInput fallbackInput = toiContext->toiInput; + fallbackInput.proxyB = (b3ShapeProxy){ &toiContext->localCentroidB, 1, toiContext->fallbackRadius + B3_LINEAR_SLOP }; + output = b3TimeOfImpact( &fallbackInput ); + + if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction ) + { + toiContext->toiOutput = output; + toiContext->toiInput.maxFraction = output.fraction; + toiContext->toiOutput.usedFallback = true; + } + } + + // Continue the query + return true; +} + +typedef struct b3CompoundImpactContext +{ + b3TOIInput toiInput; + b3TOIOutput toiOutput; + b3Transform compoundTransform; + + // Bounds local to compound + b3AABB localSweepBoundsB; + + // Centroid of shape in body B local space + b3Vec3 localCentroidB; + float fallbackRadius; +} b3CompoundImpactContext; + +// Implements b3CompoundQueryFcn +static bool b3CompoundTimeOfImpactFcn( const b3CompoundData* compound, int childIndex, void* context ) +{ + b3CompoundImpactContext* toiContext = (b3CompoundImpactContext*)context; + + b3ChildShape child = b3GetCompoundChild( compound, childIndex ); + + b3TOIOutput output = { 0 }; + toiContext->toiInput.sweepA = b3MakeCompoundChildSweep( toiContext->compoundTransform, child.transform ); + + switch ( child.type ) + { + case b3_capsuleShape: + { + toiContext->toiInput.proxyA.points = &child.capsule.center1; + toiContext->toiInput.proxyA.count = 2; + toiContext->toiInput.proxyA.radius = child.capsule.radius; + output = b3TimeOfImpact( &toiContext->toiInput ); + } + break; + + case b3_hullShape: + { + toiContext->toiInput.proxyA.points = b3GetHullPoints( child.hull ); + toiContext->toiInput.proxyA.count = child.hull->vertexCount; + toiContext->toiInput.proxyA.radius = 0.0f; + output = b3TimeOfImpact( &toiContext->toiInput ); + } + break; + + case b3_meshShape: + { + b3MeshImpactContext meshContext = { 0 }; + meshContext.toiInput = toiContext->toiInput; + meshContext.isSensor = false; + meshContext.localCentroidB = toiContext->localCentroidB; + meshContext.fallbackRadius = toiContext->fallbackRadius; + + b3Transform meshWorldTransform = b3MulTransforms( toiContext->compoundTransform, child.transform ); + + const b3Sweep* sweepB = &toiContext->toiInput.sweepB; + b3Transform xfB1 = { + .p = b3Sub( sweepB->c1, b3RotateVector( sweepB->q1, sweepB->localCenter ) ), + .q = sweepB->q1, + }; + + b3Transform xfB2 = { + .p = b3Sub( sweepB->c2, b3RotateVector( sweepB->q2, sweepB->localCenter ) ), + .q = sweepB->q2, + }; + + meshContext.meshLocalCentroidB1 = + b3InvTransformPoint( meshWorldTransform, b3TransformPoint( xfB1, meshContext.localCentroidB ) ); + meshContext.meshLocalCentroidB2 = + b3InvTransformPoint( meshWorldTransform, b3TransformPoint( xfB2, meshContext.localCentroidB ) ); + + // Bounds local to mesh + b3AABB localBounds = b3AABB_Transform( b3InvertTransform( child.transform ), toiContext->localSweepBoundsB ); + + b3QueryMesh( &child.mesh, localBounds, b3MeshTimeOfImpactFcn, &meshContext ); + + output = meshContext.toiOutput; + } + break; + + case b3_sphereShape: + { + toiContext->toiInput.proxyA.points = &child.sphere.center; + toiContext->toiInput.proxyA.count = 1; + toiContext->toiInput.proxyA.radius = child.sphere.radius; + output = b3TimeOfImpact( &toiContext->toiInput ); + } + break; + + default: + B3_ASSERT( false ); + break; + } + + if ( 0.0f < output.fraction && output.fraction < toiContext->toiInput.maxFraction ) + { + toiContext->toiOutput = output; + toiContext->toiInput.maxFraction = output.fraction; + } + + // Clear this to be safe + toiContext->toiInput.proxyA = (b3ShapeProxy){ 0 }; + + // Continue the query + return true; +} + +b3TOIOutput b3ShapeTimeOfImpact( b3Shape* shapeA, b3Shape* shapeB, b3Sweep* sweepA, b3Sweep* sweepB, float maxFraction ) +{ + bool isSensor = shapeA->sensorIndex != B3_NULL_INDEX; + + b3ShapeType typeA = shapeA->type; + if ( typeA == b3_compoundShape ) + { + // todo implement b3CompoundTimeOfImpact + b3CompoundImpactContext context = { 0 }; + context.toiInput.proxyB = b3MakeShapeProxy( shapeB ); + context.toiInput.sweepB = *sweepB; + context.toiInput.maxFraction = maxFraction; + + context.compoundTransform = (b3Transform){ + .p = sweepA->c1, + .q = sweepA->q1, + }; + + b3Vec3 localCentroidB = b3GetShapeCentroid( shapeB ); + context.localCentroidB = localCentroidB; + + b3ShapeExtent extents = b3ComputeShapeExtent( shapeB, context.localCentroidB ); + context.fallbackRadius = b3MaxFloat( 0.75f * extents.minExtent, B3_SPECULATIVE_DISTANCE ); + + // Swept bounds of shapeB + b3AABB bounds = b3ComputeSweptShapeAABB( shapeB, sweepB, maxFraction ); + + // Bounds local to mesh + b3AABB localBounds = b3AABB_Transform( b3InvertTransform( context.compoundTransform ), bounds ); + context.localSweepBoundsB = localBounds; + + b3QueryCompound( shapeA->compound, localBounds, b3CompoundTimeOfImpactFcn, &context ); + + return context.toiOutput; + } + + if ( typeA == b3_heightShape || typeA == b3_meshShape ) + { + // todo implement b3MeshTimeOfImpact and b3HeightFieldTimeOfImpact + // Note: assuming mesh is static + + uint64_t ticks = b3GetTicks(); + + b3MeshImpactContext context = { 0 }; + context.toiInput.sweepA = *sweepA; + context.toiInput.proxyA.count = 3; + context.toiInput.proxyB = b3MakeShapeProxy( shapeB ); + context.toiInput.sweepB = *sweepB; + context.toiInput.maxFraction = maxFraction; + context.isSensor = isSensor; + + b3Vec3 localCentroidB = b3GetShapeCentroid( shapeB ); + context.localCentroidB = localCentroidB; + + // Assume mesh is static + b3Transform xfA = { + .p = b3Sub( sweepA->c1, b3RotateVector( sweepA->q1, sweepA->localCenter ) ), + .q = sweepA->q1, + }; + + b3Transform xfB1 = { + .p = b3Sub( sweepB->c1, b3RotateVector( sweepB->q1, sweepB->localCenter ) ), + .q = sweepB->q1, + }; + + b3Transform xfB2 = { + .p = b3Sub( sweepB->c2, b3RotateVector( sweepB->q2, sweepB->localCenter ) ), + .q = sweepB->q2, + }; + + context.meshLocalCentroidB1 = b3InvTransformPoint( xfA, b3TransformPoint( xfB1, localCentroidB ) ); + context.meshLocalCentroidB2 = b3InvTransformPoint( xfA, b3TransformPoint( xfB2, localCentroidB ) ); + + b3ShapeExtent extents = b3ComputeShapeExtent( shapeB, context.localCentroidB ); + context.fallbackRadius = b3MaxFloat( 0.5f * extents.minExtent, B3_LINEAR_SLOP ); + + // Swept bounds of shapeB + // todo pass in xfA to get local bounds directly + b3AABB bounds = b3ComputeSweptShapeAABB( shapeB, sweepB, maxFraction ); + + // Bounds local to mesh + b3AABB localBounds = b3AABB_Transform( b3InvertTransform( xfA ), bounds ); + + if ( typeA == b3_meshShape ) + { + b3QueryMesh( &shapeA->mesh, localBounds, b3MeshTimeOfImpactFcn, &context ); + } + else if ( typeA == b3_heightShape ) + { + b3QueryHeightField( shapeA->heightField, localBounds, b3MeshTimeOfImpactFcn, &context ); + } + + float ms = b3GetMilliseconds( ticks ); + if ( ms > 1000.0f * b3GetStallThreshold() ) + { + b3Log( "CCD stall: visited %d triangles", context.visitCount ); + } + + return context.toiOutput; + } + + B3_ASSERT( shapeB->type != b3_compoundShape && shapeB->type != b3_meshShape && shapeB->type != b3_heightShape ); + + b3TOIInput input; + input.proxyA = b3MakeShapeProxy( shapeA ); + input.proxyB = b3MakeShapeProxy( shapeB ); + input.sweepA = *sweepA; + input.sweepB = *sweepB; + input.maxFraction = maxFraction; + + b3TOIOutput output = b3TimeOfImpact( &input ); + +#if 0 + // todo I'm not sure this is worth it for convex vs convex. + if (0.0f < output.fraction && output.fraction < maxFraction) + { + return output; + } + + if (0.0f == output.fraction) + { + // fallback to TOI of a small circle around the fast shape centroid + b3Vec3 centroid = b3GetShapeCentroid( shapeB ); + input.proxyB = ( b3ShapeProxy ){ ¢roid, 1, B3_SPECULATIVE_DISTANCE }; + output = b3TimeOfImpact( &input ); + return output; + } +#endif + + return output; +} + +// Resolve the user material id for a hit point on the given shape. Mesh/heightfield shapes +// use the manifold-point triangleIndex to pick a per-triangle material. Compound shapes use +// the contact's childIndex to find the participating child, then for a mesh child apply the +// child's materialIndices indirection on top of the per-triangle index. Convex shapes fall +// back to materials[0]. childIndex is unused for non-compound shapes. +uint64_t b3GetShapeUserMaterialId( const b3Shape* shape, int childIndex, int triangleIndex ) +{ + if ( shape->materialCount == 0 ) + { + return 0; + } + + int materialIndex = 0; + if ( shape->type == b3_meshShape ) + { + const uint8_t* indices = b3GetMeshMaterialIndices( shape->mesh.data ); + if ( indices != NULL ) + { + materialIndex = indices[triangleIndex]; + } + } + else if ( shape->type == b3_heightShape ) + { + materialIndex = b3GetHeightFieldMaterial( shape->heightField, triangleIndex ); + } + else if ( shape->type == b3_compoundShape ) + { + b3ChildShape child = b3GetCompoundChild( shape->compound, childIndex ); + if ( child.type == b3_meshShape ) + { + const uint8_t* indices = b3GetMeshMaterialIndices( child.mesh.data ); + int meshMaterialIndex = indices != NULL ? indices[triangleIndex] : 0; + meshMaterialIndex = b3ClampInt( meshMaterialIndex, 0, B3_MAX_COMPOUND_MESH_MATERIALS - 1 ); + materialIndex = child.materialIndices[meshMaterialIndex]; + } + else + { + materialIndex = child.materialIndices[0]; + } + } + + materialIndex = b3ClampInt( materialIndex, 0, shape->materialCount - 1 ); + return b3GetShapeMaterials( shape )[materialIndex].userMaterialId; +} diff --git a/vendor/box3d/src/src/shape.h b/vendor/box3d/src/src/shape.h new file mode 100644 index 000000000..519ce8694 --- /dev/null +++ b/vendor/box3d/src/src/shape.h @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "math_internal.h" + +#include "box3d/types.h" + +#include + +typedef struct b3BroadPhase b3BroadPhase; +typedef struct b3World b3World; + +typedef enum b3ShapeFlags +{ + b3_enableSensorEvents = 0x01, + b3_enableContactEvents = 0x02, + b3_enableCustomFiltering = 0x04, + b3_enableHitEvents = 0x08, + b3_enablePreSolveEvents = 0x10, + b3_enlargedAABB = 0x20, + b3_enableSpeculative = 0x40, +} b3ShapeFlags; + +typedef struct b3Shape +{ + int id; + int bodyId; + int prevShapeId; + int nextShapeId; + int sensorIndex; + int proxyKey; + b3ShapeType type; + float density; + float explosionScale; + float aabbMargin; + + b3AABB aabb; + b3AABB fatAABB; + b3Vec3 localCentroid; + + int materialCount; + b3SurfaceMaterial material; + b3SurfaceMaterial* materials; + + b3Filter filter; + void* userData; + void* userShape; + + uint32_t nameId; + uint16_t generation; + + // b3ShapeFlags + uint8_t flags; + + union + { + b3Capsule capsule; + b3Sphere sphere; + const b3HullData* hull; + b3Mesh mesh; + const b3HeightFieldData* heightField; + const b3CompoundData* compound; + }; + +} b3Shape; + +// A single material shape keeps its material inline. Multi material meshes and compounds own a heap +// array. Reach the materials the same way for both: a single material shape presents its inline +// material as a one element array. Do not cache the pointer, the shapes array can move. +static inline b3SurfaceMaterial* b3GetShapeMaterials( const b3Shape* shape ) +{ + return shape->materials != NULL ? shape->materials : (b3SurfaceMaterial*)&shape->material; +} + +void b3CreateShapeProxy( b3Shape* shape, b3BroadPhase* bp, b3BodyType type, b3WorldTransform transform, bool forcePairCreation ); +void b3DestroyShapeProxy( b3Shape* shape, b3BroadPhase* bp ); + +void b3DestroyShapeAllocations( b3World* world, b3Shape* shape ); + +b3MassData b3ComputeShapeMass( const b3Shape* shape ); +b3ShapeExtent b3ComputeShapeExtent( const b3Shape* shape, b3Vec3 localCenter ); + +b3AABB b3ComputeSweptSphereAABB( const b3Sphere* shape, b3Transform xf1, b3Transform xf2 ); +b3AABB b3ComputeSweptCapsuleAABB( const b3Capsule* shape, b3Transform xf1, b3Transform xf2 ); + +b3AABB b3ComputeShapeAABB( const b3Shape* shape, b3Transform transform ); + +// Conservative world AABB for a shape inflated by extra margin. In double precision mode the +// box is built in the body local frame, translated by the double origin, and rounded outward. +b3AABB b3ComputeFatShapeAABB( const b3Shape* shape, b3WorldTransform transform, float extra ); +b3AABB b3ComputeSweptShapeAABB( const b3Shape* shape, const b3Sweep* sweep, float time ); +b3Vec3 b3GetShapeCentroid( const b3Shape* shape ); +float b3GetShapeArea( const b3Shape* shape ); +float b3GetShapeProjectedArea( const b3Shape* shape, b3Vec3 planeNormal ); +uint64_t b3GetShapeUserMaterialId( const b3Shape* shape, int childIndex, int triangleIndex ); + +b3ShapeProxy b3MakeShapeProxy( const b3Shape* shape ); +b3ShapeProxy b3MakeLocalProxy( const b3ShapeProxy* proxy, b3Transform transform, b3Vec3* buffer ); +b3AABB b3ComputeProxyAABB( const b3ShapeProxy* proxy ); + +b3CastOutput b3RayCastShape( const b3Shape* shape, b3Transform transform, const b3RayCastInput* input ); +b3CastOutput b3ShapeCastShape( const b3Shape* shape, b3Transform transform, const b3ShapeCastInput* input ); +bool b3OverlapShape( const b3Shape* shape, b3Transform transform, const b3ShapeProxy* proxy ); + +float b3GetShapeArea( const b3Shape* shape ); +float b3GetShapeProjectedArea( const b3Shape* shape, b3Vec3 planeNormal ); +b3TOIOutput b3ShapeTimeOfImpact( b3Shape* shapeA, b3Shape* shapeB, b3Sweep* sweepA, b3Sweep* sweepB, float maxFraction ); + +int b3CollideMoverAndSphere( b3PlaneResult* result, const b3Sphere* shape, const b3Capsule* mover ); +int b3CollideMoverAndCapsule( b3PlaneResult* result, const b3Capsule* shape, const b3Capsule* mover ); +int b3CollideMoverAndHull( b3PlaneResult* result, const b3HullData* shape, const b3Capsule* mover ); +int b3CollideMoverAndMesh( b3PlaneResult* planes, int capacity, const b3Mesh* shape, const b3Capsule* mover ); +int b3CollideMoverAndHeightField( b3PlaneResult* results, int capacity, const b3HeightFieldData* shape, const b3Capsule* mover ); +int b3CollideMover( b3PlaneResult* planes, int planeCapacity, const b3Shape* shape, b3Transform transform, + const b3Capsule* mover ); + +// Hull +int b3FindHullSupportVertex( const b3HullData* hull, b3Vec3 direction ); +int b3FindHullSupportFace( const b3HullData* hull, b3Vec3 direction ); +bool b3IsValidHull( const b3HullData* hull ); +b3AABB b3ComputeSweptHullAABB( const b3HullData* shape, b3Transform xf1, b3Transform xf2 ); +b3ShapeExtent b3ComputeHullExtent( const b3HullData* hull, b3Vec3 origin ); +float b3ComputeHullProjectedArea( const b3HullData* hull, b3Vec3 direction ); + +// Height field +b3Triangle b3GetHeightFieldTriangle( const b3HeightFieldData* heightField, int triangleIndex ); +int b3GetHeightFieldMaterial( const b3HeightFieldData* heightField, int triangleIndex ); + +static inline int b3GetHeightFieldTriangleCount( const b3HeightFieldData* heightField ) +{ + int cellCount = ( heightField->rowCount - 1 ) * ( heightField->columnCount - 1 ); + return 2 * cellCount; +} + +// Mesh +b3Triangle b3GetMeshTriangle( const b3Mesh* mesh, int triangleIndex ); +bool b3IsValidMesh( const b3MeshData* meshData ); + +static inline bool b3ShouldShapesCollide( b3Filter filterA, b3Filter filterB ) +{ + if ( filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0 ) + { + return filterA.groupIndex > 0; + } + + return ( filterA.maskBits & filterB.categoryBits ) != 0 && ( filterA.categoryBits & filterB.maskBits ) != 0; +} + +static inline bool b3ShouldQueryCollide( const b3Filter* shapeFilter, const b3QueryFilter* queryFilter ) +{ + return ( shapeFilter->categoryBits & queryFilter->maskBits ) != 0 && + ( shapeFilter->maskBits & queryFilter->categoryBits ) != 0; +} diff --git a/vendor/box3d/src/src/simd.c b/vendor/box3d/src/src/simd.c new file mode 100644 index 000000000..6a9faace0 --- /dev/null +++ b/vendor/box3d/src/src/simd.c @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// Dirk Gregorius contributed portions of this code + +#include "simd.h" + +#if defined( B3_SIMD_SSE2 ) + +#define B3_TRANSPOSE3( C1, C2, C3 ) \ + { \ + b3V32 T1 = _mm_unpacklo_ps( ( C1 ), ( C2 ) ); \ + b3V32 T2 = _mm_unpackhi_ps( ( C1 ), ( C2 ) ); \ + ( C1 ) = _mm_shuffle_ps( ( T1 ), ( C3 ), _MM_SHUFFLE( 0, 0, 1, 0 ) ); \ + ( C2 ) = _mm_shuffle_ps( ( T1 ), ( C3 ), _MM_SHUFFLE( 1, 1, 3, 2 ) ); \ + ( C3 ) = _mm_shuffle_ps( ( T2 ), ( C3 ), _MM_SHUFFLE( 2, 2, 1, 0 ) ); \ + } + +static inline b3V32 b3SplatXV( b3V32 v ) +{ + return _mm_shuffle_ps( v, v, _MM_SHUFFLE( 0, 0, 0, 0 ) ); +} + +static inline b3V32 b3SplatYV( b3V32 v ) +{ + return _mm_shuffle_ps( v, v, _MM_SHUFFLE( 1, 1, 1, 1 ) ); +} + +static inline b3V32 b3SplatZV( b3V32 v ) +{ + return _mm_shuffle_ps( v, v, _MM_SHUFFLE( 2, 2, 2, 2 ) ); +} + +static inline bool b3AnyGreaterEq3V( b3V32 a, b3V32 b ) +{ + b3V32 v = _mm_cmpge_ps( a, b ); + return ( _mm_movemask_ps( v ) & 0x07 ) != 0; +} + +static inline b3V32 b3Dot3V( b3V32 a, b3V32 b ) +{ + b3V32 m = _mm_mul_ps( a, b ); + b3V32 x = _mm_shuffle_ps( m, m, _MM_SHUFFLE( 0, 0, 0, 0 ) ); + b3V32 y = _mm_shuffle_ps( m, m, _MM_SHUFFLE( 1, 1, 1, 1 ) ); + b3V32 z = _mm_shuffle_ps( m, m, _MM_SHUFFLE( 2, 2, 2, 2 ) ); + + return _mm_add_ps( _mm_add_ps( x, y ), z ); +} + +#else + +#define B3_TRANSPOSE3( C1, C2, C3 ) \ + { \ + float temp1 = C1.y; \ + float temp2 = C1.z; \ + float temp3 = C2.z; \ + \ + C1.y = C2.x; \ + C1.z = C3.x; \ + C2.z = C3.y; \ + \ + C2.x = temp1; \ + C3.x = temp2; \ + C3.y = temp3; \ + } + +static inline b3V32 b3SplatXV( b3V32 a ) +{ + return B3_LITERAL( b3V32 ){ a.x, a.x, a.x }; +} + +static inline b3V32 b3SplatYV( b3V32 a ) +{ + return B3_LITERAL( b3V32 ){ a.y, a.y, a.y }; +} + +static inline b3V32 b3SplatZV( b3V32 a ) +{ + return B3_LITERAL( b3V32 ){ a.z, a.z, a.z }; +} + +static inline bool b3AnyGreaterEq3V( b3V32 a, b3V32 b ) +{ + return a.x >= b.x || a.y >= b.y || a.z >= b.z; +} + +static inline b3V32 b3Dot3V( b3V32 a, b3V32 b ) +{ + float d = a.x * b.x + a.y * b.y + a.z * b.z; + return B3_LITERAL( b3V32 ){ d, d, d }; +} + +#endif + +bool b3TestBoundsTriangleOverlap( b3V32 nodeCenter, b3V32 nodeExtent, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 ) +{ + b3V32 two = b3SplatV( 2.0f ); + + // Setup triangle + vertex1 = b3SubV( vertex1, nodeCenter ); + vertex2 = b3SubV( vertex2, nodeCenter ); + vertex3 = b3SubV( vertex3, nodeCenter ); + + // Face separation + b3V32 triangleMin = b3MinV( vertex1, b3MinV( vertex2, vertex3 ) ); + b3V32 triangleMax = b3MaxV( vertex1, b3MaxV( vertex2, vertex3 ) ); + + b3V32 separation1 = b3SubV( triangleMin, nodeExtent ); + b3V32 separation2 = b3AddV( triangleMax, nodeExtent ); + + b3V32 faceSeparation = b3MaxV( separation1, b3NegV( separation2 ) ); + if ( b3AnyGreater3V( faceSeparation, b3_zeroV ) ) + { + return false; + } + + // SAT: Face separation + b3V32 edge1 = b3SubV( vertex2, vertex1 ); + b3V32 edge2 = b3SubV( vertex3, vertex2 ); + b3V32 edge3 = b3SubV( vertex1, vertex3 ); + + b3V32 normal = b3CrossV( edge1, edge2 ); + + b3V32 triangleSeparation = b3SubV( b3AbsV( b3Dot3V( normal, vertex1 ) ), b3Dot3V( b3AbsV( normal ), nodeExtent ) ); + if ( b3AnyGreater3V( triangleSeparation, b3_zeroV ) ) + { + return false; + } + + // SAT: Edge separation + b3V32 edgeSeparation1 = b3SubV( b3SubV( b3AbsV( b3CrossV( edge1, b3AddV( vertex1, vertex3 ) ) ), b3AbsV( b3CrossV( edge1, edge3 ) ) ), + b3MulV( two, b3ModifiedCrossV( b3AbsV( edge1 ), nodeExtent ) ) ); + if ( b3AnyGreater3V( edgeSeparation1, b3_zeroV ) ) + { + return false; + } + + b3V32 edgeSeparation2 = b3SubV( b3SubV( b3AbsV( b3CrossV( edge2, b3AddV( vertex1, vertex2 ) ) ), b3AbsV( b3CrossV( edge2, edge1 ) ) ), + b3MulV( two, b3ModifiedCrossV( b3AbsV( edge2 ), nodeExtent ) ) ); + if ( b3AnyGreater3V( edgeSeparation2, b3_zeroV ) ) + { + return false; + } + + b3V32 edgeSeparation3 = b3SubV( b3SubV( b3AbsV( b3CrossV( edge3, b3AddV( vertex2, vertex3 ) ) ), b3AbsV( b3CrossV( edge3, edge2 ) ) ), + b3MulV( two, b3ModifiedCrossV( b3AbsV( edge3 ), nodeExtent ) ) ); + if ( b3AnyGreater3V( edgeSeparation3, b3_zeroV ) ) + { + return false; + } + + return true; +} + +float b3IntersectRayTriangle( b3V32 rayStart, b3V32 rayDelta, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 ) +{ + // Test if ray intersects this triangle sharing same calculations for each triangle + { + b3V32 edge1 = b3SubV( vertex3, vertex2 ); + b3V32 edge2 = b3SubV( vertex1, vertex3 ); + b3V32 edge3 = b3SubV( vertex2, vertex1 ); + + b3V32 midPoint1 = b3MulV( b3_halfV, b3AddV( vertex2, vertex3 ) ); + b3V32 midPoint2 = b3MulV( b3_halfV, b3AddV( vertex3, vertex1 ) ); + b3V32 midPoint3 = b3MulV( b3_halfV, b3AddV( vertex1, vertex2 ) ); + + b3V32 normal1 = b3CrossV( edge1, b3SubV( midPoint1, rayStart ) ); + b3V32 normal2 = b3CrossV( edge2, b3SubV( midPoint2, rayStart ) ); + b3V32 normal3 = b3CrossV( edge3, b3SubV( midPoint3, rayStart ) ); + B3_TRANSPOSE3( normal1, normal2, normal3 ); + + b3V32 rayDeltaX = b3SplatXV( rayDelta ); + b3V32 rayDeltaY = b3SplatYV( rayDelta ); + b3V32 rayDeltaZ = b3SplatZV( rayDelta ); + + b3V32 volumes = b3AddV( b3AddV( b3MulV( normal1, rayDeltaX ), b3MulV( normal2, rayDeltaY ) ), b3MulV( normal3, rayDeltaZ ) ); + if ( b3AnyLess3V( volumes, b3_zeroV ) ) + { + return 1.0f; + } + } + + // Compute intersection with triangle plane + b3V32 edge1 = b3SubV( vertex2, vertex1 ); + b3V32 edge2 = b3SubV( vertex3, vertex1 ); + b3V32 normal = b3CrossV( edge1, edge2 ); + + b3V32 denominator = b3Dot3V( normal, rayDelta ); + if ( b3AnyGreaterEq3V( denominator, b3_zeroV ) ) + { + return 1.0f; + } + + b3V32 lambda = b3DivV( b3Dot3V( normal, b3SubV( vertex1, rayStart ) ), denominator ); + if ( b3AnyLessEq3V( lambda, b3_zeroV ) ) + { + return 1.0f; + } + + lambda = b3MinV( lambda, b3_oneV ); + return b3GetXV( lambda ); +} diff --git a/vendor/box3d/src/src/simd.h b/vendor/box3d/src/src/simd.h new file mode 100644 index 000000000..ac7e9e8b5 --- /dev/null +++ b/vendor/box3d/src/src/simd.h @@ -0,0 +1,357 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "core.h" + +#include + +#if defined( B3_SIMD_SSE2 ) + +#include + +// wide float holds 4 numbers +typedef __m128 b3V32; + +typedef union b3128 +{ + b3V32 v; + float f[4]; +} b3128; + +#if defined( _MSC_VER ) && !defined( __clang__ ) + +static const b3V32 b3_zeroV = { { 0.0f, 0.0f, 0.0f, 0.0f } }; +static const b3V32 b3_halfV = { { 0.5f, 0.5f, 0.5f, 0.5f } }; +static const b3V32 b3_oneV = { { 1.0f, 1.0f, 1.0f, 1.0f } }; + +#else + +static const b3V32 b3_zeroV = { 0.0f, 0.0f, 0.0f, 0.0f }; +static const b3V32 b3_halfV = { 0.5f, 0.5f, 0.5f, 0.5f }; +static const b3V32 b3_oneV = { 1.0f, 1.0f, 1.0f, 1.0f }; + +#endif + +static inline b3V32 b3AddV( b3V32 a, b3V32 b ) +{ + return _mm_add_ps( a, b ); +} + +static inline b3V32 b3SubV( b3V32 a, b3V32 b ) +{ + return _mm_sub_ps( a, b ); +} + +static inline b3V32 b3MulV( b3V32 a, b3V32 b ) +{ + return _mm_mul_ps( a, b ); +} + +static inline b3V32 b3DivV( b3V32 a, b3V32 b ) +{ + return _mm_div_ps( a, b ); +} + +static inline b3V32 b3NegV( b3V32 a ) +{ + return _mm_sub_ps( _mm_setzero_ps(), a ); +} + +static inline b3V32 b3LoadV( const float* src ) +{ + // Loads exactly 12 bytes: 8 via movsd, 4 via movss. + // Result lane 3 is implicitly zero from the partial loads. + __m128 xy = _mm_castpd_ps( _mm_load_sd( (const double*)( src ) ) ); + __m128 z = _mm_load_ss( src + 2 ); + return _mm_movelh_ps( xy, z ); // { src[0], src[1], src[2], 0.0f } +} + +static inline b3V32 b3ZeroV( void ) +{ + return _mm_setzero_ps(); +} + +static inline float b3GetXV( b3V32 a ) +{ + return _mm_cvtss_f32( a ); +} + +static inline float b3GetYV( b3V32 a ) +{ + return _mm_cvtss_f32( _mm_shuffle_ps( a, a, _MM_SHUFFLE( 1, 1, 1, 1 ) ) ); +} + +static inline float b3GetZV( b3V32 a ) +{ + return _mm_cvtss_f32( _mm_shuffle_ps( a, a, _MM_SHUFFLE( 2, 2, 2, 2 ) ) ); +} + +static inline float b3GetV( b3V32 a, int index ) +{ + b3128 b; + b.v = a; + return b.f[index]; +} + +static inline b3V32 b3SplatV( float x ) +{ + return _mm_set_ps1( x ); +} + +static inline b3V32 b3AbsV( b3V32 a ) +{ + // Abs( V ) = Max( -V, V ) + b3V32 zero = _mm_setzero_ps(); + return _mm_max_ps( _mm_sub_ps( zero, a ), a ); +} + +static inline b3V32 b3MinV( b3V32 a, b3V32 b ) +{ + return _mm_min_ps( a, b ); +} + +static inline b3V32 b3MaxV( b3V32 a, b3V32 b ) +{ + return _mm_max_ps( a, b ); +} + +static inline b3V32 b3CrossV( b3V32 a, b3V32 b ) +{ + b3V32 yzX1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 0, 2, 1 ) ); + b3V32 zxY1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 1, 0, 2 ) ); + b3V32 yzX2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 0, 2, 1 ) ); + b3V32 zxY2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 1, 0, 2 ) ); + + return _mm_sub_ps( _mm_mul_ps( yzX1, zxY2 ), _mm_mul_ps( zxY1, yzX2 ) ); +} + +static inline b3V32 b3ModifiedCrossV( b3V32 a, b3V32 b ) +{ + b3V32 yzX1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 0, 2, 1 ) ); + b3V32 zxY1 = _mm_shuffle_ps( a, a, _MM_SHUFFLE( 3, 1, 0, 2 ) ); + b3V32 yzX2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 0, 2, 1 ) ); + b3V32 zxY2 = _mm_shuffle_ps( b, b, _MM_SHUFFLE( 3, 1, 0, 2 ) ); + + return _mm_add_ps( _mm_mul_ps( yzX1, zxY2 ), _mm_mul_ps( zxY1, yzX2 ) ); +} + +static inline bool b3AnyLess3V( b3V32 a, b3V32 b ) +{ + b3V32 v = _mm_cmplt_ps( a, b ); + return ( _mm_movemask_ps( v ) & 0x07 ) != 0; +} + +static inline bool b3AnyLessEq3V( b3V32 a, b3V32 b ) +{ + b3V32 v = _mm_cmple_ps( a, b ); + return ( _mm_movemask_ps( v ) & 0x07 ) != 0; +} + +static inline bool b3AnyGreater3V( b3V32 a, b3V32 b ) +{ + b3V32 v = _mm_cmpgt_ps( a, b ); + return ( _mm_movemask_ps( v ) & 0x07 ) != 0; +} + +static inline bool b3AllLessEq3V( b3V32 a, b3V32 b ) +{ + b3V32 v = _mm_cmple_ps( a, b ); + return ( _mm_movemask_ps( v ) & 0x07 ) == 0x07; +} + +#else + +// I don't expect the use case of b3V32 to benefit from Neon code. +// In particular the cross product is very complex in Neon. + +// scalar math +typedef struct b3V32 +{ + float x, y, z; +} b3V32; + +typedef union b3128 +{ + b3V32 v; + float f[3]; +} b3128; + +static const b3V32 b3_zeroV = { 0.0f, 0.0f, 0.0f }; +static const b3V32 b3_halfV = { 0.5f, 0.5f, 0.5f }; +static const b3V32 b3_oneV = { 1.0f, 1.0f, 1.0f }; + +static inline b3V32 b3AddV( b3V32 a, b3V32 b ) +{ + return B3_LITERAL( b3V32 ){ + a.x + b.x, + a.y + b.y, + a.z + b.z, + }; +} + +static inline b3V32 b3SubV( b3V32 a, b3V32 b ) +{ + return B3_LITERAL( b3V32 ){ + a.x - b.x, + a.y - b.y, + a.z - b.z, + }; +} + +static inline b3V32 b3MulV( b3V32 a, b3V32 b ) +{ + return B3_LITERAL( b3V32 ){ + a.x * b.x, + a.y * b.y, + a.z * b.z, + }; +} + +static inline b3V32 b3DivV( b3V32 a, b3V32 b ) +{ + return B3_LITERAL( b3V32 ){ + a.x / b.x, + a.y / b.y, + a.z / b.z, + }; +} + +static inline b3V32 b3NegV( b3V32 a ) +{ + return B3_LITERAL( b3V32 ){ + -a.x, + -a.y, + -a.z, + }; +} + +// Unaligned loads are much faster on recent hardware with little to no penalty +static inline b3V32 b3LoadV( const float* src ) +{ + return B3_LITERAL( b3V32 ){ src[0], src[1], src[2] }; +} + +static inline b3V32 b3ZeroV( void ) +{ + return B3_LITERAL( b3V32 ){ 0.0f, 0.0f, 0.0f }; +} + +static inline float b3GetXV( b3V32 a ) +{ + return a.x; +} + +static inline float b3GetYV( b3V32 a ) +{ + return a.y; +} + +static inline float b3GetZV( b3V32 a ) +{ + return a.z; +} + +static inline float b3GetV( b3V32 a, int index ) +{ + b3128 b; + b.v = a; + return b.f[index]; +} + +static inline b3V32 b3SplatV( float x ) +{ + return B3_LITERAL( b3V32 ){ x, x, x }; +} + +static inline b3V32 b3AbsV( b3V32 a ) +{ + return B3_LITERAL( b3V32 ){ + a.x < 0.0f ? -a.x : a.x, + a.y < 0.0f ? -a.y : a.y, + a.z < 0.0f ? -a.z : a.z, + }; +} + +static inline b3V32 b3MinV( b3V32 a, b3V32 b ) +{ + return B3_LITERAL( b3V32 ){ + a.x < b.x ? a.x : b.x, + a.y < b.y ? a.y : b.y, + a.z < b.z ? a.z : b.z, + }; +} + +static inline b3V32 b3MaxV( b3V32 a, b3V32 b ) +{ + return B3_LITERAL( b3V32 ){ + a.x > b.x ? a.x : b.x, + a.y > b.y ? a.y : b.y, + a.z > b.z ? a.z : b.z, + }; +} + +static inline b3V32 b3CrossV( b3V32 a, b3V32 b ) +{ + b3V32 c; + c.x = a.y * b.z - a.z * b.y; + c.y = a.z * b.x - a.x * b.z; + c.z = a.x * b.y - a.y * b.x; + return c; +} + +static inline b3V32 b3ModifiedCrossV( b3V32 a, b3V32 b ) +{ + b3V32 c; + c.x = a.y * b.z + a.z * b.y; + c.y = a.z * b.x + a.x * b.z; + c.z = a.x * b.y + a.y * b.x; + return c; +} + +static inline bool b3AnyLess3V( b3V32 a, b3V32 b ) +{ + return a.x < b.x || a.y < b.y || a.z < b.z; +} + +static inline bool b3AnyLessEq3V( b3V32 a, b3V32 b ) +{ + return a.x <= b.x || a.y <= b.y || a.z <= b.z; +} + +static inline bool b3AnyGreater3V( b3V32 a, b3V32 b ) +{ + return a.x > b.x || a.y > b.y || a.z > b.z; +} + +static inline bool b3AllLessEq3V( b3V32 a, b3V32 b ) +{ + return a.x <= b.x && a.y <= b.y && a.z <= b.z; +} + +#endif + +static inline bool b3TestBoundsOverlap( b3V32 nodeMin1, b3V32 nodeMax1, b3V32 nodeMin2, b3V32 nodeMax2 ) +{ + b3V32 separation = b3MaxV( b3SubV( nodeMin2, nodeMax1 ), b3SubV( nodeMin1, nodeMax2 ) ); + return b3AllLessEq3V( separation, b3_zeroV ); +} + +// Test a ray for edge separation with an AABB (Gino, p80). +static inline bool b3TestBoundsRayOverlap( b3V32 nodeMin, b3V32 nodeMax, b3V32 rayStart, b3V32 rayDelta ) +{ + // Setup node + b3V32 nodeCenter = b3MulV( b3_halfV, b3AddV( nodeMin, nodeMax ) ); + b3V32 nodeExtent = b3SubV( nodeMax, nodeCenter ); + + // Setup ray + rayStart = b3SubV( rayStart, nodeCenter ); + + // SAT: Edge separation + b3V32 edgeSeparation = b3SubV( b3AbsV( b3CrossV( rayDelta, rayStart ) ), b3ModifiedCrossV( b3AbsV( rayDelta ), nodeExtent ) ); + return b3AllLessEq3V( edgeSeparation, b3_zeroV ); +} + +bool b3TestBoundsTriangleOverlap( b3V32 nodeCenter, b3V32 nodeExtent, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 ); +float b3IntersectRayTriangle( b3V32 rayStart, b3V32 rayDelta, b3V32 vertex1, b3V32 vertex2, b3V32 vertex3 ); diff --git a/vendor/box3d/src/src/solver.c b/vendor/box3d/src/src/solver.c new file mode 100644 index 000000000..a2b1be977 --- /dev/null +++ b/vendor/box3d/src/src/solver.c @@ -0,0 +1,2328 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "solver.h" + +#include "arena_allocator.h" +#include "bitset.h" +#include "body.h" +#include "compound.h" +#include "contact.h" +#include "contact_solver.h" +#include "core.h" +#include "ctz.h" +#include "island.h" +#include "joint.h" +#include "parallel_for.h" +#include "physics_world.h" +#include "platform.h" +#include "sensor.h" +#include "shape.h" +#include "solver_set.h" + +#include +#include +#include + +// these are useful for solver testing +#define ITERATIONS 1 +#define RELAX_ITERATIONS 1 + +#if ( defined( __GNUC__ ) || defined( __clang__ ) ) && ( defined( __i386__ ) || defined( __x86_64__ ) ) +static void b3Pause( void ) +{ + __asm__ __volatile__( "pause\n" ); +} +#elif ( defined( __arm__ ) && defined( __ARM_ARCH ) && __ARM_ARCH >= 7 ) || defined( __aarch64__ ) +static void b3Pause( void ) +{ + __asm__ __volatile__( "yield" ::: "memory" ); +} +#elif defined( _MSC_VER ) && ( defined( _M_IX86 ) || defined( _M_X64 ) ) +static void b3Pause( void ) +{ + _mm_pause(); +} +#elif defined( _MSC_VER ) && ( defined( _M_ARM ) || defined( _M_ARM64 ) ) +static void b3Pause( void ) +{ + __yield(); +} +#else +static void b3Pause( void ) +{ +} +#endif + +typedef struct b3WorkerContext +{ + b3StepContext* context; + int workerIndex; + void* userTask; +} b3WorkerContext; + +// Integrate velocities, apply damping, and gyroscopic torque +static void b3IntegrateVelocitiesTask( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( integrate_velocity, "IntVel", b3_colorDeepPink, true ); + + B3_VALIDATE( block.startIndex + block.count <= context->world->solverSets.data[b3_awakeSet].bodyStates.count ); + + b3BodyState* states = context->states; + b3BodySim* sims = context->sims; + + b3Vec3 gravity = context->world->gravity; + float h = context->h; + + for ( int i = block.startIndex; i < block.startIndex + block.count; ++i ) + { + b3BodySim* sim = sims + i; + b3BodyState* state = states + i; + + b3Vec3 v = state->linearVelocity; + b3Vec3 w = state->angularVelocity; + + // Damping math + // Differential equation: dv/dt + c * v = 0 + // Solution: v(t) = v0 * exp(-c * t) + // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v(t) * exp(-c * dt) + // v2 = exp(-c * dt) * v1 + // Pade approximation: + // v2 = v1 * 1 / (1 + c * dt) + float linearDamping = 1.0f / ( 1.0f + h * sim->linearDamping ); + float angularDamping = 1.0f / ( 1.0f + h * sim->angularDamping ); + + // Gravity scale will be zero for kinematic bodies + float gravityScale = sim->invMass > 0.0f ? sim->gravityScale : 0.0f; + + b3Vec3 linearVelocityDelta = b3Blend2( h * sim->invMass, sim->force, h * gravityScale, gravity ); + v = b3MulAdd( linearVelocityDelta, linearDamping, v ); + + b3Vec3 angularVelocityDelta = b3MulSV( h, b3MulMV( sim->invInertiaWorld, sim->torque ) ); + w = b3MulAdd( angularVelocityDelta, angularDamping, w ); + + // Gyroscopic torque by solving this nonlinear equation using Newton-Raphson. + // I * (w2 - w1) + h * cross(w2, I * w2) = 0 + // This is all done in local coordinates where the Jacobian is easier to compute. + // This improves the simulation of long skinny bodies. + { + // Get current rotation. + b3Quat q0 = sim->transform.q; + b3Quat q = b3MulQuat( state->deltaRotation, q0 ); + + // todo wasteful computation + b3Matrix3 inertiaLocal = b3InvertMatrix( sim->invInertiaLocal ); + + // Compute local angular velocity + b3Vec3 omega1 = b3InvRotateVector( q, w ); + b3Vec3 omega2 = omega1; + + // Symmetric inertia tensor: 6 unique entries (column-major) + const float i00 = inertiaLocal.cx.x; + const float i01 = inertiaLocal.cy.x; + const float i02 = inertiaLocal.cz.x; + const float i11 = inertiaLocal.cy.y; + const float i12 = inertiaLocal.cz.y; + const float i22 = inertiaLocal.cz.z; + + for ( int gyroIteration = 0; gyroIteration < 1; ++gyroIteration ) + { + const float w1 = omega2.x; + const float w2 = omega2.y; + const float w3 = omega2.z; + + // Iw = I * omega2 (shared between residual and Jacobian) + const float Iw1 = i00 * w1 + i01 * w2 + i02 * w3; + const float Iw2 = i01 * w1 + i11 * w2 + i12 * w3; + const float Iw3 = i02 * w1 + i12 * w2 + i22 * w3; + + // Residual: b = I*(omega2 - omega1) + h * (omega2 × I*omega2) + const b3Vec3 dw = b3Sub( omega2, omega1 ); + b3Vec3 b = { + i00 * dw.x + i01 * dw.y + i02 * dw.z + h * ( w2 * Iw3 - w3 * Iw2 ), + i01 * dw.x + i11 * dw.y + i12 * dw.z + h * ( w3 * Iw1 - w1 * Iw3 ), + i02 * dw.x + i12 * dw.y + i22 * dw.z + h * ( w1 * Iw2 - w2 * Iw1 ), + }; + + // Jacobian J = I + h * (skew(omega2) * I - skew(I*omega2)) + // Jacobian derived by Erin Catto, Ph.D. Do not attempt to do this without a Ph.D. + // Doubled inertia terms above fold into Iw, e.g. row 2 col 1: i00*w3 - i02*w1 - Iw3. + b3Matrix3 J = { + { i00 + h * ( w2 * i02 - w3 * i01 ), i01 + h * ( w3 * i00 - w1 * i02 - Iw3 ), + i02 + h * ( w1 * i01 - w2 * i00 + Iw2 ) }, + { i01 + h * ( w2 * i12 - w3 * i11 + Iw3 ), i11 + h * ( w3 * i01 - w1 * i12 ), + i12 + h * ( w1 * i11 - w2 * i01 - Iw1 ) }, + { i02 + h * ( w2 * i22 - w3 * i12 - Iw2 ), i12 + h * ( w3 * i02 - w1 * i22 + Iw1 ), + i22 + h * ( w1 * i12 - w2 * i02 ) }, + }; + + omega2 = b3Sub( omega2, b3Solve3( J, b ) ); + } + + w = b3RotateVector( q, omega2 ); + } + + state->linearVelocity = v; + state->angularVelocity = w; + } + + b3TracyCZoneEnd( integrate_velocity ); +} + +static void b3IntegratePositionsTask( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( integrate_positions, "IntPos", b3_colorDarkSeaGreen, true ); + + B3_VALIDATE( block.startIndex + block.count <= context->world->solverSets.data[b3_awakeSet].bodyStates.count ); + + b3BodyState* states = context->states; + float h = context->h; + float maxLinearSpeed = context->maxLinearVelocity; + float maxAngularSpeed = B3_MAX_ROTATION * context->inv_dt; + float maxLinearSpeedSquared = maxLinearSpeed * maxLinearSpeed; + float maxAngularSpeedSquared = maxAngularSpeed * maxAngularSpeed; + + for ( int i = block.startIndex; i < block.startIndex + block.count; ++i ) + { + b3BodyState* state = states + i; + + b3Vec3 v = state->linearVelocity; + b3Vec3 w = state->angularVelocity; + + // Motion locks - these can be viewed as a constraint that come last + v.x = ( state->flags & b3_lockLinearX ) ? 0.0f : v.x; + v.y = ( state->flags & b3_lockLinearY ) ? 0.0f : v.y; + v.z = ( state->flags & b3_lockLinearZ ) ? 0.0f : v.z; + w.x = ( state->flags & b3_lockAngularX ) ? 0.0f : w.x; + w.y = ( state->flags & b3_lockAngularY ) ? 0.0f : w.y; + w.z = ( state->flags & b3_lockAngularZ ) ? 0.0f : w.z; + + // Clamp to max linear speed + if ( b3Dot( v, v ) > maxLinearSpeedSquared ) + { + float ratio = maxLinearSpeed / b3Length( v ); + v = b3MulSV( ratio, v ); + state->flags |= b3_isSpeedCapped; + } + + // Clamp to max angular speed + if ( b3Dot( w, w ) > maxAngularSpeedSquared && ( state->flags & b3_allowFastRotation ) == 0 ) + { + float ratio = maxAngularSpeed / b3Length( w ); + w = b3MulSV( ratio, w ); + state->flags |= b3_isSpeedCapped; + } + + state->linearVelocity = v; + state->angularVelocity = w; + state->deltaPosition = b3MulAdd( state->deltaPosition, h, v ); + state->deltaRotation = b3IntegrateRotation( state->deltaRotation, b3MulSV( h, w ) ); + } + + b3TracyCZoneEnd( integrate_positions ); +} + +static void b3PrepareJointsTask( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( prepare_joints, "PrepJoints", b3_colorOldLace, true ); + + b3JointPrepareSpan* spans = context->jointPrepareSpans; + + int index = block.startIndex; + int endIndex = block.startIndex + block.count; + + // Find color for start index. Linear search but fast. + int colorIndex = 0; + while ( spans[colorIndex + 1].start <= index ) + { + colorIndex += 1; + } + + // Loop over block + while ( index < endIndex ) + { + int colorStart = spans[colorIndex].start; + int colorEndIndex = b3MinInt( spans[colorIndex + 1].start, endIndex ); + b3JointSim* joints = spans[colorIndex].joints; + + // Loop over color + for ( ; index < colorEndIndex; ++index ) + { + B3_ASSERT( 0 <= index - colorStart && index - colorStart < spans[colorIndex].count ); + b3JointSim* joint = joints + ( index - colorStart ); + b3PrepareJoint( joint, context ); + } + + // Advance to next color + colorIndex += 1; + } + + b3TracyCZoneEnd( prepare_joints ); +} + +static void b3WarmStartJointsTask( b3SolverBlock block, b3StepContext* context ) +{ + b3TracyCZoneNC( warm_joints, "WarmJoints", b3_colorGold, true ); + + b3GraphColor* color = context->graph->colors + block.colorIndex; + b3JointSim* joints = color->jointSims.data; + + for ( int i = block.startIndex; i < block.startIndex + block.count; ++i ) + { + b3JointSim* joint = joints + i; + b3WarmStartJoint( joint, context ); + } + + b3TracyCZoneEnd( warm_joints ); +} + +static void b3SolveJointsTask( b3SolverBlock block, b3StepContext* context, bool useBias, int workerIndex ) +{ + b3TracyCZoneNC( solve_joints, "SolveJoints", b3_colorLemonChiffon, true ); + + b3GraphColor* color = context->graph->colors + block.colorIndex; + b3JointSim* joints = color->jointSims.data; + + B3_ASSERT( 0 <= block.startIndex && block.startIndex + block.count <= color->jointSims.count ); + + b3BitSet* jointStateBitSet = &context->world->taskContexts.data[workerIndex].jointStateBitSet; + + for ( int i = block.startIndex; i < block.startIndex + block.count; ++i ) + { + b3JointSim* joint = joints + i; + b3SolveJoint( joint, context, useBias ); + + if ( useBias && ( joint->forceThreshold < FLT_MAX || joint->torqueThreshold < FLT_MAX ) && + b3GetBit( jointStateBitSet, joint->jointId ) == false ) + { + float force, torque; + b3GetJointReaction( context->world, joint, context->inv_h, &force, &torque ); + + // Check thresholds. A zero threshold means all awake joints get reported. + if ( force >= joint->forceThreshold || torque >= joint->torqueThreshold ) + { + // Flag this joint for processing. + b3SetBit( jointStateBitSet, joint->jointId ); + } + } + } + + b3TracyCZoneEnd( solve_joints ); +} + +#define B2_MAX_CONTINUOUS_SENSOR_HITS 8 + +typedef struct b3ContinuousContext +{ + b3World* world; + b3BodySim* fastBodySim; + b3Shape* fastShape; + b3Vec3 centroid1, centroid2; + b3Sweep sweep; + // World base for re-centering sweeps. Keeps TOI in float precision far from the origin. + b3Pos base; + float fraction; + b3SensorHit sensorHits[B2_MAX_CONTINUOUS_SENSOR_HITS]; + float sensorFractions[B2_MAX_CONTINUOUS_SENSOR_HITS]; + int sensorCount; + + int visitCount; + + int distanceIterations; + int pushBackIterations; + int rootIterations; +} b3ContinuousContext; + +// This is called from b3DynamicTree_Query for continuous collision +static bool b3ContinuousQueryCallback( int proxyId, uint64_t userData, void* context ) +{ + B3_UNUSED( proxyId ); + + int shapeId = (int)userData; + b3ContinuousContext* continuousContext = context; + continuousContext->visitCount += 1; + + b3Shape* fastShape = continuousContext->fastShape; + b3BodySim* fastBodySim = continuousContext->fastBodySim; + + B3_ASSERT( fastShape->sensorIndex == B3_NULL_INDEX ); + + // Skip same shape + if ( shapeId == fastShape->id ) + { + return true; + } + + b3World* world = continuousContext->world; + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + // Skip same body + if ( shape->bodyId == fastShape->bodyId ) + { + return true; + } + + // Skip sensors unless both shapes want sensor events + bool isSensor = shape->sensorIndex != B3_NULL_INDEX; + if ( isSensor && ( ( shape->flags & b3_enableSensorEvents ) == 0 || ( fastShape->flags & b3_enableSensorEvents ) == 0 ) ) + { + return true; + } + + // Skip filtered shapes + bool canCollide = b3ShouldShapesCollide( fastShape->filter, shape->filter ); + if ( canCollide == false ) + { + return true; + } + + b3Body* body = b3Array_Get( world->bodies, shape->bodyId ); + + b3BodySim* bodySim = b3GetBodySim( world, body ); + B3_ASSERT( body->type == b3_staticBody || ( fastBodySim->flags & b3_isBullet ) ); + + // Skip bullets + if ( bodySim->flags & b3_isBullet ) + { + return true; + } + + // Skip filtered bodies + b3Body* fastBody = b3Array_Get( world->bodies, fastBodySim->bodyId ); + canCollide = b3ShouldBodiesCollide( world, fastBody, body ); + if ( canCollide == false ) + { + return true; + } + + // Custom user filtering + if ( ( shape->flags & b3_enableCustomFiltering ) != 0 || ( fastShape->flags & b3_enableCustomFiltering ) != 0 ) + { + b3CustomFilterFcn* customFilterFcn = world->customFilterFcn; + if ( customFilterFcn != NULL ) + { + b3ShapeId idA = { shape->id + 1, world->worldId, shape->generation }; + b3ShapeId idB = { fastShape->id + 1, world->worldId, fastShape->generation }; + canCollide = customFilterFcn( idA, idB, world->customFilterContext ); + if ( canCollide == false ) + { + return true; + } + } + } + + uint64_t ticks = b3GetTicks(); + + // todo does having a sweep on shapeA help with bullets? + b3Sweep sweepA = b3MakeRelativeSweep( bodySim, continuousContext->base ); + + // Time of impact versus shape. Supports all shape types + b3TOIOutput output = b3ShapeTimeOfImpact( shape, fastShape, &sweepA, &continuousContext->sweep, continuousContext->fraction ); + if ( isSensor ) + { + // Only accept a sensor hit that is sooner than the current solid hit. + if ( output.fraction <= continuousContext->fraction && continuousContext->sensorCount < B2_MAX_CONTINUOUS_SENSOR_HITS ) + { + int index = continuousContext->sensorCount; + + // The hit shape is a sensor + b3SensorHit sensorHit = { + .sensorId = shape->id, + .visitorId = fastShape->id, + }; + + continuousContext->sensorHits[index] = sensorHit; + continuousContext->sensorFractions[index] = output.fraction; + continuousContext->sensorCount += 1; + } + } + else if ( 0.0f < output.fraction && output.fraction < continuousContext->fraction ) + { + bool didHit = true; + + if ( didHit && ( ( shape->flags & b3_enablePreSolveEvents ) || ( fastShape->flags & b3_enablePreSolveEvents ) ) ) + { + b3ShapeId shapeIdA = { shape->id + 1, world->worldId, shape->generation }; + b3ShapeId shapeIdB = { fastShape->id + 1, world->worldId, fastShape->generation }; + b3Pos point = b3OffsetPos( continuousContext->base, output.point ); + didHit = world->preSolveFcn( shapeIdA, shapeIdB, point, output.normal, world->preSolveContext ); + } + + if ( didHit ) + { + fastBodySim->flags |= b3_hadTimeOfImpact; + continuousContext->fraction = output.fraction; + continuousContext->distanceIterations = b3MaxInt( continuousContext->distanceIterations, output.distanceIterations ); + continuousContext->pushBackIterations = b3MaxInt( continuousContext->pushBackIterations, output.pushBackIterations ); + continuousContext->rootIterations = b3MaxInt( continuousContext->rootIterations, output.rootIterations ); + } + } + + float ms = b3GetMilliseconds( ticks ); + if ( ms > 1000.0f * b3GetStallThreshold() ) + { + const char* nameFast = b3FindNameWithDefault( &world->names, fastBody->nameId, "NULL" ); + const char* name = b3FindNameWithDefault( &world->names, body->nameId, "NULL" ); + b3Log( "CCD stall: duration %.1f ms for %s versus %s", ms, nameFast, name ); + } + + // Continue query + return true; +} + +// Continuous collision of dynamic versus static +static void b3SolveContinuous( b3World* world, int bodySimIndex, b3TaskContext* taskContext ) +{ + b3TracyCZoneNC( ccd, "CCD", b3_colorDarkGoldenRod, true ); + + uint64_t ticks = b3GetTicks(); + + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + b3BodySim* fastBodySim = b3Array_Get( awakeSet->bodySims, bodySimIndex ); + B3_ASSERT( fastBodySim->flags & b3_isFast ); + + // Re-center the sweep on the fast body so the TOI and the swept query stay in float precision + b3Pos base = fastBodySim->center0; + + b3Sweep sweep = b3MakeRelativeSweep( fastBodySim, base ); + + b3Transform xf1; + xf1.q = sweep.q1; + xf1.p = b3Sub( sweep.c1, b3RotateVector( sweep.q1, sweep.localCenter ) ); + + b3Transform xf2; + xf2.q = sweep.q2; + xf2.p = b3Sub( sweep.c2, b3RotateVector( sweep.q2, sweep.localCenter ) ); + + b3DynamicTree* staticTree = world->broadPhase.trees + b3_staticBody; + b3DynamicTree* kinematicTree = world->broadPhase.trees + b3_kinematicBody; + b3DynamicTree* dynamicTree = world->broadPhase.trees + b3_dynamicBody; + b3Body* fastBody = b3Array_Get( world->bodies, fastBodySim->bodyId ); + + b3ContinuousContext context = { 0 }; + context.world = world; + context.sweep = sweep; + context.base = base; + context.fastBodySim = fastBodySim; + context.fraction = 1.0f; + + bool isBullet = ( fastBodySim->flags & b3_isBullet ) != 0; + + int shapeId = fastBody->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* fastShape = b3Array_Get( world->shapes, shapeId ); + shapeId = fastShape->nextShapeId; + + context.fastShape = fastShape; + context.centroid1 = b3TransformPoint( xf1, fastShape->localCentroid ); + context.centroid2 = b3TransformPoint( xf2, fastShape->localCentroid ); + + b3AABB box1 = fastShape->aabb; + // xf2 is relative to the base, so translate the box back to world space, rounding outward + b3AABB box2 = b3OffsetAABB( b3ComputeShapeAABB( fastShape, xf2 ), base ); + + // Store this to avoid double computation in the case there is no impact event + fastShape->aabb = box2; + + // No continuous collision for meshes + if ( fastShape->type == b3_meshShape || fastShape->type == b3_heightShape ) + { + continue; + } + + // No continuous collision for sensors + if ( fastShape->sensorIndex != B3_NULL_INDEX ) + { + continue; + } + + b3AABB sweptBox = b3AABB_Union( box1, box2 ); + b3DynamicTree_Query( staticTree, sweptBox, B3_DEFAULT_MASK_BITS, false, b3ContinuousQueryCallback, &context ); + + if ( isBullet ) + { + b3DynamicTree_Query( kinematicTree, sweptBox, B3_DEFAULT_MASK_BITS, false, b3ContinuousQueryCallback, &context ); + b3DynamicTree_Query( dynamicTree, sweptBox, B3_DEFAULT_MASK_BITS, false, b3ContinuousQueryCallback, &context ); + } + } + + const float speculativeScalar = B3_SPECULATIVE_DISTANCE; + + if ( context.fraction < 1.0f ) + { + // Handle time of impact event. The sweep is relative to the base, so re-add the base + // to return the advanced pose to world space. + b3Quat q = b3NLerp( sweep.q1, sweep.q2, context.fraction ); + b3Vec3 c = b3Lerp( sweep.c1, sweep.c2, context.fraction ); + b3Vec3 origin = b3Sub( c, b3RotateVector( q, sweep.localCenter ) ); + + // Advance body + b3WorldTransform transform = { b3OffsetPos( base, origin ), q }; + b3Pos center = b3OffsetPos( base, c ); + fastBodySim->transform = transform; + fastBodySim->center = center; + fastBodySim->rotation0 = q; + fastBodySim->center0 = center; + + // The move event was written before CCD, so correct it with the impact pose + b3BodyMoveEvent* event = b3Array_Get( world->bodyMoveEvents, bodySimIndex ); + event->transform = fastBodySim->transform; + + // Prepare AABBs for broad-phase. + // Even though a body is fast, it may not move much. So the AABB may not need enlargement. + + shapeId = fastBody->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + // Must recompute aabb at the interpolated transform + b3AABB aabb = b3ComputeFatShapeAABB( shape, transform, speculativeScalar ); + shape->aabb = aabb; + + if ( b3AABB_Contains( shape->fatAABB, aabb ) == false ) + { + float marginScalar = shape->aabbMargin; + b3Vec3 aabbMargin = { marginScalar, marginScalar, marginScalar }; + shape->fatAABB = (b3AABB){ b3Sub( aabb.lowerBound, aabbMargin ), b3Add( aabb.upperBound, aabbMargin ) }; + + shape->flags |= b3_enlargedAABB; + fastBodySim->flags |= b3_enlargeBounds; + } + + shapeId = shape->nextShapeId; + } + } + else + { + // No time of impact event + + // Advance body + fastBodySim->rotation0 = fastBodySim->transform.q; + fastBodySim->center0 = fastBodySim->center; + + // Prepare AABBs for broad-phase + shapeId = fastBody->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + // shape->aabb is still valid from above + + if ( b3AABB_Contains( shape->fatAABB, shape->aabb ) == false ) + { + float marginScalar = shape->aabbMargin; + b3Vec3 aabbMargin = { marginScalar, marginScalar, marginScalar }; + shape->fatAABB = (b3AABB){ + .lowerBound = b3Sub( shape->aabb.lowerBound, aabbMargin ), + .upperBound = b3Add( shape->aabb.upperBound, aabbMargin ), + }; + + shape->flags |= b3_enlargedAABB; + fastBodySim->flags |= b3_enlargeBounds; + } + + shapeId = shape->nextShapeId; + } + } + + // Push sensor hits on the the task context for serial processing. + for ( int i = 0; i < context.sensorCount; ++i ) + { + // Skip any sensor hits that occurred after a solid hit + if ( context.sensorFractions[i] < context.fraction ) + { + b3Array_Push( taskContext->sensorHits, context.sensorHits[i] ); + } + } + + taskContext->distanceIterations = b3MaxInt( taskContext->distanceIterations, context.distanceIterations ); + taskContext->pushBackIterations = b3MaxInt( taskContext->pushBackIterations, context.pushBackIterations ); + taskContext->rootIterations = b3MaxInt( taskContext->rootIterations, context.rootIterations ); + + float ms = b3GetMilliseconds( ticks ); + if ( ms > 1000.0f * b3GetStallThreshold() ) + { + const char* nameFast = b3FindNameWithDefault( &world->names, fastBody->nameId, "NULL" ); + b3Vec3 c1 = sweep.c1; + b3Vec3 c2 = sweep.c2; + int vc = context.visitCount; + b3Log( "CCD stall: duration %.1f ms and visit count %d for %s: c1 = (%g, %g, %g), c2 = (%g, %g, %g)", ms, vc, nameFast, + c1.x, c1.y, c1.z, c2.x, c2.y, c2.z ); + } + + b3TracyCZoneEnd( ccd ); +} + +// Implements b3ParallelForCallback +static void b3FinalizeBodiesTask( int startIndex, int endIndex, int workerIndex, void* context ) +{ + b3TracyCZoneNC( finalize_bodies, "Finalize", b3_colorMediumSeaGreen, true ); + + b3StepContext* stepContext = (b3StepContext*)context; + b3World* world = stepContext->world; + b3Body* bodies = world->bodies.data; + b3BodySim* sims = stepContext->sims; + b3BodyState* states = stepContext->states; + + B3_ASSERT( endIndex <= world->bodyMoveEvents.count ); + + bool enableSleep = world->enableSleep; + bool enableContinuous = world->enableContinuous; + float timeStep = stepContext->dt; + float invTimeStep = stepContext->inv_dt; + uint16_t worldId = world->worldId; + + // The body move event array has should already have the correct size + b3BodyMoveEvent* moveEvents = world->bodyMoveEvents.data; + + b3TaskContext* taskContext = world->taskContexts.data + workerIndex; + b3BitSet* enlargedSimBitSet = &taskContext->enlargedSimBitSet; + b3BitSet* awakeIslandBitSet = &taskContext->awakeIslandBitSet; + + const float speculativeScalar = B3_SPECULATIVE_DISTANCE; + + for ( int simIndex = startIndex; simIndex < endIndex; ++simIndex ) + { + b3BodyState* state = states + simIndex; + b3BodySim* sim = sims + simIndex; + + b3Vec3 v = state->linearVelocity; + b3Vec3 w = state->angularVelocity; + b3Vec3 localOmega = b3InvRotateVector( sim->transform.q, w ); + b3Vec3 localDeltaRotation = b3InvRotateVector( sim->transform.q, state->deltaRotation.v ); + + if ( b3IsValidVec3( v ) == false || b3IsValidVec3( w ) == false ) + { + const char* name = b3FindNameWithDefault( &world->names, bodies[sim->bodyId].nameId, "NULL" ); + b3Log( "unstable: %s", name ); + } + + B3_ASSERT( b3IsValidVec3( v ) ); + B3_ASSERT( b3IsValidVec3( w ) ); + + sim->center = b3OffsetPos( sim->center, state->deltaPosition ); + sim->transform.q = b3NormalizeQuat( b3MulQuat( state->deltaRotation, sim->transform.q ) ); + + // Use the velocity of the farthest point on the body to account for rotation. + b3Vec3 velocityArc = b3ModifiedCross( b3Abs( localOmega ), sim->maxExtent ); + float maxVelocity = b3Length( v ) + b3Length( velocityArc ); + + // Sleep needs to observe position correction as well as true velocity. + // q = [sin(theta/2) * v, cos(theta/2)] + // for small angles abs(theta) ~= 2 * length(sin(theta/2) * v) + b3Vec3 rotationArc = b3ModifiedCross( b3Abs( localDeltaRotation ), sim->maxExtent ); + float maxDeltaPosition = b3Length( state->deltaPosition ) + 2.0f * b3Length( rotationArc ); + + // Position correction is not as important for sleep as true velocity. + float positionSleepFactor = 0.5f; + float sleepVelocity = b3MaxFloat( maxVelocity, positionSleepFactor * invTimeStep * maxDeltaPosition ); + + // reset state deltas + state->deltaPosition = b3Vec3_zero; + state->deltaRotation = b3Quat_identity; + + sim->transform.p = b3OffsetPos( sim->center, b3Neg( b3RotateVector( sim->transform.q, sim->localCenter ) ) ); + + // cache miss here, however I need the shape list below + b3Body* body = bodies + sim->bodyId; + body->bodyMoveIndex = simIndex; + body->sleepVelocity = sleepVelocity; + + moveEvents[simIndex].userData = body->userData; + moveEvents[simIndex].transform = sim->transform; + moveEvents[simIndex].bodyId = (b3BodyId){ sim->bodyId + 1, worldId, body->generation }; + moveEvents[simIndex].fellAsleep = false; + + // reset applied force and torque + sim->force = b3Vec3_zero; + sim->torque = b3Vec3_zero; + + // If you hit this then it means you deferred mass computation but never called b3Body_ApplyMassFromShapes + // or b3Body_SetMassData. + B3_ASSERT( ( body->flags & b3_dirtyMass ) == 0 ); + + body->flags &= ~b3_bodyTransientFlags; + body->flags |= ( sim->flags & ( b3_isSpeedCapped | b3_hadTimeOfImpact ) ); + body->flags |= ( state->flags & ( b3_isSpeedCapped | b3_hadTimeOfImpact ) ); + sim->flags &= ~b3_bodyTransientFlags; + state->flags &= ~b3_bodyTransientFlags; + + if ( enableSleep == false || ( body->flags & b3_enableSleep ) == 0 || sleepVelocity > body->sleepThreshold ) + { + // Body is not sleepy + body->sleepTime = 0.0f; + + const float safetyFactor = 0.5f; + float maxMotion = b3MaxFloat( maxDeltaPosition, maxVelocity * timeStep ); + if ( body->type == b3_dynamicBody && enableContinuous && maxMotion > safetyFactor * sim->minExtent ) + { + // This flag is only retained for debug draw + sim->flags |= b3_isFast; + + // Store in fast array for the continuous collision stage + // This is deterministic because the order of TOI sweeps doesn't matter + if ( sim->flags & b3_isBullet ) + { + int bulletIndex = b3AtomicFetchAddInt( &stepContext->bulletBodyCount, 1 ); + stepContext->bulletBodies[bulletIndex] = simIndex; + } + else + { + b3SolveContinuous( world, simIndex, taskContext ); + } + } + else + { + // Body is safe to advance + sim->center0 = sim->center; + sim->rotation0 = sim->transform.q; + } + } + else + { + // Body is safe to advance and is falling asleep + sim->center0 = sim->center; + sim->rotation0 = sim->transform.q; + body->sleepTime += timeStep; + } + + // Update world space inverse inertia tensor. + b3Matrix3 rotationMatrix = b3MakeMatrixFromQuat( sim->transform.q ); + sim->invInertiaWorld = b3MulMM( b3MulMM( rotationMatrix, sim->invInertiaLocal ), b3Transpose( rotationMatrix ) ); + + // Any single body in an island can keep it awake + b3Island* island = b3Array_Get( world->islands, body->islandId ); + if ( body->sleepTime < B3_TIME_TO_SLEEP ) + { + // keep island awake + int islandIndex = island->localIndex; + b3SetBit( awakeIslandBitSet, islandIndex ); + } + else if ( island->constraintRemoveCount > 0 ) + { + // Body wants to sleep but its island needs splitting first. Track the sleepiest candidate. + // Break sleep time ties using the island id to ensure determinism. The cross worker reduction + // breaks ties the same way. + if ( body->sleepTime > taskContext->splitSleepTime || + ( body->sleepTime == taskContext->splitSleepTime && body->islandId > taskContext->splitIslandId ) ) + { + // pick the sleepiest candidate + taskContext->splitIslandId = body->islandId; + taskContext->splitSleepTime = body->sleepTime; + } + } + + // Update shapes AABBs + b3WorldTransform transform = sim->transform; + bool isFast = ( sim->flags & b3_isFast ) != 0; + int shapeId = body->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = b3Array_Get( world->shapes, shapeId ); + + if ( isFast ) + { + // For fast non-bullet bodies the AABB has already been updated in b3SolveContinuous + // For fast bullet bodies the AABB will be updated at a later stage + + // Add to enlarged shapes regardless of AABB changes. + // Bit-set to keep the move array sorted + b3SetBit( enlargedSimBitSet, simIndex ); + } + else + { + b3AABB aabb = b3ComputeFatShapeAABB( shape, transform, speculativeScalar ); + shape->aabb = aabb; + + B3_ASSERT( ( shape->flags & b3_enlargedAABB ) == 0 ); + + if ( b3AABB_Contains( shape->fatAABB, aabb ) == false ) + { + float marginScalar = shape->aabbMargin; + b3Vec3 aabbMargin = { marginScalar, marginScalar, marginScalar }; + shape->fatAABB = (b3AABB){ b3Sub( aabb.lowerBound, aabbMargin ), b3Add( aabb.upperBound, aabbMargin ) }; + shape->flags |= b3_enlargedAABB; + + // Bit-set to keep the move array sorted + b3SetBit( enlargedSimBitSet, simIndex ); + } + } + + shapeId = shape->nextShapeId; + } + } + + b3TracyCZoneEnd( finalize_bodies ); +} + +typedef struct b3BlockDim +{ + // number of items per block (except last block) + int size; + + // total number of blocks + int count; +} b3BlockDim; + +// A block is a range of tasks, a start index and count as a sub-array. Each worker receives at +// most M blocks of work. The workers may receive less blocks if there is not sufficient work. +// Each block of work has a minimum number of elements (block size). This in turn may limit the +// number of blocks. If there are many elements then the block size is increased so there are +// still at most M blocks of work per worker. M is a tunable number that has two goals: +// 1. keep M small to reduce overhead +// 2. keep M large enough for other workers to be able to steal work +// The block size is a power of two to make math efficient. +static inline b3BlockDim b3ComputeBlockCount( int itemCount, int minSize, int maxBlockCount ) +{ + b3BlockDim dim = { 0 }; + if ( itemCount == 0 ) + { + return dim; + } + + if ( itemCount <= minSize * maxBlockCount ) + { + dim.size = minSize; + } + else + { + dim.size = ( itemCount + maxBlockCount - 1 ) / maxBlockCount; + } + + dim.count = ( itemCount + dim.size - 1 ) / dim.size; + + B3_ASSERT( dim.count >= 1 ); + B3_ASSERT( dim.size * dim.count >= itemCount ); + + return dim; +} + +// Initialize solver blocks for a contiguous range of items. Computes block size internally +// from the same parameters used by b3ComputeBlockCount. The atomic claim counter is zeroed +// so workers can CAS (0, 1) on the first stage that owns these blocks. +static void b3InitBlocks( b3SyncBlock* blocks, b3BlockDim dim, int itemCount, uint8_t blockType, uint8_t colorIndex ) +{ + if ( dim.count == 0 ) + { + return; + } + + B3_ASSERT( itemCount >= dim.count ); + + // Compute the number of elements per block + int blockSize = dim.size; + + // Simulation too big + B3_ASSERT( blockSize <= UINT16_MAX ); + + for ( int i = 0; i < dim.count; ++i ) + { + blocks[i].block.startIndex = i * blockSize; + blocks[i].block.count = (uint16_t)blockSize; + blocks[i].block.blockType = blockType; + blocks[i].block.colorIndex = colorIndex; + b3AtomicStoreInt( &blocks[i].syncIndex, 0 ); + } + + // The last block may not be full + blocks[dim.count - 1].block.count = (uint16_t)( itemCount - ( dim.count - 1 ) * blockSize ); + + B3_VALIDATE( blocks[dim.count - 1].block.count <= blockSize ); + B3_VALIDATE( ( dim.count - 1 ) * dim.size + blocks[dim.count - 1].block.count == itemCount ); +} + +static inline b3SolverStage* b3InitStage( b3SolverStage* stage, b3SolverStageType type, b3SyncBlock* blocks, int blockCount, + uint8_t colorIndex ) +{ + stage->type = type; + stage->blocks = blocks; + stage->blockCount = blockCount; + stage->colorIndex = colorIndex; + b3AtomicStoreInt( &stage->completionCount, 0 ); + return stage + 1; +} + +// Initialize one stage per color for each iteration. Used for warm start, solve, relax, and restitution. +// All iterations of a given color share the same b3SyncBlock array so the per-block syncIndex +// grows monotonically across stages within that color. +static b3SolverStage* b3InitColorStages( b3SolverStage* stage, b3SolverStageType type, int iterations, int activeColorCount, + b3SyncBlock** colorBlocks, int* colorBlockCounts, int* activeColorIndices ) +{ + for ( int j = 0; j < iterations; ++j ) + { + for ( int i = 0; i < activeColorCount; ++i ) + { + stage = b3InitStage( stage, type, colorBlocks[i], colorBlockCounts[i], (uint8_t)activeColorIndices[i] ); + } + } + return stage; +} + +static void b3ExecuteBlock( b3SolverStage* stage, b3StepContext* context, b3SolverBlock block, int workerIndex ) +{ + b3SolverStageType stageType = stage->type; + b3SolverBlockType blockType = (b3SolverBlockType)block.blockType; + + switch ( stageType ) + { + case b3_stagePrepareJoints: + b3PrepareJointsTask( block, context ); + break; + + case b3_stagePrepareWideContacts: + b3PrepareContacts_Convex( block, context ); + break; + + case b3_stagePrepareContacts: + b3PrepareContacts_Mesh( block, context ); + break; + + case b3_stageIntegrateVelocities: + b3IntegrateVelocitiesTask( block, context ); + break; + + case b3_stageWarmStart: + if ( blockType == b3_graphJointBlock ) + { + b3WarmStartJointsTask( block, context ); + } + else if ( blockType == b3_graphWideContactBlock ) + { + b3WarmStartContacts_Convex( block, context ); + } + else + { + b3WarmStartContacts_Mesh( block, context ); + } + break; + + case b3_stageSolve: + if ( blockType == b3_graphJointBlock ) + { + bool useBias = true; + b3SolveJointsTask( block, context, useBias, workerIndex ); + } + else if ( blockType == b3_graphWideContactBlock ) + { + bool useBias = true; + b3SolveContacts_Convex( block, context, useBias ); + } + else + { + bool useBias = true; + b3SolveContacts_Mesh( block, context, useBias ); + } + break; + + case b3_stageIntegratePositions: + b3IntegratePositionsTask( block, context ); + break; + + case b3_stageRelax: + if ( blockType == b3_graphJointBlock ) + { + bool useBias = false; + b3SolveJointsTask( block, context, useBias, workerIndex ); + } + else if ( blockType == b3_graphWideContactBlock ) + { + bool useBias = false; + b3SolveContacts_Convex( block, context, useBias ); + } + else + { + bool useBias = false; + b3SolveContacts_Mesh( block, context, useBias ); + } + break; + + case b3_stageRestitution: + if ( blockType == b3_graphWideContactBlock ) + { + b3ApplyRestitution_Convex( block, context ); + } + else if ( blockType == b3_graphContactBlock ) + { + b3ApplyRestitution_Mesh( block, context ); + } + break; + + case b3_stageStoreWideImpulses: + b3StoreImpulses_Convex( block, context, workerIndex ); + break; + + case b3_stageStoreImpulses: + b3StoreImpulses_Mesh( block, context, workerIndex ); + break; + } +} + +// This staggers the worker start indices so they avoid touching the same solver blocks +static inline int GetWorkerStartIndex( int workerIndex, int blockCount, int workerCount ) +{ + if ( blockCount <= workerCount ) + { + return workerIndex < blockCount ? workerIndex : B3_NULL_INDEX; + } + + int blocksPerWorker = blockCount / workerCount; + int remainder = blockCount - blocksPerWorker * workerCount; + return blocksPerWorker * workerIndex + b3MinInt( remainder, workerIndex ); +} + +// Execute a stage, which is an array of solver blocks, each controlled with an atomic sync index. +// Each worker starts at its home index and sweeps the ring, CAS-claiming any unclaimed blocks. +static void b3ExecuteStage( b3SolverStage* stage, b3StepContext* context, int previousSyncIndex, int syncIndex, int workerIndex ) +{ + int completedCount = 0; + b3SyncBlock* blocks = stage->blocks; + int blockCount = stage->blockCount; + + int startIndex = GetWorkerStartIndex( workerIndex, blockCount, context->workerCount ); + if ( startIndex == B3_NULL_INDEX ) + { + return; + } + + B3_ASSERT( 0 <= startIndex && startIndex < blockCount ); + + int blockIndex = startIndex; + for ( int i = 0; i < blockCount; ++i ) + { + if ( b3AtomicCompareExchangeInt( &blocks[blockIndex].syncIndex, previousSyncIndex, syncIndex ) ) + { + B3_ASSERT( completedCount < blockCount ); + + // Pass the descriptor by value -- the wrapping b3SyncBlock holds the atomic + // syncIndex but we only copy .block, so the struct copy never aliases the CAS target. + b3ExecuteBlock( stage, context, blocks[blockIndex].block, workerIndex ); + completedCount += 1; + } + + blockIndex += 1; + if ( blockIndex >= blockCount ) + { + blockIndex = 0; + } + } + + (void)b3AtomicFetchAddInt( &stage->completionCount, completedCount ); +} + +// Execute a stage on worker 0 (main thread). +static void b3ExecuteMainStage( b3SolverStage* stage, b3StepContext* context, uint32_t syncBits ) +{ + int blockCount = stage->blockCount; + if ( blockCount == 0 ) + { + return; + } + + const int workerIndex = 0; + + if ( blockCount == 1 ) + { + b3ExecuteBlock( stage, context, stage->blocks[0].block, workerIndex ); + } + else + { + b3AtomicStoreU32( &context->atomicSyncBits, syncBits ); + + int syncIndex = ( syncBits >> 16 ) & 0xFFFF; + B3_ASSERT( syncIndex > 0 ); + int previousSyncIndex = syncIndex - 1; + + b3ExecuteStage( stage, context, previousSyncIndex, syncIndex, workerIndex ); + + // Spin waiting for thieves to finish + while ( b3AtomicLoadInt( &stage->completionCount ) != blockCount ) + { + b3Pause(); + } + + b3AtomicStoreInt( &stage->completionCount, 0 ); + } +} + +// Parallel solver task +static void b3SolverTask( void* taskContext ) +{ + b3WorkerContext* workerContext = (b3WorkerContext*)taskContext; + int workerIndex = workerContext->workerIndex; + b3StepContext* context = workerContext->context; + int activeColorCount = context->activeColorCount; + b3SolverStage* stages = context->stages; + b3Profile* profile = &context->world->profile; + + if ( workerIndex == 0 ) + { + // The orchestrator slot is a race. The calling thread of b3World_Step also enters here + // as worker 0, so progress is guaranteed even if the user's task system schedules tasks + // out of order, has fewer threads than workerCount, or runs the task synchronously + // inside enqueueTaskFcn. Whoever wins the CAS becomes the orchestrator; the loser + // returns and lets the spinner-only path handle workers >0. + if ( b3AtomicCompareExchangeInt( &context->mainClaimed, 0, 1 ) == false ) + { + return; + } + + // Main thread synchronizes the workers and does work itself. + // + // This needs to be a task for the main thread because the user's task system may execute + // the tasks serially and this is the first task. This single task is able to fully + // complete all work even if all other workers are blocked. + + // Stages are re-used by loops so that I don't need more stages for large substep counts. + // The sync indices grow monotonically for the body/graph/constraint groupings because they share solver blocks. + // The stage index and sync indices are combined in to sync bits for atomic synchronization. + // The workers need to compute the previous sync index for a given stage so that CAS works correctly. This + // setup makes this easy to do. + + /* + b3_stagePrepareJoints, + b3_stagePrepareContacts, + b3_stageIntegrateVelocities, + b3_stageWarmStart, + b3_stageSolve, + b3_stageIntegratePositions, + b3_stageRelax, + b3_stageRestitution, + b3_stageStoreImpulses + */ + + uint64_t ticks = b3GetTicks(); + + int bodySyncIndex = 1; + int stageIndex = 0; + + // Prepare joint constraints + uint32_t jointSyncIndex = 1; + uint32_t syncBits = ( jointSyncIndex << 16 ) | stageIndex; + B3_ASSERT( stages[stageIndex].type == b3_stagePrepareJoints ); + b3ExecuteMainStage( stages + stageIndex, context, syncBits ); + stageIndex += 1; + jointSyncIndex += 1; + + // Prepare convex contact constraints + uint32_t convexSyncIndex = 1; + syncBits = ( convexSyncIndex << 16 ) | stageIndex; + B3_ASSERT( stages[stageIndex].type == b3_stagePrepareWideContacts ); + b3ExecuteMainStage( stages + stageIndex, context, syncBits ); + stageIndex += 1; + convexSyncIndex += 1; + + // Prepare mesh contact constraints + uint32_t meshSyncIndex = 1; + syncBits = ( meshSyncIndex << 16 ) | stageIndex; + B3_ASSERT( stages[stageIndex].type == b3_stagePrepareContacts ); + b3ExecuteMainStage( stages + stageIndex, context, syncBits ); + stageIndex += 1; + meshSyncIndex += 1; + + // Single-threaded overflow work. These constraints don't fit in the graph coloring. + b3PrepareJoints_Overflow( context ); + b3PrepareContacts_Overflow( context ); + + profile->prepareConstraints += b3GetMillisecondsAndReset( &ticks ); + + int graphSyncIndex = 1; + int subStepCount = context->subStepCount; + for ( int subStepIndex = 0; subStepIndex < subStepCount; ++subStepIndex ) + { + // stageIndex restarted each iteration + // syncBits still increases monotonically because the upper bits increase each iteration + int iterationStageIndex = stageIndex; + + // Integrate velocities + syncBits = ( bodySyncIndex << 16 ) | iterationStageIndex; + B3_ASSERT( stages[iterationStageIndex].type == b3_stageIntegrateVelocities ); + b3ExecuteMainStage( stages + iterationStageIndex, context, syncBits ); + iterationStageIndex += 1; + bodySyncIndex += 1; + + profile->integrateVelocities += b3GetMillisecondsAndReset( &ticks ); + + // Warm start constraints + b3WarmStartJoints_Overflow( context ); + b3WarmStartContacts_Overflow( context ); + + for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex ) + { + syncBits = ( graphSyncIndex << 16 ) | iterationStageIndex; + B3_ASSERT( stages[iterationStageIndex].type == b3_stageWarmStart ); + b3ExecuteMainStage( stages + iterationStageIndex, context, syncBits ); + iterationStageIndex += 1; + } + graphSyncIndex += 1; + + profile->warmStart += b3GetMillisecondsAndReset( &ticks ); + + // Solve constraints + bool useBias = true; + for ( int j = 0; j < ITERATIONS; ++j ) + { + // Overflow constraints have lower priority. Typically these are dynamic-vs-dynamic. + b3SolveJoints_Overflow( context, useBias ); + b3SolveContacts_Overflow( context, useBias ); + + for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex ) + { + syncBits = ( graphSyncIndex << 16 ) | iterationStageIndex; + B3_ASSERT( stages[iterationStageIndex].type == b3_stageSolve ); + b3ExecuteMainStage( stages + iterationStageIndex, context, syncBits ); + iterationStageIndex += 1; + } + graphSyncIndex += 1; + } + + profile->solveImpulses += b3GetMillisecondsAndReset( &ticks ); + + // Integrate positions + B3_ASSERT( stages[iterationStageIndex].type == b3_stageIntegratePositions ); + syncBits = ( bodySyncIndex << 16 ) | iterationStageIndex; + b3ExecuteMainStage( stages + iterationStageIndex, context, syncBits ); + iterationStageIndex += 1; + bodySyncIndex += 1; + + profile->integratePositions += b3GetMillisecondsAndReset( &ticks ); + + // Relax constraints + useBias = false; + for ( int j = 0; j < RELAX_ITERATIONS; ++j ) + { + b3SolveJoints_Overflow( context, useBias ); + b3SolveContacts_Overflow( context, useBias ); + + for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex ) + { + syncBits = ( graphSyncIndex << 16 ) | iterationStageIndex; + B3_ASSERT( stages[iterationStageIndex].type == b3_stageRelax ); + b3ExecuteMainStage( stages + iterationStageIndex, context, syncBits ); + iterationStageIndex += 1; + } + graphSyncIndex += 1; + } + + profile->relaxImpulses += b3GetMillisecondsAndReset( &ticks ); + } + + // Advance the stage according to the sub-stepping tasks just completed + // integrate velocities / warm start / solve / integrate positions / relax + stageIndex += 1 + activeColorCount + ITERATIONS * activeColorCount + 1 + RELAX_ITERATIONS * activeColorCount; + + // Restitution + { + b3ApplyRestitution_Overflow( context ); + + int iterStageIndex = stageIndex; + for ( int colorIndex = 0; colorIndex < activeColorCount; ++colorIndex ) + { + syncBits = ( graphSyncIndex << 16 ) | iterStageIndex; + B3_ASSERT( stages[iterStageIndex].type == b3_stageRestitution ); + b3ExecuteMainStage( stages + iterStageIndex, context, syncBits ); + iterStageIndex += 1; + } + // graphSyncIndex += 1; + stageIndex += activeColorCount; + } + + profile->applyRestitution += b3GetMillisecondsAndReset( &ticks ); + + // Store impulses + b3StoreImpulses_Overflow( context ); + + syncBits = ( convexSyncIndex << 16 ) | stageIndex; + B3_ASSERT( stages[stageIndex].type == b3_stageStoreWideImpulses ); + b3ExecuteMainStage( stages + stageIndex, context, syncBits ); + stageIndex += 1; + + syncBits = ( meshSyncIndex << 16 ) | stageIndex; + B3_ASSERT( stages[stageIndex].type == b3_stageStoreImpulses ); + b3ExecuteMainStage( stages + stageIndex, context, syncBits ); + stageIndex += 1; + + profile->storeImpulses += b3GetMillisecondsAndReset( &ticks ); + + // Signal workers to finish + b3AtomicStoreU32( &context->atomicSyncBits, UINT_MAX ); + + B3_ASSERT( stageIndex == context->stageCount ); + return; + } + + // Worker spins and waits for work + uint32_t lastSyncBits = 0; + // uint64_t maxSpinTime = 10; + while ( true ) + { + // Spin until main thread bumps changes the sync bits. This can waste significant time overall, but it is necessary for + // parallel simulation with graph coloring. + // todo improve this spinner + uint32_t syncBits; + int spinCount = 0; + while ( ( syncBits = b3AtomicLoadU32( &context->atomicSyncBits ) ) == lastSyncBits ) + { + if ( spinCount > 5 ) + { + b3Yield(); + spinCount = 0; + } + else + { + // Using the cycle counter helps to account for variation in mm_pause timing across different + // CPUs. However, this is X64 only. + // uint64_t prev = __rdtsc(); + // do + //{ + // b3Pause(); + //} + // while ((__rdtsc() - prev) < maxSpinTime); + // maxSpinTime += 10; + b3Pause(); + b3Pause(); + spinCount += 1; + } + } + + if ( syncBits == UINT_MAX ) + { + // sentinel hit + break; + } + + int stageIndex = syncBits & 0xFFFF; + B3_ASSERT( stageIndex < context->stageCount ); + + int syncIndex = ( syncBits >> 16 ) & 0xFFFF; + B3_ASSERT( syncIndex > 0 ); + + int previousSyncIndex = syncIndex - 1; + + b3SolverStage* stage = stages + stageIndex; + b3ExecuteStage( stage, context, previousSyncIndex, syncIndex, workerIndex ); + + lastSyncBits = syncBits; + } +} + +static void b3BulletBodyTask( int startIndex, int endIndex, int workerIndex, void* context ) +{ + b3TracyCZoneNC( bullet_body_task, "Bullet Body Task", b3_colorLightSkyBlue, true ); + + b3StepContext* stepContext = (b3StepContext*)context; + b3TaskContext* taskContext = b3Array_Get( stepContext->world->taskContexts, workerIndex ); + + B3_ASSERT( startIndex <= endIndex ); + + for ( int i = startIndex; i < endIndex; ++i ) + { + int simIndex = stepContext->bulletBodies[i]; + b3SolveContinuous( stepContext->world, simIndex, taskContext ); + } + + b3TracyCZoneEnd( bullet_body_task ); +} + +#if B3_SIMD_WIDTH == 4 +#define B3_SIMD_SHIFT 2 +#else +#define B3_SIMD_SHIFT 0 +#endif + +// Solve with graph coloring +void b3Solve( b3World* world, b3StepContext* stepContext ) +{ + // Only count steps that advance the simulation + world->stepIndex += 1; + + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + int awakeBodyCount = awakeSet->bodySims.count; + if ( awakeBodyCount == 0 ) + { + b3ValidateNoEnlarged( &world->broadPhase ); + return; + } + + // Solve constraints using graph coloring + { + b3TracyCZoneNC( solver_setup, "Solver Setup", b3_colorDarkOrange, true ); + uint64_t setupTicks = b3GetTicks(); + + // Prepare buffers for continuous collision (fast bodies) + b3AtomicStoreInt( &stepContext->bulletBodyCount, 0 ); + stepContext->bulletBodies = (int*)b3StackAlloc( &world->stack, awakeBodyCount * sizeof( int ), "bullet bodies" ); + + b3ConstraintGraph* graph = &world->constraintGraph; + b3GraphColor* colors = graph->colors; + + stepContext->sims = awakeSet->bodySims.data; + stepContext->states = awakeSet->bodyStates.data; + + // count contacts, joints, and colors + int activeColorCount = 0; + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT - 1; ++i ) + { + int perColorContactCount = colors[i].convexContacts.count + colors[i].contacts.count; + int perColorJointCount = colors[i].jointSims.count; + int occupancyCount = perColorContactCount + perColorJointCount; + activeColorCount += occupancyCount > 0 ? 1 : 0; + } + + // prepare for move events + b3Array_Resize( world->bodyMoveEvents, awakeBodyCount ); + + int workerCount = world->workerCount; + + // Target 4 blocks per worker to allow work stealing + const int maxBlockCount = 4 * workerCount; + + // Body blocks are for parallel iteration over bodies directly (integration, update transforms) + int minBodiesPerBlock = 32; + b3BlockDim bodyDim = b3ComputeBlockCount( awakeBodyCount, minBodiesPerBlock, maxBlockCount ); + + const int minContactsPerBlock = 4; + const int minJointsPerBlock = 4; + + // Configure blocks for tasks parallel-for each active graph color + // The blocks are a mix of convex contact, mesh contact, and joint blocks + int activeColorIndices[B3_GRAPH_COLOR_COUNT]; + int colorWideContactCounts[B3_GRAPH_COLOR_COUNT]; + int colorContactCounts[B3_GRAPH_COLOR_COUNT]; + // int colorManifoldCounts[B3_GRAPH_COLOR_COUNT]; + int colorJointCounts[B3_GRAPH_COLOR_COUNT]; + b3BlockDim graphWideContactDims[B3_GRAPH_COLOR_COUNT]; + b3BlockDim graphContactDims[B3_GRAPH_COLOR_COUNT]; + b3BlockDim graphJointDims[B3_GRAPH_COLOR_COUNT]; + int graphBlockCount = 0; + + // c is the active color index + int wideContactCount = 0; + int contactCount = 0; + int manifoldCount = 0; + int jointCount = 0; + int c = 0; + for ( int i = 0; i < B3_GRAPH_COLOR_COUNT - 1; ++i ) + { + b3GraphColor* color = colors + i; + int colorConvexContactCount = color->convexContacts.count; + int colorContactCount = color->contacts.count; + int colorJointCount = color->jointSims.count; + + if ( colorConvexContactCount + colorContactCount + colorJointCount == 0 ) + { + continue; + } + + activeColorIndices[c] = i; + + // Ceiling for wide constraint count + int colorWideConstraintCount = + colorConvexContactCount > 0 ? ( ( colorConvexContactCount - 1 ) >> B3_SIMD_SHIFT ) + 1 : 0; + wideContactCount += colorWideConstraintCount; + colorWideContactCounts[c] = colorWideConstraintCount; + + colorContactCounts[c] = colorContactCount; + contactCount += colorContactCount; + + // Compute manifold starts and accumulate manifold count + for ( int j = 0; j < colorContactCount; ++j ) + { + color->contacts.data[j].manifoldStart = manifoldCount; + manifoldCount += color->contacts.data[j].manifoldCount; + } + + colorJointCounts[c] = colorJointCount; + jointCount += colorJointCount; + + // Solver block dimensions + graphWideContactDims[c] = b3ComputeBlockCount( colorWideConstraintCount, minContactsPerBlock, maxBlockCount ); + graphContactDims[c] = b3ComputeBlockCount( colorContactCount, minContactsPerBlock, maxBlockCount ); + graphJointDims[c] = b3ComputeBlockCount( colorJointCount, minJointsPerBlock, maxBlockCount ); + graphBlockCount += graphWideContactDims[c].count + graphContactDims[c].count + graphJointDims[c].count; + + c += 1; + } + activeColorCount = c; + + // Prepare and store run as one flat parallel-for over the entire wide constraint range, + // partitioned into uniformly sized blocks. Color info is consulted inside the task via + // a small span array, so blocks do not need to honor color boundaries here. + b3BlockDim convexPrepareDim = b3ComputeBlockCount( wideContactCount, minContactsPerBlock, maxBlockCount ); + b3BlockDim meshPrepareDim = b3ComputeBlockCount( contactCount, minContactsPerBlock, maxBlockCount ); + b3BlockDim jointPrepareDim = b3ComputeBlockCount( jointCount, minJointsPerBlock, maxBlockCount ); + + int wideContactByteCount = b3GetWideContactConstraintByteCount(); + b3ContactConstraintWide* wideConstraints = + (b3ContactConstraintWide*)b3StackAlloc( &world->stack, wideContactCount * wideContactByteCount, "wide contacts" ); + b3ContactConstraint* contactConstraints = + (b3ContactConstraint*)b3StackAlloc( &world->stack, contactCount * sizeof( b3ContactConstraint ), "contacts" ); + b3ManifoldConstraint* manifoldConstraints = (b3ManifoldConstraint*)b3StackAlloc( + &world->stack, manifoldCount * sizeof( b3ManifoldConstraint ), "manifold constraints" ); + + b3GraphColor* overflow = colors + B3_OVERFLOW_INDEX; + int overflowCount = overflow->contacts.count; + int overflowManifoldCount = 0; + for ( int i = 0; i < overflowCount; ++i ) + { + overflow->contacts.data[i].manifoldStart = overflowManifoldCount; + overflowManifoldCount += overflow->contacts.data[i].manifoldCount; + } + + overflow->contactConstraints = (b3ContactConstraint*)b3StackAlloc( + &world->stack, overflowCount * sizeof( b3ContactConstraint ), "overflow contacts" ); + overflow->manifoldConstraints = (b3ManifoldConstraint*)b3StackAlloc( + &world->stack, overflowManifoldCount * sizeof( b3ManifoldConstraint ), "overflow manifolds" ); + + // Build the span table for the flat prepare/store parallel-for while I slice the + // wide constraint buffer across colors. One entry per active color plus a sentinel + // at wideContactCount. + b3WidePrepareSpan widePrepareSpans[B3_GRAPH_COLOR_COUNT + 1]; + b3ContactPrepareSpan contactPrepareSpans[B3_GRAPH_COLOR_COUNT + 1]; + b3JointPrepareSpan jointPrepareSpans[B3_GRAPH_COLOR_COUNT + 1]; + + // Distribute transient constraints to each graph color and prepare spans + // todo it might be simpler for solver blocks to index into the global arrays + { + int wideBase = 0; + int contactBase = 0; + int jointBase = 0; + for ( int i = 0; i < activeColorCount; ++i ) + { + int j = activeColorIndices[i]; + b3GraphColor* color = colors + j; + + int colorConvexContactCount = color->convexContacts.count; + widePrepareSpans[i].start = wideBase; + widePrepareSpans[i].count = colorConvexContactCount; + widePrepareSpans[i].contacts = color->convexContacts.data; + + if ( colorConvexContactCount == 0 ) + { + color->wideConstraints = NULL; + color->wideConstraintCount = 0; + } + else + { + color->wideConstraints = + (b3ContactConstraintWide*)( (uint8_t*)wideConstraints + wideBase * wideContactByteCount ); + + int colorContactCountW = ( ( colorConvexContactCount - 1 ) >> B3_SIMD_SHIFT ) + 1; + color->wideConstraintCount = colorContactCountW; + + // Zero remainder lanes in the tail wide slot so prepare workers don't need to + // initialize them. + if ( ( colorConvexContactCount & ( B3_SIMD_WIDTH - 1 ) ) != 0 ) + { + memset( (uint8_t*)color->wideConstraints + ( colorContactCountW - 1 ) * wideContactByteCount, 0, + wideContactByteCount ); + } + + wideBase += colorContactCountW; + } + + int colorContactCount = color->contacts.count; + contactPrepareSpans[i].start = contactBase; + contactPrepareSpans[i].count = colorContactCount; + contactPrepareSpans[i].contacts = color->contacts.data; + + if ( colorContactCount == 0 ) + { + color->contactConstraints = NULL; + color->contactConstraintCount = 0; + } + else + { + color->contactConstraints = contactConstraints + contactBase; + color->contactConstraintCount = colorContactCount; + contactBase += colorContactCount; + } + + jointPrepareSpans[i].start = jointBase; + jointPrepareSpans[i].count = color->jointSims.count; + jointPrepareSpans[i].joints = color->jointSims.data; + jointBase += color->jointSims.count; + } + + // Sentinels + widePrepareSpans[activeColorCount].start = wideContactCount; + widePrepareSpans[activeColorCount].count = 0; + widePrepareSpans[activeColorCount].contacts = NULL; + B3_ASSERT( wideBase == wideContactCount ); + + contactPrepareSpans[activeColorCount].start = contactCount; + contactPrepareSpans[activeColorCount].count = 0; + contactPrepareSpans[activeColorCount].contacts = NULL; + B3_ASSERT( contactBase == contactCount ); + + jointPrepareSpans[activeColorCount].start = jointCount; + jointPrepareSpans[activeColorCount].count = 0; + jointPrepareSpans[activeColorCount].joints = NULL; + B3_ASSERT( jointBase == jointCount ); + } + + //// Special span for overflow to allow for function re-use + b3ContactPrepareSpan overflowSpans[2] = { 0 }; + overflowSpans[0].start = 0; + overflowSpans[0].count = overflow->contacts.count; + overflowSpans[0].contacts = overflow->contacts.data; + overflowSpans[1].start = overflow->contacts.count; + overflowSpans[1].count = 0; + overflowSpans[1].contacts = NULL; + + int stageCount = 0; + + // b3_stagePrepareJoints + stageCount += 1; + // b3_stagePrepareWideContacts + stageCount += 1; + // b3_stagePrepareContacts + stageCount += 1; + // b3_stageIntegrateVelocities + stageCount += 1; + // b3_stageWarmStart + stageCount += activeColorCount; + // b3_stageSolve + stageCount += ITERATIONS * activeColorCount; + // b3_stageIntegratePositions + stageCount += 1; + // b3_stageRelax + stageCount += RELAX_ITERATIONS * activeColorCount; + // b3_stageRestitution + stageCount += activeColorCount; + // b3_stageStoreWideImpulses + stageCount += 1; + // b3_stageStoreImpulses + stageCount += 1; + + b3SolverStage* stages = (b3SolverStage*)b3StackAlloc( &world->stack, stageCount * sizeof( b3SolverStage ), "stages" ); + b3SyncBlock* bodyBlocks = + (b3SyncBlock*)b3StackAlloc( &world->stack, bodyDim.count * sizeof( b3SyncBlock ), "body blocks" ); + b3SyncBlock* convexBlocks = + (b3SyncBlock*)b3StackAlloc( &world->stack, convexPrepareDim.count * sizeof( b3SyncBlock ), "convex blocks" ); + b3SyncBlock* meshBlocks = + (b3SyncBlock*)b3StackAlloc( &world->stack, meshPrepareDim.count * sizeof( b3SyncBlock ), "mesh blocks" ); + b3SyncBlock* jointBlocks = + (b3SyncBlock*)b3StackAlloc( &world->stack, jointPrepareDim.count * sizeof( b3SyncBlock ), "joint blocks" ); + b3SyncBlock* graphBlocks = + (b3SyncBlock*)b3StackAlloc( &world->stack, graphBlockCount * sizeof( b3SyncBlock ), "graph blocks" ); + + // Split an awake island. This modifies: + // - stack allocator + // - world island array and solver set + // - island indices on bodies, contacts, and joints + // I'm squeezing this task in here because it may be expensive and this is a safe place to put it. + // Note: cannot split islands in parallel with FinalizeBodies + void* splitIslandTask = NULL; + if ( world->splitIslandId != B3_NULL_INDEX ) + { + if ( world->taskCount < B3_MAX_TASKS ) + { + splitIslandTask = world->enqueueTaskFcn( &b3SplitIslandTask, world, world->userTaskContext, "split" ); + world->taskCount += 1; + world->activeTaskCount += splitIslandTask == NULL ? 0 : 1; + } + else + { + b3SplitIslandTask( world ); + } + } + + // Prepare body blocks + b3InitBlocks( bodyBlocks, bodyDim, awakeBodyCount, b3_bodyBlock, UINT8_MAX ); + + // Prepare blocks as a single flat parallel-for over the whole constraint range. + // The task walks spans to decode flat slot indices back to per-color arrays. + b3InitBlocks( convexBlocks, convexPrepareDim, wideContactCount, b3_wideContactBlock, UINT8_MAX ); + b3InitBlocks( meshBlocks, meshPrepareDim, contactCount, b3_contactBlock, UINT8_MAX ); + b3InitBlocks( jointBlocks, jointPrepareDim, jointCount, b3_jointBlock, UINT8_MAX ); + + // Prepare graph work blocks. Each color gets joint blocks followed by contact blocks. + b3SyncBlock* graphColorBlocks[B3_GRAPH_COLOR_COUNT] = { 0 }; + b3SyncBlock* baseGraphBlock = graphBlocks; + int graphBlockCounts[B3_GRAPH_COLOR_COUNT] = { 0 }; + for ( int i = 0; i < activeColorCount; ++i ) + { + graphColorBlocks[i] = baseGraphBlock; + + uint8_t colorIndex = (uint8_t)activeColorIndices[i]; + b3InitBlocks( baseGraphBlock, graphJointDims[i], colorJointCounts[i], b3_graphJointBlock, colorIndex ); + baseGraphBlock += graphJointDims[i].count; + + b3InitBlocks( baseGraphBlock, graphWideContactDims[i], colorWideContactCounts[i], b3_graphWideContactBlock, + colorIndex ); + baseGraphBlock += graphWideContactDims[i].count; + + b3InitBlocks( baseGraphBlock, graphContactDims[i], colorContactCounts[i], b3_graphContactBlock, colorIndex ); + baseGraphBlock += graphContactDims[i].count; + + graphBlockCounts[i] = graphJointDims[i].count + graphWideContactDims[i].count + graphContactDims[i].count; + } + + B3_ASSERT( (ptrdiff_t)( baseGraphBlock - graphBlocks ) == graphBlockCount ); + + b3SolverStage* stage = stages; + stage = b3InitStage( stage, b3_stagePrepareJoints, jointBlocks, jointPrepareDim.count, UINT8_MAX ); + stage = b3InitStage( stage, b3_stagePrepareWideContacts, convexBlocks, convexPrepareDim.count, UINT8_MAX ); + stage = b3InitStage( stage, b3_stagePrepareContacts, meshBlocks, meshPrepareDim.count, UINT8_MAX ); + stage = b3InitStage( stage, b3_stageIntegrateVelocities, bodyBlocks, bodyDim.count, UINT8_MAX ); + stage = b3InitColorStages( stage, b3_stageWarmStart, 1, activeColorCount, graphColorBlocks, graphBlockCounts, + activeColorIndices ); + stage = b3InitColorStages( stage, b3_stageSolve, ITERATIONS, activeColorCount, graphColorBlocks, graphBlockCounts, + activeColorIndices ); + stage = b3InitStage( stage, b3_stageIntegratePositions, bodyBlocks, bodyDim.count, UINT8_MAX ); + stage = b3InitColorStages( stage, b3_stageRelax, RELAX_ITERATIONS, activeColorCount, graphColorBlocks, graphBlockCounts, + activeColorIndices ); + // Note: joint blocks mixed in, could have joint limit restitution + stage = b3InitColorStages( stage, b3_stageRestitution, 1, activeColorCount, graphColorBlocks, graphBlockCounts, + activeColorIndices ); + stage = b3InitStage( stage, b3_stageStoreWideImpulses, convexBlocks, convexPrepareDim.count, UINT8_MAX ); + stage = b3InitStage( stage, b3_stageStoreImpulses, meshBlocks, meshPrepareDim.count, UINT8_MAX ); + + B3_ASSERT( (int)( stage - stages ) == stageCount ); + + B3_ASSERT( workerCount <= B3_MAX_WORKERS ); + b3WorkerContext workerContext[B3_MAX_WORKERS]; + + stepContext->graph = graph; + stepContext->activeColorCount = activeColorCount; + stepContext->workerCount = workerCount; + stepContext->stageCount = stageCount; + stepContext->stages = stages; + stepContext->wideConstraints = wideConstraints; + stepContext->widePrepareSpans = widePrepareSpans; + stepContext->wideContactCount = wideContactCount; + stepContext->manifoldConstraints = manifoldConstraints; + stepContext->contactConstraints = contactConstraints; + stepContext->contactPrepareSpans = contactPrepareSpans; + stepContext->overflowSpans = overflowSpans; + stepContext->jointPrepareSpans = jointPrepareSpans; + b3AtomicStoreU32( &stepContext->atomicSyncBits, 0 ); + b3AtomicStoreInt( &stepContext->mainClaimed, 0 ); + + world->profile.solverSetup = b3GetMillisecondsAndReset( &setupTicks ); + b3TracyCZoneEnd( solver_setup ); + + b3TracyCZoneNC( solve_constraints, "Solve Constraints", b3_colorIndigo, true ); + uint64_t constraintTicks = b3GetTicks(); + + int jointIdCapacity = b3GetIdCapacity( &world->jointIdPool ); + int contactIdCapacity = b3GetIdCapacity( &world->contactIdPool ); + for ( int i = 0; i < workerCount; ++i ) + { + b3TaskContext* taskContext = b3Array_Get( world->taskContexts, i ); + b3SetBitCountAndClear( &taskContext->jointStateBitSet, jointIdCapacity ); + b3SetBitCountAndClear( &taskContext->hitEventBitSet, contactIdCapacity ); + taskContext->hasHitEvents = false; + + workerContext[i].context = stepContext; + workerContext[i].workerIndex = i; + + if ( world->taskCount < B3_MAX_TASKS ) + { + char buffer[16]; + snprintf( buffer, sizeof( buffer ), "solve[%d]", i ); + workerContext[i].userTask = + world->enqueueTaskFcn( &b3SolverTask, workerContext + i, world->userTaskContext, buffer ); + world->taskCount += 1; + world->activeTaskCount += workerContext[i].userTask == NULL ? 0 : 1; + } + else + { + workerContext[i].userTask = NULL; + b3SolverTask( workerContext + i ); + } + } + + // The calling thread of b3World_Step also enters b3SolverTask as worker 0 and races for the + // orchestrator slot via the CAS inside. This guarantees progress even when the user's task + // system can't run the queued worker 0 promptly: it might schedule out of order, have fewer + // threads than workerCount, or invert priority by parking the calling thread in finishTaskFcn. + // Whoever wins the CAS becomes the orchestrator; the loser returns and lets the spinner-only + // path handle workers >0. + b3WorkerContext callerContext = { stepContext, 0, NULL }; + b3SolverTask( &callerContext ); + + // Finish constraint solve + for ( int i = 0; i < workerCount; ++i ) + { + if ( workerContext[i].userTask != NULL ) + { + world->finishTaskFcn( workerContext[i].userTask, world->userTaskContext ); + world->activeTaskCount -= 1; + } + } + + // Finish island split + if ( splitIslandTask != NULL ) + { + world->finishTaskFcn( splitIslandTask, world->userTaskContext ); + world->activeTaskCount -= 1; + } + world->splitIslandId = B3_NULL_INDEX; + + world->profile.constraints = b3GetMillisecondsAndReset( &constraintTicks ); + b3TracyCZoneEnd( solve_constraints ); + + b3TracyCZoneNC( update_transforms, "Update Transforms", b3_colorMediumSeaGreen, true ); + uint64_t transformTicks = b3GetTicks(); + + // Prepare contact, enlarged body, and island bit sets used in body finalization. + int awakeIslandCount = awakeSet->islandSims.count; + for ( int i = 0; i < world->workerCount; ++i ) + { + b3TaskContext* taskContext = world->taskContexts.data + i; + b3Array_Clear( taskContext->sensorHits ); + b3SetBitCountAndClear( &taskContext->enlargedSimBitSet, awakeBodyCount ); + b3SetBitCountAndClear( &taskContext->awakeIslandBitSet, awakeIslandCount ); + taskContext->splitIslandId = B3_NULL_INDEX; + taskContext->splitSleepTime = 0.0f; + } + + // Finalize bodies. Must happen after the constraint solver and after island splitting. + b3ParallelFor( world, &b3FinalizeBodiesTask, awakeBodyCount, 16, stepContext, "ccd" ); + + // Free in reverse order + b3StackFree( &world->stack, graphBlocks ); + b3StackFree( &world->stack, jointBlocks ); + b3StackFree( &world->stack, meshBlocks ); + b3StackFree( &world->stack, convexBlocks ); + b3StackFree( &world->stack, bodyBlocks ); + b3StackFree( &world->stack, stages ); + b3StackFree( &world->stack, overflow->manifoldConstraints ); + b3StackFree( &world->stack, overflow->contactConstraints ); + b3StackFree( &world->stack, manifoldConstraints ); + b3StackFree( &world->stack, contactConstraints ); + b3StackFree( &world->stack, wideConstraints ); + + world->profile.transforms = b3GetMilliseconds( transformTicks ); + b3TracyCZoneEnd( update_transforms ); + } + + // Report joint events + { + b3TracyCZoneNC( joint_events, "Joint Events", b3_colorPeru, true ); + uint64_t jointEventTicks = b3GetTicks(); + + // Gather bits for all joints that have force/torque events + b3BitSet* jointStateBitSet = &world->taskContexts.data[0].jointStateBitSet; + for ( int i = 1; i < world->workerCount; ++i ) + { + b3InPlaceUnion( jointStateBitSet, &world->taskContexts.data[i].jointStateBitSet ); + } + + { + uint32_t wordCount = jointStateBitSet->blockCount; + uint64_t* bits = jointStateBitSet->bits; + + b3Joint* jointArray = world->joints.data; + uint16_t worldIndex0 = world->worldId; + + for ( uint32_t k = 0; k < wordCount; ++k ) + { + uint64_t word = bits[k]; + while ( word != 0 ) + { + uint32_t ctz = b3CTZ64( word ); + int jointId = (int)( 64 * k + ctz ); + + B3_ASSERT( jointId < world->joints.capacity ); + + b3Joint* joint = jointArray + jointId; + + B3_ASSERT( joint->setIndex == b3_awakeSet ); + + b3JointEvent event = { + .jointId = + { + .index1 = jointId + 1, + .world0 = worldIndex0, + .generation = joint->generation, + }, + .userData = joint->userData, + }; + + b3Array_Push( world->jointEvents, event ); + + // Clear the smallest set bit + word = word & ( word - 1 ); + } + } + } + + world->profile.jointEvents = b3GetMilliseconds( jointEventTicks ); + b3TracyCZoneEnd( joint_events ); + } + + // Report hit events + { + b3TracyCZoneNC( hit_events, "Hit Events", b3_colorRosyBrown, true ); + uint64_t hitTicks = b3GetTicks(); + + B3_ASSERT( world->contactHitEvents.count == 0 ); + + // Fast path: if no worker flagged any hit-event candidates during b2StoreImpulsesTask, skip entirely. + bool anyHitEvents = false; + for ( int i = 0; i < world->workerCount; ++i ) + { + if ( world->taskContexts.data[i].hasHitEvents ) + { + anyHitEvents = true; + break; + } + } + + if ( anyHitEvents ) + { + // Union per-worker bits into worker 0's bit set. + b3BitSet* hitEventBitSet = &world->taskContexts.data[0].hitEventBitSet; + for ( int i = 1; i < world->workerCount; ++i ) + { + if ( world->taskContexts.data[i].hasHitEvents ) + { + b3InPlaceUnion( hitEventBitSet, &world->taskContexts.data[i].hitEventBitSet ); + } + } + + float threshold = world->hitEventThreshold; + b3Contact* contactArray = world->contacts.data; + uint16_t worldId = world->worldId; + + uint32_t wordCount = hitEventBitSet->blockCount; + uint64_t* bits = hitEventBitSet->bits; + for ( uint32_t k = 0; k < wordCount; ++k ) + { + uint64_t word = bits[k]; + while ( word != 0 ) + { + uint32_t ctz = b3CTZ64( word ); + int contactId = (int)( 64 * k + ctz ); + + b3Contact* contact = contactArray + contactId; + B3_ASSERT( contact->setIndex == b3_awakeSet && contact->colorIndex != B3_NULL_INDEX ); + + b3Shape* shapeA = b3Array_Get( world->shapes, contact->shapeIdA ); + b3Shape* shapeB = b3Array_Get( world->shapes, contact->shapeIdB ); + b3Body* bodyA = b3Array_Get( world->bodies, shapeA->bodyId ); + b3Body* bodyB = b3Array_Get( world->bodies, shapeB->bodyId ); + b3BodySim* simA = b3GetBodySim( world, bodyA ); + b3BodySim* simB = b3GetBodySim( world, bodyB ); + b3Pos midCenter = b3LerpPosition( simA->center, simB->center, 0.5f ); + + b3ContactHitEvent event = { 0 }; + event.approachSpeed = threshold; + + bool found = false; + int triangleIndex = 0; + int manifoldCount = contact->manifoldCount; + for ( int i = 0; i < manifoldCount; ++i ) + { + b3Manifold* manifold = contact->manifolds + i; + int pointCount = manifold->pointCount; + for ( int p = 0; p < pointCount; ++p ) + { + b3ManifoldPoint* mp = manifold->points + p; + float approachSpeed = -mp->normalVelocity; + + // Need to check total impulse because the point may be speculative and not colliding + if ( approachSpeed > event.approachSpeed && mp->totalNormalImpulse > 0.0f ) + { + event.approachSpeed = approachSpeed; + event.point = b3OffsetPos( midCenter, b3Lerp( mp->anchorA, mp->anchorB, 0.5f ) ); + event.normal = manifold->normal; + triangleIndex = mp->triangleIndex; + found = true; + } + } + } + + if ( found == true ) + { + event.shapeIdA = (b3ShapeId){ shapeA->id + 1, worldId, shapeA->generation }; + event.shapeIdB = (b3ShapeId){ shapeB->id + 1, worldId, shapeB->generation }; + + event.contactId = (b3ContactId){ + .index1 = contact->contactId + 1, + .world0 = worldId, + .padding = 0, + .generation = contact->generation, + }; + + // shapeB is never a compound today (asserted in b3CreateContact), so the + // childIndex argument is irrelevant for it. shapeA carries the compound. + event.userMaterialIdA = b3GetShapeUserMaterialId( shapeA, contact->childIndex, triangleIndex ); + event.userMaterialIdB = b3GetShapeUserMaterialId( shapeB, 0, triangleIndex ); + + b3Array_Push( world->contactHitEvents, event ); + } + + // Clear the smallest set bit + word = word & ( word - 1 ); + } + } + } + + world->profile.hitEvents = b3GetMilliseconds( hitTicks ); + b3TracyCZoneEnd( hit_events ); + } + + { + b3TracyCZoneNC( refit_bvh, "Refit BVH", b3_colorFireBrick, true ); + uint64_t refitTicks = b3GetTicks(); + + // Finish the user tree task that was queued earlier in the time step. This must be complete before touching the + // broad-phase. + if ( world->userTreeTask != NULL ) + { + world->finishTaskFcn( world->userTreeTask, world->userTaskContext ); + world->userTreeTask = NULL; + world->activeTaskCount -= 1; + } + + b3ValidateNoEnlarged( &world->broadPhase ); + + // Gather bits for all sim bodies that have enlarged AABBs + b3BitSet* enlargedBodyBitSet = &world->taskContexts.data[0].enlargedSimBitSet; + for ( int i = 1; i < world->workerCount; ++i ) + { + b3InPlaceUnion( enlargedBodyBitSet, &world->taskContexts.data[i].enlargedSimBitSet ); + } + + // Enlarge broad-phase proxies and build move array + // Apply shape AABB changes to broad-phase. This also create the move array which must be + // in deterministic order. I'm tracking sim bodies because the number of shape ids can be huge. + // This has to happen before bullets are processed. + { + b3BroadPhase* broadPhase = &world->broadPhase; + uint32_t wordCount = enlargedBodyBitSet->blockCount; + uint64_t* bits = enlargedBodyBitSet->bits; + + // Fast array access is important here + b3Body* bodyArray = world->bodies.data; + b3BodySim* bodySimArray = awakeSet->bodySims.data; + b3Shape* shapeArray = world->shapes.data; + + for ( uint32_t k = 0; k < wordCount; ++k ) + { + uint64_t word = bits[k]; + while ( word != 0 ) + { + uint32_t ctz = b3CTZ64( word ); + uint32_t bodySimIndex = 64 * k + ctz; + + b3BodySim* bodySim = bodySimArray + bodySimIndex; + + b3Body* body = bodyArray + bodySim->bodyId; + + int shapeId = body->headShapeId; + if ( ( bodySim->flags & ( b3_isBullet | b3_isFast ) ) == ( b3_isBullet | b3_isFast ) ) + { + // Fast bullet bodies don't have their final AABB yet + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = shapeArray + shapeId; + + // Shape is fast. It's aabb will be enlarged in continuous collision. + // Update the move array here for determinism because bullets are processed + // below in non-deterministic order. + b3BufferMove( broadPhase, shape->proxyKey ); + + shapeId = shape->nextShapeId; + } + } + else + { + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = shapeArray + shapeId; + + // The AABB may not have been enlarged, despite the body being flagged as enlarged. + // For example, a body with multiple shapes may have not have all shapes enlarged. + // A fast body may have been flagged as enlarged despite having no shapes enlarged. + if ( shape->flags & b3_enlargedAABB ) + { + b3BroadPhase_EnlargeProxy( broadPhase, shape->proxyKey, shape->fatAABB ); + shape->flags &= ~b3_enlargedAABB; + } + + shapeId = shape->nextShapeId; + } + } + + // Clear the smallest set bit + word = word & ( word - 1 ); + } + } + } + + b3ValidateBroadPhase( &world->broadPhase ); + + world->profile.refit = b3GetMilliseconds( refitTicks ); + b3TracyCZoneEnd( refit_bvh ); + } + + int bulletBodyCount = b3AtomicLoadInt( &stepContext->bulletBodyCount ); + if ( bulletBodyCount > 0 ) + { + b3TracyCZoneNC( bullets, "Bullets", b3_colorDarkGoldenRod, true ); + uint64_t bulletTicks = b3GetTicks(); + + // Fast bullet bodies + // Note: a bullet body may be moving slow + int minRange = 8; + b3ParallelFor( world, &b3BulletBodyTask, bulletBodyCount, minRange, stepContext, "bullets" ); + + // Serially enlarge broad-phase proxies for bullet shapes + b3BroadPhase* broadPhase = &world->broadPhase; + b3DynamicTree* dynamicTree = broadPhase->trees + b3_dynamicBody; + + // Fast array access is important here + b3Body* bodyArray = world->bodies.data; + b3BodySim* bodySimArray = awakeSet->bodySims.data; + b3Shape* shapeArray = world->shapes.data; + + // Serially enlarge broad-phase proxies for bullet shapes + int* bulletBodySimIndices = stepContext->bulletBodies; + + // This loop has non-deterministic order but it shouldn't affect the result + for ( int i = 0; i < bulletBodyCount; ++i ) + { + b3BodySim* bulletBodySim = bodySimArray + bulletBodySimIndices[i]; + if ( ( bulletBodySim->flags & b3_enlargeBounds ) == 0 ) + { + continue; + } + + // Clear flag + bulletBodySim->flags &= ~b3_enlargeBounds; + + int bodyId = bulletBodySim->bodyId; + B3_ASSERT( 0 <= bodyId && bodyId < world->bodies.count ); + b3Body* bulletBody = bodyArray + bodyId; + + int shapeId = bulletBody->headShapeId; + while ( shapeId != B3_NULL_INDEX ) + { + b3Shape* shape = shapeArray + shapeId; + if ( ( shape->flags & b3_enlargedAABB ) == 0 ) + { + shapeId = shape->nextShapeId; + continue; + } + + // clear flag + shape->flags &= ~b3_enlargedAABB; + + int proxyKey = shape->proxyKey; + int proxyId = B3_PROXY_ID( proxyKey ); + B3_ASSERT( B3_PROXY_TYPE( proxyKey ) == b3_dynamicBody ); + + // all fast bullet shapes should already be in the move buffer + B3_ASSERT( b3GetBit( &broadPhase->movedProxies[b3_dynamicBody], proxyId ) ); + + b3DynamicTree_EnlargeProxy( dynamicTree, proxyId, shape->fatAABB ); + + shapeId = shape->nextShapeId; + } + } + + world->profile.bullets = b3GetMilliseconds( bulletTicks ); + b3TracyCZoneEnd( bullets ); + } + + b3StackFree( &world->stack, stepContext->bulletBodies ); + stepContext->bulletBodies = NULL; + b3AtomicStoreInt( &stepContext->bulletBodyCount, 0 ); + + // Report sensor hits. This may include bullets sensor hits. + { + b3TracyCZoneNC( sensor_hits, "Sensor Hits", b3_colorPowderBlue, true ); + uint64_t sensorHitTicks = b3GetTicks(); + + int workerCount = world->workerCount; + B3_ASSERT( workerCount == world->taskContexts.count ); + + for ( int i = 0; i < workerCount; ++i ) + { + b3TaskContext* taskContext = world->taskContexts.data + i; + int hitCount = taskContext->sensorHits.count; + b3SensorHit* hits = taskContext->sensorHits.data; + + for ( int j = 0; j < hitCount; ++j ) + { + b3SensorHit hit = hits[j]; + b3Shape* sensorShape = b3Array_Get( world->shapes, hit.sensorId ); + b3Shape* visitor = b3Array_Get( world->shapes, hit.visitorId ); + + b3Sensor* sensor = b3Array_Get( world->sensors, sensorShape->sensorIndex ); + b3Visitor shapeRef = { + .shapeId = hit.visitorId, + .generation = visitor->generation, + }; + b3Array_Push( sensor->hits, shapeRef ); + } + } + + world->profile.sensorHits = b3GetMilliseconds( sensorHitTicks ); + b3TracyCZoneEnd( sensor_hits ); + } + + // Island sleeping + // This must be done last because putting islands to sleep invalidates the enlarged body bits. + // todo_erin figure out how to do this in parallel with tree refit + if ( world->enableSleep == true ) + { + b3TracyCZoneNC( sleep_islands, "Island Sleep", b3_colorLightSlateGray, true ); + uint64_t sleepTicks = b3GetTicks(); + + // Collect split island candidate for the next time step. No need to split if sleeping is disabled. + B3_ASSERT( world->splitIslandId == B3_NULL_INDEX ); + float splitSleepTimer = 0.0f; + for ( int i = 0; i < world->workerCount; ++i ) + { + b3TaskContext* taskContext = world->taskContexts.data + i; + if ( taskContext->splitIslandId != B3_NULL_INDEX && taskContext->splitSleepTime >= splitSleepTimer ) + { + B3_ASSERT( taskContext->splitSleepTime > 0.0f ); + + // Tie breaking for determinism. Largest island id wins. Needed due to work stealing. + if ( taskContext->splitSleepTime == splitSleepTimer && taskContext->splitIslandId < world->splitIslandId ) + { + continue; + } + + world->splitIslandId = taskContext->splitIslandId; + splitSleepTimer = taskContext->splitSleepTime; + } + } + + b3BitSet* awakeIslandBitSet = &world->taskContexts.data[0].awakeIslandBitSet; + for ( int i = 1; i < world->workerCount; ++i ) + { + b3InPlaceUnion( awakeIslandBitSet, &world->taskContexts.data[i].awakeIslandBitSet ); + } + + // Need to process in reverse because this moves islands to sleeping solver sets. + b3IslandSim* islands = awakeSet->islandSims.data; + int count = awakeSet->islandSims.count; + for ( int islandIndex = count - 1; islandIndex >= 0; islandIndex -= 1 ) + { + if ( b3GetBit( awakeIslandBitSet, islandIndex ) == true ) + { + // this island is still awake + continue; + } + + b3IslandSim* island = islands + islandIndex; + int islandId = island->islandId; + + b3TrySleepIsland( world, islandId ); + } + + b3ValidateSolverSets( world ); + + world->profile.sleepIslands = b3GetMilliseconds( sleepTicks ); + b3TracyCZoneEnd( sleep_islands ); + } +} diff --git a/vendor/box3d/src/src/solver.h b/vendor/box3d/src/src/solver.h new file mode 100644 index 000000000..7c8a1c71b --- /dev/null +++ b/vendor/box3d/src/src/solver.h @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +// Solver work is partitioned into fixed-size blocks that worker threads claim +// in parallel via atomic CAS on a per-block syncIndex. The descriptor (b3SolverBlock) +// and the atomic counter sit in a wrapping b3SyncBlock so the CAS-winner can +// pass the descriptor by value into stage tasks without aliasing the atomic +// memory other threads are CAS-writing. Three properties of this design +// matter for performance: +// +// 1. Distributed contention. Per-block atomic syncIndex avoids the cache line stampede +// that a single shared fetch_add counter would cause. Once a worker +// settles into a block range, its CAS targets live in its own L1. +// +// 2. Monotonic syncIndex across iterations. Iterative stages (warm start, +// solve, relax) reuse the same block array every sub-step iteration. +// syncIndex grows each iteration; workers CAS (prev, prev+1), so the +// main thread never touches any per-block state between iterations. +// Non-iterative stages simply use syncIndex 1. +// +// 3. L2 affinity across iterations. Each worker picks a start offset from +// its workerIndex, then scans forward and (after wrap) backward: +// +// blocks: [0] [1] [2] [3] [4] [5] [6] [7] +// ^ ^ ^ ^ +// W0 W1 W2 W3 <- start offsets +// +// W0 claims 0,1,2,3 (forward), W1 claims 4,5, etc. Under balanced load +// each worker re-hits the same block range every iteration, keeping that +// range's hot data resident in its L2. A failed CAS means a neighbour +// already claimed the block, so the stealing worker stops -- preserving +// locality under mild imbalance while still draining the queue. +// +// A graph color stage lays out joint blocks first, then contact blocks: +// +// stage->blocks -> +// +------+------+------+------+------+------+------+ +// | J0 | J1 | J2 | C0 | C1 | C2 | C3 | +// +------+------+------+------+------+------+------+ +// <-- graphJointBlocks --><---- graphContactBlocks ----> +// +// Each block carries its type so the dispatcher routes J-blocks to the joint +// solver and C-blocks to the SIMD contact solver; both kinds run concurrently +// within the stage -- no barrier between them. The type tag lives on the +// block (not the stage) so that mixed-type stages can keep the concurrency. +// +// The solver threading model is inspired by https://github.com/bepu/bepuphysics2 + +#pragma once + +#include "container.h" +#include "core.h" + +#include "box3d/math_functions.h" + +#include + +typedef struct b3BodySim b3BodySim; +typedef struct b3BodyState b3BodyState; +typedef struct b3ContactConstraint b3ContactConstraint; +typedef struct b3ContactConstraintWide b3ContactConstraintWide; +typedef struct b3ContactSpec b3ContactSpec; +typedef struct b3JointSim b3JointSim; +typedef struct b3Manifold b3Manifold; +typedef struct b3ManifoldConstraint b3ManifoldConstraint; +typedef struct b3World b3World; + +// Solver stages +typedef enum b3SolverStageType +{ + b3_stagePrepareJoints, + b3_stagePrepareWideContacts, + b3_stagePrepareContacts, + b3_stageIntegrateVelocities, + b3_stageWarmStart, + b3_stageSolve, + b3_stageIntegratePositions, + b3_stageRelax, + b3_stageRestitution, + b3_stageStoreWideImpulses, + b3_stageStoreImpulses, +} b3SolverStageType; + +typedef enum b3SolverBlockType +{ + // Block for iterating across bodies. + b3_bodyBlock, + + // Block for iterating across joints. For prepare. + b3_jointBlock, + + // Block for iterating across wide contacts. For prepare and store. + b3_wideContactBlock, + + // Block for iterating across contacts. For prepare and store. + b3_contactBlock, + + // Block for iterating across joints of a single graph color. + b3_graphJointBlock, + + // Block for iterating across wide contacts of a single graph color. + b3_graphWideContactBlock, + + // Block for iterating across contacts of a single graph color. + b3_graphContactBlock, + + // Block for processing overflow constraints + b3_overflowBlock, +} b3SolverBlockType; + +// Solver block describes a multithreaded unit of work. +typedef struct b3SolverBlock +{ + int startIndex; + uint16_t count; + // b3SolverBlockType + uint8_t blockType; + uint8_t colorIndex; +} b3SolverBlock; + +// A unit of multithreaded work along with atomic synchronization. The syncIndex grows +// monotonically allowing the solver block to be re-used across sub-steps. +typedef struct b3SyncBlock +{ + b3SolverBlock block; + b3AtomicInt syncIndex; +} b3SyncBlock; + +// Each stage must be completed before going to the next stage. +// Non-iterative stages use a stage instance once while iterative stages re-use the same instance each iteration. +typedef struct b3SolverStage +{ + b3SyncBlock* blocks; + b3SolverStageType type; + int blockCount; + uint8_t colorIndex; + b3AtomicInt completionCount; +} b3SolverStage; + +// Constraint softness +typedef struct b3Softness +{ + float biasRate; + float massScale; + float impulseScale; +} b3Softness; + +// Prepare/store run as a flat parallel-for over the whole wide-constraint +// range. Each span maps a slice of that range back to the owning color's +// contacts so workers can decode flat wide-slot indices without touching +// graph state. The spans array has one entry per active color plus a sentinel +// whose start == wideContactCount. +typedef struct b3WidePrepareSpan +{ + int start; + int count; + int* contacts; +} b3WidePrepareSpan; + +typedef struct b3ContactPrepareSpan +{ + int start; + int count; + b3ContactSpec* contacts; +} b3ContactPrepareSpan; + +typedef struct b3JointPrepareSpan +{ + int start; + int count; + b3JointSim* joints; +} b3JointPrepareSpan; + +// Context for a time step. Recreated each time step. +typedef struct b3StepContext +{ + // time step + float dt; + + // inverse time step (0 if dt == 0). + float inv_dt; + + // sub-step + float h; + float inv_h; + + int subStepCount; + + b3Softness contactSoftness; + b3Softness staticSoftness; + + float restitutionThreshold; + float maxLinearVelocity; + + struct b3World* world; + struct b3ConstraintGraph* graph; + + // shortcut to body states from awake set + b3BodyState* states; + + // shortcut to body sims from awake set + b3BodySim* sims; + + // array of all shape ids for shapes that have enlarged AABBs + int* enlargedShapes; + int enlargedShapeCount; + + // Array of bullet bodies that need continuous collision handling + int* bulletBodies; + b3AtomicInt bulletBodyCount; + + // Contact ids for simplified parallel-for access. Used in narrow-phase. + // These contacts may or may not be touching. They are associated with awake bodies. + int* awakeContactIndices; + + // Flat view of the wide contact constraint array used by prepare and store. + // prepareSpans has activeColorCount + 1 entries, the last being a sentinel + // at wideContactCount. wideContactConstraints is the contiguous base + // pointer; per-color slices live at colors[i].wideConstraints. + struct b3ContactConstraintWide* wideConstraints; + b3WidePrepareSpan* widePrepareSpans; + int wideContactCount; + + // Similar for mesh/overflow contact constraints + struct b3ManifoldConstraint* manifoldConstraints; + struct b3ContactConstraint* contactConstraints; + b3ContactPrepareSpan* contactPrepareSpans; + b3ContactPrepareSpan* overflowSpans; + b3JointPrepareSpan* jointPrepareSpans; + + int activeColorCount; + int workerCount; + + b3SolverStage* stages; + int stageCount; + bool enableWarmStarting; + + // padding to prevent false sharing + char padding1[64]; + + // This atomic is central to multi-threaded solver task synchronization. + // It prevents ABA problems by monotonically growing as the solver advances. + // This means a delayed worker thread will catch up without repeating already completed + // work (causing a race condition). + // sync index (16-bits) | stage type (16-bits) + b3AtomicU32 atomicSyncBits; + + // padding to prevent false sharing + char padding2[64]; + + // Race flag claimed by whichever runner reaches b3SolverTask with workerIndex 0 first. + // The calling thread of b3World_Step also races for this slot so the orchestrator can + // always make progress, regardless of how the user's task system schedules tasks (out + // of order, fewer threads than workers, or synchronously inside enqueueTaskFcn). The + // loser of the race no-ops as workerIndex 0. + b3AtomicInt mainClaimed; + + // padding to prevent false sharing + char padding3[64]; +} b3StepContext; + +void b3Solve( b3World* world, b3StepContext* stepContext ); + +static inline b3Softness b3MakeSoft( float hertz, float zeta, float h ) +{ + if ( hertz == 0.0f ) + { + return B3_LITERAL( b3Softness ){ + .biasRate = 0.0f, + .massScale = 0.0f, + .impulseScale = 0.0f, + }; + } + + float omega = 2.0f * B3_PI * hertz; + float a1 = 2.0f * zeta + h * omega; + float a2 = h * omega * a1; + float a3 = 1.0f / ( 1.0f + a2 ); + + // bias = w / (2 * z + hw) + // massScale = hw * (2 * z + hw) / (1 + hw * (2 * z + hw)) + // impulseScale = 1 / (1 + hw * (2 * z + hw)) + + // If z == 0 + // bias = 1/h + // massScale = hw^2 / (1 + hw^2) + // impulseScale = 1 / (1 + hw^2) + + // w -> inf + // bias = 1/h + // massScale = 1 + // impulseScale = 0 + + // if w = pi / 4 * inv_h + // massScale = (pi/4)^2 / (1 + (pi/4)^2) = pi^2 / (16 + pi^2) ~= 0.38 + // impulseScale = 1 / (1 + (pi/4)^2) = 16 / (16 + pi^2) ~= 0.62 + + // In all cases: + // massScale + impulseScale == 1 + + return ( b3Softness ){ + .biasRate = omega / a1, + .massScale = a2 * a3, + .impulseScale = a3, + }; +} diff --git a/vendor/box3d/src/src/solver_set.c b/vendor/box3d/src/src/solver_set.c new file mode 100644 index 000000000..f050a869d --- /dev/null +++ b/vendor/box3d/src/src/solver_set.c @@ -0,0 +1,633 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "solver_set.h" + +#include "body.h" +#include "constraint_graph.h" +#include "contact.h" +#include "island.h" +#include "joint.h" +#include "physics_world.h" + +#include + +void b3DestroySolverSet( b3World* world, int setIndex ) +{ + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + + b3Array_Destroy( set->bodySims ); + b3Array_Destroy( set->bodyStates ); + b3Array_Destroy( set->contactIndices ); + b3Array_Destroy( set->jointSims ); + b3Array_Destroy( set->islandSims ); + + b3FreeId( &world->solverSetIdPool, setIndex ); + *set = (b3SolverSet){ 0 }; + set->setIndex = B3_NULL_INDEX; +} + +// Wake a solver set. Does not merge islands. +// Contacts can be in several places: +// 1. non-touching contacts in the disabled set +// 2. non-touching contacts already in the awake set +// 3. touching contacts in the sleeping set +// This handles contact types 1 and 3. Type 2 doesn't need any action. +void b3WakeSolverSet( b3World* world, int setIndex ) +{ + B3_ASSERT( setIndex >= b3_firstSleepingSet ); + b3SolverSet* set = b3Array_Get( world->solverSets, setIndex ); + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet ); + + b3Body* bodies = world->bodies.data; + + int bodyCount = set->bodySims.count; + for ( int i = 0; i < bodyCount; ++i ) + { + b3BodySim* simSrc = set->bodySims.data + i; + + b3Body* body = bodies + simSrc->bodyId; + B3_ASSERT( body->setIndex == setIndex ); + body->setIndex = b3_awakeSet; + body->localIndex = awakeSet->bodySims.count; + + // Reset sleep timer + body->sleepTime = 0.0f; + + b3BodySim* simDst = b3Array_Emplace( awakeSet->bodySims ); + memcpy( simDst, simSrc, sizeof( b3BodySim ) ); + + b3BodyState* state = b3Array_Emplace( awakeSet->bodyStates ); + *state = b3_identityBodyState; + state->flags = body->flags; + + // move non-touching contacts from disabled set to awake set + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int edgeIndex = contactKey & 1; + int contactId = contactKey >> 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + contactKey = contact->edges[edgeIndex].nextKey; + + if ( contact->setIndex != b3_disabledSet ) + { + B3_ASSERT( contact->setIndex == b3_awakeSet || contact->setIndex == setIndex ); + continue; + } + + int localIndex = contact->localIndex; + + B3_ASSERT( 0 <= localIndex && localIndex < disabledSet->contactIndices.count ); + B3_ASSERT( disabledSet->contactIndices.data[localIndex] == contactId ); + + B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) == 0 && contact->manifoldCount == 0 ); + + contact->setIndex = b3_awakeSet; + contact->localIndex = awakeSet->contactIndices.count; + b3Array_Push( awakeSet->contactIndices, contactId ); + + int movedLocalIndex = b3Array_RemoveSwap( disabledSet->contactIndices, localIndex ); + if ( movedLocalIndex != B3_NULL_INDEX ) + { + // fix moved element + int movedContactIndex = disabledSet->contactIndices.data[localIndex]; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex ); + B3_ASSERT( movedContact->localIndex == movedLocalIndex ); + movedContact->localIndex = localIndex; + } + } + } + + // Transfer touching contacts from sleeping set to constraint graph. + { + int contactCount = set->contactIndices.count; + for ( int i = 0; i < contactCount; ++i ) + { + int contactIndex = set->contactIndices.data[i]; + b3Contact* contact = b3Array_Get( world->contacts, contactIndex ); + B3_ASSERT( contact->flags & b3_contactTouchingFlag ); + B3_ASSERT( contact->flags & b3_simTouchingFlag ); + B3_ASSERT( contact->setIndex == setIndex ); + b3AddContactToGraph( world, contact ); + contact->setIndex = b3_awakeSet; + } + } + + // transfer joints from sleeping set to awake set + { + int jointCount = set->jointSims.count; + for ( int i = 0; i < jointCount; ++i ) + { + b3JointSim* jointSim = set->jointSims.data + i; + b3Joint* joint = b3Array_Get( world->joints, jointSim->jointId ); + B3_ASSERT( joint->setIndex == setIndex ); + b3AddJointToGraph( world, jointSim, joint ); + joint->setIndex = b3_awakeSet; + } + } + + // transfer island from sleeping set to awake set + // Usually a sleeping set has only one island, but it is possible + // that joints are created between sleeping islands and they + // are moved to the same sleeping set. + { + int islandCount = set->islandSims.count; + for ( int i = 0; i < islandCount; ++i ) + { + b3IslandSim* islandSrc = set->islandSims.data + i; + b3Island* island = b3Array_Get( world->islands, islandSrc->islandId ); + island->setIndex = b3_awakeSet; + island->localIndex = awakeSet->islandSims.count; + b3IslandSim* islandDst = b3Array_Emplace( awakeSet->islandSims ); + memcpy( islandDst, islandSrc, sizeof( b3IslandSim ) ); + } + } + + // destroy the sleeping set + b3DestroySolverSet( world, setIndex ); +} + +void b3TrySleepIsland( b3World* world, int islandId ) +{ + b3Island* island = b3Array_Get( world->islands, islandId ); + B3_ASSERT( island->setIndex == b3_awakeSet ); + + // Cannot put an island to sleep while it has a pending split and more than one body. + if ( island->constraintRemoveCount > 0 && island->bodies.count > 1 ) + { + return; + } + + // island is sleeping + // - create new sleeping solver set + // - move island to sleeping solver set + // - identify non-touching contacts that should move to sleeping solver set or disabled set + // - remove old island + // - fix island + int sleepSetId = b3AllocId( &world->solverSetIdPool ); + if ( sleepSetId == world->solverSets.count ) + { + b3SolverSet set = { 0 }; + set.setIndex = B3_NULL_INDEX; + b3Array_Push( world->solverSets, set ); + } + + b3SolverSet* sleepSet = b3Array_Get( world->solverSets, sleepSetId ); + *sleepSet = (b3SolverSet){ 0 }; + + // grab awake set after creating the sleep set because the solver set array may have been resized + b3SolverSet* awakeSet = b3Array_Get( world->solverSets, b3_awakeSet ); + b3SolverSet* disabledSet = b3Array_Get( world->solverSets, b3_disabledSet ); + B3_ASSERT( 0 <= island->localIndex && island->localIndex < awakeSet->islandSims.count ); + + sleepSet->setIndex = sleepSetId; + b3Array_Reserve( sleepSet->bodySims, island->bodies.count ); + b3Array_Reserve( sleepSet->contactIndices, island->contacts.count ); + b3Array_Reserve( sleepSet->jointSims, island->joints.count ); + + // move awake bodies to sleeping set + // this shuffles around bodies in the awake set + { + for ( int i = 0; i < island->bodies.count; ++i ) + { + int bodyId = island->bodies.data[i]; + b3Body* body = b3Array_Get( world->bodies, bodyId ); + B3_ASSERT( body->setIndex == b3_awakeSet ); + B3_ASSERT( body->islandId == islandId ); + B3_ASSERT( body->islandIndex == i ); + + // Update the body move event to indicate this body fell asleep + // It could happen the body is forced asleep before it ever moves. + if ( body->bodyMoveIndex != B3_NULL_INDEX ) + { + b3BodyMoveEvent* moveEvent = b3Array_Get( world->bodyMoveEvents, body->bodyMoveIndex ); + B3_ASSERT( moveEvent->bodyId.index1 - 1 == bodyId ); + B3_ASSERT( moveEvent->bodyId.generation == body->generation ); + moveEvent->fellAsleep = true; + body->bodyMoveIndex = B3_NULL_INDEX; + } + + int awakeBodyIndex = body->localIndex; + b3BodySim* awakeSim = b3Array_Get( awakeSet->bodySims, awakeBodyIndex ); + + // move body sim to sleep set + int sleepBodyIndex = sleepSet->bodySims.count; + b3BodySim* sleepBodySim = b3Array_Emplace( sleepSet->bodySims ); + memcpy( sleepBodySim, awakeSim, sizeof( b3BodySim ) ); + + int movedIndex = b3Array_RemoveSwap( awakeSet->bodySims, awakeBodyIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // fix local index on moved element + b3BodySim* movedSim = awakeSet->bodySims.data + awakeBodyIndex; + int movedId = movedSim->bodyId; + b3Body* movedBody = b3Array_Get( world->bodies, movedId ); + B3_ASSERT( movedBody->localIndex == movedIndex ); + movedBody->localIndex = awakeBodyIndex; + } + + // destroy state, no need to clone + b3Array_RemoveSwap( awakeSet->bodyStates, awakeBodyIndex ); + + body->setIndex = sleepSetId; + body->localIndex = sleepBodyIndex; + + // Move non-touching contacts to the disabled set. + // Non-touching contacts may exist between sleeping islands and there is no clear ownership. + int contactKey = body->headContactKey; + while ( contactKey != B3_NULL_INDEX ) + { + int contactId = contactKey >> 1; + int edgeIndex = contactKey & 1; + + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + + B3_ASSERT( contact->setIndex == b3_awakeSet || contact->setIndex == b3_disabledSet ); + contactKey = contact->edges[edgeIndex].nextKey; + + if ( contact->setIndex == b3_disabledSet ) + { + // already moved to disabled set by another body in the island + continue; + } + + if ( contact->colorIndex != B3_NULL_INDEX ) + { + // contact is touching and will be moved separately + B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) != 0 ); + continue; + } + + // the other body may still be awake, it still may go to sleep and then it will be responsible + // for moving this contact to the disabled set. + int otherEdgeIndex = edgeIndex ^ 1; + int otherBodyId = contact->edges[otherEdgeIndex].bodyId; + b3Body* otherBody = b3Array_Get( world->bodies, otherBodyId ); + if ( otherBody->setIndex == b3_awakeSet ) + { + continue; + } + + int localIndex = contact->localIndex; + B3_ASSERT( awakeSet->contactIndices.data[localIndex] == contactId ); + + B3_ASSERT( contact->manifoldCount == 0 ); + B3_ASSERT( ( contact->flags & b3_contactTouchingFlag ) == 0 ); + + // Move the non-touching contact to the disabled set. + contact->setIndex = b3_disabledSet; + + // This is mandatory for validation to work correctly + contact->localIndex = disabledSet->contactIndices.count; + b3Array_Push( disabledSet->contactIndices, contact->contactId ); + + int movedLocalIndex = b3Array_RemoveSwap( awakeSet->contactIndices, localIndex ); + if ( movedLocalIndex != B3_NULL_INDEX ) + { + // fix moved element + int movedContactIndex = awakeSet->contactIndices.data[localIndex]; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactIndex ); + B3_ASSERT( movedContact->localIndex == movedLocalIndex ); + movedContact->localIndex = localIndex; + } + } + } + } + + // move touching contacts to sleeping set + // this shuffles contacts in the awake set + { + for ( int i = 0; i < island->contacts.count; ++i ) + { + int contactId = island->contacts.data[i].contactId; + b3Contact* contact = b3Array_Get( world->contacts, contactId ); + B3_ASSERT( contact->setIndex == b3_awakeSet ); + B3_ASSERT( contact->islandId == islandId ); + B3_ASSERT( contact->islandIndex == i ); + int colorIndex = contact->colorIndex; + B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT ); + + b3GraphColor* color = world->constraintGraph.colors + colorIndex; + + // Remove bodies from graph coloring associated with this constraint + if ( colorIndex != B3_OVERFLOW_INDEX ) + { + // might clear a bit for a static body, but this has no effect + b3ClearBit( &color->bodySet, contact->edges[0].bodyId ); + b3ClearBit( &color->bodySet, contact->edges[1].bodyId ); + } + + int sleepContactIndex = sleepSet->contactIndices.count; + b3Array_Push( sleepSet->contactIndices, contactId ); + + int localIndex = contact->localIndex; + if ( ( contact->flags & b3_simMeshContact ) || colorIndex == B3_OVERFLOW_INDEX ) + { + int movedLocalIndex = b3Array_RemoveSwap( color->contacts, localIndex ); + if ( movedLocalIndex != B3_NULL_INDEX ) + { + // fix moved element + int movedContactId = color->contacts.data[localIndex].contactId; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId ); + B3_ASSERT( movedContact->localIndex == movedLocalIndex ); + movedContact->localIndex = localIndex; + } + } + else + { + int movedLocalIndex = b3Array_RemoveSwap( color->convexContacts, localIndex ); + if ( movedLocalIndex != B3_NULL_INDEX ) + { + // fix moved element + int movedContactId = color->convexContacts.data[localIndex]; + b3Contact* movedContact = b3Array_Get( world->contacts, movedContactId ); + B3_ASSERT( movedContact->localIndex == movedLocalIndex ); + movedContact->localIndex = localIndex; + } + } + + contact->setIndex = sleepSetId; + contact->colorIndex = B3_NULL_INDEX; + contact->localIndex = sleepContactIndex; + } + } + + // move joints + // this shuffles joints in the awake set + { + for ( int i = 0; i < island->joints.count; ++i ) + { + int jointId = island->joints.data[i].jointId; + b3Joint* joint = b3Array_Get( world->joints, jointId ); + B3_ASSERT( joint->setIndex == b3_awakeSet ); + B3_ASSERT( joint->islandId == islandId ); + B3_ASSERT( joint->islandIndex == i ); + int colorIndex = joint->colorIndex; + int localIndex = joint->localIndex; + + B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT ); + + b3GraphColor* color = world->constraintGraph.colors + colorIndex; + + b3JointSim* awakeJointSim = b3Array_Get( color->jointSims, localIndex ); + + if ( colorIndex != B3_OVERFLOW_INDEX ) + { + // might clear a bit for a static body, but this has no effect + b3ClearBit( &color->bodySet, joint->edges[0].bodyId ); + b3ClearBit( &color->bodySet, joint->edges[1].bodyId ); + } + + int sleepJointIndex = sleepSet->jointSims.count; + b3JointSim* sleepJointSim = b3Array_Emplace( sleepSet->jointSims ); + memcpy( sleepJointSim, awakeJointSim, sizeof( b3JointSim ) ); + + int movedIndex = b3Array_RemoveSwap( color->jointSims, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // fix moved element + b3JointSim* movedJointSim = color->jointSims.data + localIndex; + int movedId = movedJointSim->jointId; + b3Joint* movedJoint = b3Array_Get( world->joints, movedId ); + B3_ASSERT( movedJoint->localIndex == movedIndex ); + movedJoint->localIndex = localIndex; + } + + joint->setIndex = sleepSetId; + joint->colorIndex = B3_NULL_INDEX; + joint->localIndex = sleepJointIndex; + } + } + + // move island struct + { + B3_ASSERT( island->setIndex == b3_awakeSet ); + + int islandIndex = island->localIndex; + b3IslandSim* sleepIsland = b3Array_Emplace( sleepSet->islandSims ); + sleepIsland->islandId = islandId; + + int movedIslandIndex = b3Array_RemoveSwap( awakeSet->islandSims, islandIndex ); + if ( movedIslandIndex != B3_NULL_INDEX ) + { + // fix index on moved element + b3IslandSim* movedIslandSim = awakeSet->islandSims.data + islandIndex; + int movedIslandId = movedIslandSim->islandId; + b3Island* movedIsland = b3Array_Get( world->islands, movedIslandId ); + B3_ASSERT( movedIsland->localIndex == movedIslandIndex ); + movedIsland->localIndex = islandIndex; + } + + island->setIndex = sleepSetId; + island->localIndex = 0; + } + + if ( world->splitIslandId == islandId ) + { + world->splitIslandId = B3_NULL_INDEX; + } + + b3ValidateSolverSets( world ); +} + +// This is called when joints are created between sets. I want to allow the sets +// to continue sleeping if both are asleep. Otherwise one set is waked. +// Islands will get merge when the set is woke. +void b3MergeSolverSets( b3World* world, int setId1, int setId2 ) +{ + B3_ASSERT( setId1 >= b3_firstSleepingSet ); + B3_ASSERT( setId2 >= b3_firstSleepingSet ); + b3SolverSet* set1 = b3Array_Get( world->solverSets, setId1 ); + b3SolverSet* set2 = b3Array_Get( world->solverSets, setId2 ); + + // Move the fewest number of bodies + if ( set1->bodySims.count < set2->bodySims.count ) + { + b3SolverSet* tempSet = set1; + set1 = set2; + set2 = tempSet; + + int tempId = setId1; + setId1 = setId2; + setId2 = tempId; + } + + // transfer bodies + { + b3Body* bodies = world->bodies.data; + int bodyCount = set2->bodySims.count; + for ( int i = 0; i < bodyCount; ++i ) + { + b3BodySim* simSrc = set2->bodySims.data + i; + + b3Body* body = bodies + simSrc->bodyId; + B3_ASSERT( body->setIndex == setId2 ); + body->setIndex = setId1; + body->localIndex = set1->bodySims.count; + + b3BodySim* simDst = b3Array_Emplace( set1->bodySims ); + memcpy( simDst, simSrc, sizeof( b3BodySim ) ); + } + } + + // transfer contacts + { + int contactCount = set2->contactIndices.count; + for ( int i = 0; i < contactCount; ++i ) + { + int contactIndex = set2->contactIndices.data[i]; + b3Contact* contact = b3Array_Get( world->contacts, contactIndex ); + B3_ASSERT( contact->setIndex == setId2 ); + contact->setIndex = setId1; + contact->localIndex = set1->contactIndices.count; + b3Array_Push( set1->contactIndices, contactIndex ); + } + } + + // transfer joints + { + int jointCount = set2->jointSims.count; + for ( int i = 0; i < jointCount; ++i ) + { + b3JointSim* jointSrc = set2->jointSims.data + i; + + b3Joint* joint = b3Array_Get( world->joints, jointSrc->jointId ); + B3_ASSERT( joint->setIndex == setId2 ); + joint->setIndex = setId1; + joint->localIndex = set1->jointSims.count; + + b3JointSim* jointDst = b3Array_Emplace( set1->jointSims ); + memcpy( jointDst, jointSrc, sizeof( b3JointSim ) ); + } + } + + // transfer islands + { + int islandCount = set2->islandSims.count; + for ( int i = 0; i < islandCount; ++i ) + { + b3IslandSim* islandSrc = set2->islandSims.data + i; + int islandId = islandSrc->islandId; + + b3Island* island = b3Array_Get( world->islands, islandId ); + island->setIndex = setId1; + island->localIndex = set1->islandSims.count; + + b3IslandSim* islandDst = b3Array_Emplace( set1->islandSims ); + memcpy( islandDst, islandSrc, sizeof( b3IslandSim ) ); + } + } + + // destroy the merged set + // Warning: need to be careful not to destroy things that got transferred, like triangle caches. + b3DestroySolverSet( world, setId2 ); + + b3ValidateSolverSets( world ); +} + +void b3TransferBody( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Body* body ) +{ + if ( targetSet == sourceSet ) + { + return; + } + + int sourceIndex = body->localIndex; + b3BodySim* sourceSim = b3Array_Get( sourceSet->bodySims, sourceIndex ); + + int targetIndex = targetSet->bodySims.count; + b3BodySim* targetSim = b3Array_Emplace( targetSet->bodySims ); + memcpy( targetSim, sourceSim, sizeof( b3BodySim ) ); + + // Clear transient body flags + targetSim->flags &= ~( b3_isFast | b3_isSpeedCapped | b3_hadTimeOfImpact ); + + // Remove body sim from solver set that owns it + int movedIndex = b3Array_RemoveSwap( sourceSet->bodySims, sourceIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // Fix moved body index + b3BodySim* movedSim = sourceSet->bodySims.data + sourceIndex; + int movedId = movedSim->bodyId; + b3Body* movedBody = b3Array_Get( world->bodies, movedId ); + B3_ASSERT( movedBody->localIndex == movedIndex ); + movedBody->localIndex = sourceIndex; + } + + if ( sourceSet->setIndex == b3_awakeSet ) + { + b3Array_RemoveSwap( sourceSet->bodyStates, sourceIndex ); + } + else if ( targetSet->setIndex == b3_awakeSet ) + { + b3BodyState* state = b3Array_Emplace( targetSet->bodyStates ); + *state = b3_identityBodyState; + state->flags = body->flags; + } + + body->setIndex = targetSet->setIndex; + body->localIndex = targetIndex; +} + +void b3TransferJoint( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Joint* joint ) +{ + if ( targetSet == sourceSet ) + { + return; + } + + int localIndex = joint->localIndex; + int colorIndex = joint->colorIndex; + + // Retrieve source. + b3JointSim* sourceSim; + if ( sourceSet->setIndex == b3_awakeSet ) + { + B3_ASSERT( 0 <= colorIndex && colorIndex < B3_GRAPH_COLOR_COUNT ); + b3GraphColor* color = world->constraintGraph.colors + colorIndex; + + sourceSim = b3Array_Get( color->jointSims, localIndex ); + } + else + { + B3_ASSERT( colorIndex == B3_NULL_INDEX ); + sourceSim = b3Array_Get( sourceSet->jointSims, localIndex ); + } + + // Create target and copy. Fix joint. + if ( targetSet->setIndex == b3_awakeSet ) + { + b3AddJointToGraph( world, sourceSim, joint ); + joint->setIndex = b3_awakeSet; + } + else + { + joint->setIndex = targetSet->setIndex; + joint->localIndex = targetSet->jointSims.count; + joint->colorIndex = B3_NULL_INDEX; + + b3JointSim* targetSim = b3Array_Emplace( targetSet->jointSims ); + memcpy( targetSim, sourceSim, sizeof( b3JointSim ) ); + } + + // Destroy source. + if ( sourceSet->setIndex == b3_awakeSet ) + { + b3RemoveJointFromGraph( world, joint->edges[0].bodyId, joint->edges[1].bodyId, colorIndex, localIndex ); + } + else + { + int movedIndex = b3Array_RemoveSwap( sourceSet->jointSims, localIndex ); + if ( movedIndex != B3_NULL_INDEX ) + { + // fix swapped element + b3JointSim* movedJointSim = sourceSet->jointSims.data + localIndex; + int movedId = movedJointSim->jointId; + b3Joint* movedJoint = b3Array_Get( world->joints, movedId ); + movedJoint->localIndex = localIndex; + } + } +} diff --git a/vendor/box3d/src/src/solver_set.h b/vendor/box3d/src/src/solver_set.h new file mode 100644 index 000000000..bf78ab04c --- /dev/null +++ b/vendor/box3d/src/src/solver_set.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "container.h" +#include "contact.h" + +typedef struct b3Body b3Body; +typedef struct b3BodySim b3BodySim; +typedef struct b3BodyState b3BodyState; +typedef struct b3IslandSim b3IslandSim; +typedef struct b3Joint b3Joint; +typedef struct b3JointSim b3JointSim; +typedef struct b3World b3World; + +b3DeclareArray( b3BodySim ); +b3DeclareArray( b3BodyState ); +b3DeclareArray( b3JointSim ); +b3DeclareArray( b3IslandSim ); + +// This holds solver set data. The following sets are used: +// - static set for all static bodies and joints between static bodies +// - active set for all active bodies with body states (no +// contacts or joints) +// - disabled set for disabled bodies and their joints +// - all further sets are sleeping island sets along with their contacts and joints +// The purpose of solver sets is to achieve high memory locality. +// https://www.youtube.com/watch?v=nZNd5FjSquk +typedef struct b3SolverSet +{ + // Body array. Empty for unused set. + b3Array( b3BodySim ) bodySims; + + // Body state only exists for active set + b3Array( b3BodyState ) bodyStates; + + // This holds sleeping/disabled joints. Empty for static/active set. + b3Array( b3JointSim ) jointSims; + + // This holds all contacts for sleeping sets. + // This holds non-touching contacts for the awake set. + // This should be empty for the static and disabled sets. + b3Array( int ) contactIndices; + + // The awake set has an array of islands. Sleeping sets normally have a single islands. However, joints + // created between sleeping sets causes the sets to merge, leaving them with multiple islands. These sleeping + // islands will be naturally merged with the set is woken. + // The static and disabled sets have no islands. + // Islands live in the solver sets to limit the number of islands that need to be considered for sleeping. + b3Array( b3IslandSim ) islandSims; + + // Aligns with b3World::solverSetIdPool. Used to create a stable id for body/contact/joint/islands. + int setIndex; +} b3SolverSet; + +void b3DestroySolverSet( b3World* world, int setIndex ); + +void b3WakeSolverSet( b3World* world, int setIndex ); +void b3TrySleepIsland( b3World* world, int islandId ); + +// Merge set 2 into set 1 then destroy set 2. +// Warning: any pointers into these sets will be orphaned. +void b3MergeSolverSets( b3World* world, int setIndex1, int setIndex2 ); + +void b3TransferBody( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Body* body ); +void b3TransferJoint( b3World* world, b3SolverSet* targetSet, b3SolverSet* sourceSet, b3Joint* joint ); diff --git a/vendor/box3d/src/src/sphere.c b/vendor/box3d/src/src/sphere.c new file mode 100644 index 000000000..9e4bdaac0 --- /dev/null +++ b/vendor/box3d/src/src/sphere.c @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +// Dirk Gregorius contributed portions of this code + +#include "math_internal.h" +#include "shape.h" + +#include "box3d/base.h" +#include "box3d/collision.h" +#include "box3d/constants.h" + +b3MassData b3ComputeSphereMass( const b3Sphere* shape, float density ) +{ + b3Vec3 center = shape->center; + float radius = shape->radius; + + float volume = 4.0f / 3.0f * B3_PI * radius * radius * radius; + float mass = volume * density; + float ixx = 0.4f * mass * radius * radius; + + b3MassData out; + out.mass = mass; + out.center = center; + + // Inertia about the center of mass + out.inertia = b3MakeDiagonalMatrix( ixx, ixx, ixx ); + return out; +} + +b3AABB b3ComputeSphereAABB( const b3Sphere* shape, b3Transform transform ) +{ + b3Vec3 center = b3TransformPoint( transform, shape->center ); + float radius = shape->radius; + b3Vec3 extent = { radius, radius, radius }; + return ( b3AABB ){ b3Sub( center, extent ), b3Add( center, extent ) }; +} + +b3AABB b3ComputeSweptSphereAABB( const b3Sphere* shape, b3Transform xf1, b3Transform xf2 ) +{ + b3Vec3 r = { shape->radius, shape->radius, shape->radius }; + b3Vec3 center1 = b3TransformPoint( xf1, shape->center ); + b3Vec3 center2 = b3TransformPoint( xf2, shape->center ); + return ( b3AABB ){ b3Sub( b3Min( center1, center2 ), r ), b3Add( b3Max( center1, center2 ), r ) }; +} + +bool b3OverlapSphere( const b3Sphere* shape, b3Transform shapeTransform, const b3ShapeProxy* proxy ) +{ + b3DistanceInput input; + input.proxyA = ( b3ShapeProxy ){ &shape->center, 1, shape->radius }; + input.proxyB = *proxy; + input.transform = b3InvMulTransforms( shapeTransform, b3Transform_identity ); + input.useRadii = true; + + b3SimplexCache cache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &cache, NULL, 0 ); + return output.distance < B3_OVERLAP_SLOP; +} + +// Precision Improvements for Ray / Sphere Intersection - Ray Tracing Gems 2019 +// http://www.codercorner.com/blog/?p=321 +b3CastOutput b3RayCastSphere(const b3Sphere* shape, const b3RayCastInput* input ) +{ + B3_ASSERT( b3IsValidRay( input ) ); + b3CastOutput output = { 0 }; + + b3Vec3 p = shape->center; + + // Shift ray so sphere center is the origin + b3Vec3 s = b3Sub( input->origin, p ); + + float r = shape->radius; + float rr = r * r; + + float length; + b3Vec3 d = b3GetLengthAndNormalize( &length, input->translation ); + if ( length == 0.0f ) + { + // zero length ray + + if ( b3LengthSquared( s ) < rr ) + { + // initial overlap + output.point = input->origin; + output.hit = true; + } + + return output; + } + + // Find closest point on ray to origin + + // solve: dot(s + t * d, d) = 0 + float t = -b3Dot( s, d ); + + // c is the closest point on the line to the origin + b3Vec3 c = b3MulAdd( s, t, d ); + + float cc = b3Dot( c, c ); + + if ( cc > rr ) + { + // closest point is outside the sphere + return output; + } + + // Pythagoras + float h = sqrtf( rr - cc ); + + float fraction = t - h; + + if ( fraction < 0.0f || input->maxFraction * length < fraction ) + { + // intersection is point outside the range of the ray segment + + if ( b3LengthSquared( s ) < rr ) + { + // initial overlap + output.point = input->origin; + output.hit = true; + } + + return output; + } + + b3Vec3 hitPoint = b3MulAdd( s, fraction, d ); + + output.fraction = fraction / length; + + if ( output.fraction > input->maxFraction ) + { + b3Log( "sphere input fraction = %g, output fraction = %g", input->maxFraction, output.fraction ); + output.fraction = input->maxFraction; + } + + output.normal = b3Normalize( hitPoint ); + output.point = b3MulAdd( p, shape->radius, output.normal ); + output.hit = true; + + return output; +} + +// Precision Improvements for Ray / Sphere Intersection - Ray Tracing Gems 2019 +// http://www.codercorner.com/blog/?p=321 +// This will do interior hits. +b3CastOutput b3RayCastHollowSphere( const b3Sphere* sphere, const b3RayCastInput* input ) +{ + b3Vec3 p = sphere->center; + + b3CastOutput output = { 0 }; + + // Shift ray so sphere center is the origin + b3Vec3 s = b3Sub( input->origin, p ); + b3Vec3 d = b3Normalize( input->translation ); + + // Find closest point on ray to origin + + // solve: dot(s + t * d, d) = 0 + float t = -b3Dot( s, d ); + + // c is the closest point on the line to the origin + b3Vec3 c = b3MulAdd( s, t, d ); + + float cc = b3Dot( c, c ); + float r = sphere->radius; + float rr = r * r; + + if ( cc > rr ) + { + // closest point is outside the sphere + return output; + } + + // Pythagoras + float h = sqrtf( rr - cc ); + + float fraction = t - h; + + if ( fraction < 0.0f ) + { + fraction = t + h; + } + + if ( fraction < 0.0f ) + { + // behind the ray + return output; + } + + if (fraction > input->maxFraction) + { + return output; + } + + b3Vec3 hitPoint = b3MulAdd( s, fraction, d ); + + output.fraction = fraction; + output.normal = b3Normalize( hitPoint ); + output.point = b3MulAdd( p, sphere->radius, output.normal ); + output.hit = true; + + return output; +} + +b3CastOutput b3ShapeCastSphere( const b3Sphere* sphere, const b3ShapeCastInput* input ) +{ + b3ShapeCastPairInput pairInput; + pairInput.proxyA = ( b3ShapeProxy ){ &sphere->center, 1, sphere->radius }; + pairInput.proxyB = input->proxy; + pairInput.transform = b3Transform_identity; + pairInput.translationB = input->translation; + pairInput.maxFraction = input->maxFraction; + pairInput.canEncroach = input->canEncroach; + + b3CastOutput output = b3ShapeCast( &pairInput ); + return output; +} + +int b3CollideMoverAndSphere( b3PlaneResult* result, const b3Sphere* shape, const b3Capsule* mover ) +{ + float totalRadius = mover->radius + shape->radius; + b3Vec3 closest = b3PointToSegmentDistance( mover->center1, mover->center2, shape->center ); + + // The normal points from the sphere toward the mover. + float distance; + b3Vec3 normal = b3GetLengthAndNormalize( &distance, b3Sub( closest, shape->center ) ); + + if ( distance > totalRadius ) + { + return 0; + } + + float linearSlop = B3_LINEAR_SLOP; + if ( distance < linearSlop ) + { + // Deep overlap: the mover axis passes through the sphere center, so no + // direction is preferred. Push perpendicular to the mover axis. + float length; + b3Vec3 axis = b3GetLengthAndNormalize( &length, b3Sub( mover->center2, mover->center1 ) ); + normal = length > linearSlop ? b3Perp( axis ) : b3Vec3_axisY; + distance = 0.0f; + } + + b3Plane plane = { normal, totalRadius - distance }; + *result = ( b3PlaneResult ){ plane, shape->center }; + return 1; +} diff --git a/vendor/box3d/src/src/spherical_joint.c b/vendor/box3d/src/src/spherical_joint.c new file mode 100644 index 000000000..3f699ac18 --- /dev/null +++ b/vendor/box3d/src/src/spherical_joint.c @@ -0,0 +1,745 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "core.h" +#include "joint.h" +#include "math_internal.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +void b3SphericalJoint_EnableConeLimit( b3JointId jointId, bool enableLimit ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointEnableConeLimit, jointId, enableLimit ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + if ( enableLimit != base->sphericalJoint.enableConeLimit ) + { + base->sphericalJoint.swingImpulse = 0.0f; + } + base->sphericalJoint.enableConeLimit = enableLimit; +} + +bool b3SphericalJoint_IsConeLimitEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.enableConeLimit; +} + +float b3SphericalJoint_GetConeLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.coneAngle; +} + +void b3SphericalJoint_SetConeLimit( b3JointId jointId, float angleRadians ) +{ + B3_ASSERT( b3IsValidFloat( angleRadians ) && 0 <= angleRadians && angleRadians <= 0.5f * B3_PI ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetConeLimit, jointId, angleRadians ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.coneAngle = angleRadians; +} + +float b3SphericalJoint_GetConeAngle( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB ); + + b3Quat quatA = b3MulQuat( transformA.q, base->localFrameA.q ); + b3Quat quatB = b3MulQuat( transformB.q, base->localFrameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the swing angle in the range [0, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + return b3GetSwingAngle( relQ ); +} + +void b3SphericalJoint_EnableTwistLimit( b3JointId jointId, bool enableLimit ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointEnableTwistLimit, jointId, enableLimit ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + if ( enableLimit != base->sphericalJoint.enableTwistLimit ) + { + base->sphericalJoint.lowerTwistImpulse = 0.0f; + base->sphericalJoint.upperTwistImpulse = 0.0f; + } + base->sphericalJoint.enableTwistLimit = enableLimit; +} + +bool b3SphericalJoint_IsTwistLimitEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.enableTwistLimit; +} + +float b3SphericalJoint_GetLowerTwistLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.lowerTwistAngle; +} + +float b3SphericalJoint_GetUpperTwistLimit( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.upperTwistAngle; +} + +void b3SphericalJoint_SetTwistLimits( b3JointId jointId, float lowerLimitRadians, float upperLimitRadians ) +{ + B3_ASSERT( b3IsValidFloat( lowerLimitRadians ) && b3IsValidFloat( upperLimitRadians ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetTwistLimits, jointId, lowerLimitRadians, upperLimitRadians ); + + float lowerAngle = b3MinFloat( lowerLimitRadians, upperLimitRadians ); + float upperAngle = b3MaxFloat( lowerLimitRadians, upperLimitRadians ); + + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.lowerTwistAngle = b3ClampFloat( lowerAngle, -0.99f * B3_PI, 0.99f * B3_PI ); + base->sphericalJoint.upperTwistAngle = b3ClampFloat( upperAngle, -0.99f * B3_PI, 0.99f * B3_PI ); +} + +float b3SphericalJoint_GetTwistAngle( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform transformB = b3GetBodyTransform( world, base->bodyIdB ); + + b3Quat quatA = b3MulQuat( transformA.q, base->localFrameA.q ); + b3Quat quatB = b3MulQuat( transformB.q, base->localFrameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the twist angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + return b3GetTwistAngle( relQ ); +} + +void b3SphericalJoint_EnableSpring( b3JointId jointId, bool enableSpring ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointEnableSpring, jointId, enableSpring ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + if ( enableSpring != base->sphericalJoint.enableSpring ) + { + base->sphericalJoint.springImpulse = b3Vec3_zero; + } + base->sphericalJoint.enableSpring = enableSpring; +} + +bool b3SphericalJoint_IsSpringEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.enableSpring; +} + +void b3SphericalJoint_SetTargetRotation( b3JointId jointId, b3Quat targetRotation ) +{ + B3_ASSERT( b3IsValidQuat( targetRotation ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetTargetRotation, jointId, targetRotation ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.targetRotation = targetRotation; +} + +b3Quat b3SphericalJoint_GetTargetRotation( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.targetRotation; +} + +void b3SphericalJoint_SetSpringHertz( b3JointId jointId, float hertz ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetSpringHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.hertz = hertz; +} + +float b3SphericalJoint_GetSpringHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.hertz; +} + +void b3SphericalJoint_SetSpringDampingRatio( b3JointId jointId, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetSpringDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.dampingRatio = dampingRatio; +} + +float b3SphericalJoint_GetSpringDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.dampingRatio; +} + +void b3SphericalJoint_EnableMotor( b3JointId jointId, bool enableMotor ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointEnableMotor, jointId, enableMotor ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + if ( enableMotor != base->sphericalJoint.enableMotor ) + { + base->sphericalJoint.motorImpulse = b3Vec3_zero; + } + base->sphericalJoint.enableMotor = enableMotor; +} + +bool b3SphericalJoint_IsMotorEnabled( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.enableMotor; +} + +void b3SphericalJoint_SetMotorVelocity( b3JointId jointId, b3Vec3 motorVelocity ) +{ + B3_ASSERT( b3IsValidVec3( motorVelocity ) ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetMotorVelocity, jointId, motorVelocity ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.motorVelocity = motorVelocity; +} + +b3Vec3 b3SphericalJoint_GetMotorVelocity( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.motorVelocity; +} + +void b3SphericalJoint_SetMaxMotorTorque( b3JointId jointId, float maxForce ) +{ + B3_ASSERT( b3IsValidFloat( maxForce ) && maxForce >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, SphericalJointSetMaxMotorTorque, jointId, maxForce ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + base->sphericalJoint.maxMotorTorque = maxForce; +} + +float b3SphericalJoint_GetMaxMotorTorque( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return base->sphericalJoint.maxMotorTorque; +} + +b3Vec3 b3SphericalJoint_GetMotorTorque( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_sphericalJoint ); + return b3MulSV( world->inv_h, base->sphericalJoint.motorImpulse ); +} + +b3Vec3 b3GetSphericalJointForce( b3World* world, b3JointSim* base ) +{ + b3Vec3 force = b3MulSV( world->inv_h, base->sphericalJoint.linearImpulse ); + return force; +} + +b3Vec3 b3GetSphericalJointTorque( b3World* world, b3JointSim* base ) +{ + b3WorldTransform xfA = b3GetBodyTransform( world, base->bodyIdA ); + b3WorldTransform xfB = b3GetBodyTransform( world, base->bodyIdB ); + b3Quat qA = b3MulQuat( xfA.q, base->localFrameA.q ); + b3Quat qB = b3MulQuat( xfB.q, base->localFrameB.q ); + + // Cone axis is the z-axis of body A. + b3Vec3 coneAxis = b3RotateVector( qA, b3Vec3_axisZ ); + b3Vec3 twistAxis = b3RotateVector( qB, b3Vec3_axisZ ); + b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) ); + + b3SphericalJoint* joint = &base->sphericalJoint; + b3Vec3 impulse = b3Add( joint->springImpulse, joint->motorImpulse ); + impulse = b3MulAdd( impulse, joint->lowerTwistImpulse - joint->upperTwistImpulse, twistAxis ); + impulse = b3MulAdd( impulse, joint->swingImpulse, swingAxis ); + b3Vec3 torque = b3MulSV( world->inv_h, impulse ); + return torque; +} + +// Point-to-point constraint +// C = p2 - p1 +// Cdot = v2 - v1 +// = v2 + cross(w2, r2) - v1 - cross(w1, r1) +// J = [-I r1_skew I -r2_skew ] +// K = J * invM * transpose(J) +// transpose(skew(r)) = -skew(r) +// K = diag(1/m1 + 1/m2) - r1_skew * invI1 * r1_skew - r2_skew * invI2 * r2_skew + +// r_skew = R * skew(r_local) * RT +// invI = R * invI_local * RT +// r_skew * invI * r_skew = R * skew(r_local) * RT * R * invI_local * RT * R * r_skew * RT +// = R * ( skew(r_local) * invI_local * skew(r_local) ) * RT + +void b3PrepareSphericalJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_sphericalJoint ); + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB ); + + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ); + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3SphericalJoint* joint = &base->sphericalJoint; + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + + // Cone axis is the z-axis of body A. + b3Vec3 coneAxis = b3RotateVector( joint->frameA.q, b3Vec3_axisZ ); + + // Twist axis is the z-axis of body B. + b3Vec3 twistAxis = b3RotateVector( joint->frameB.q, b3Vec3_axisZ ); + + if ( joint->enableConeLimit ) + { + // Swing axis may be zero + b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) ); + float k = b3Dot( swingAxis, b3MulMV( invInertiaSum, swingAxis ) ); + joint->swingMass = k > 0.0f ? 1.0f / k : 0.0f; + joint->swingAxis = swingAxis; + } + + if ( joint->enableTwistLimit ) + { + b3Quat relQ = b3InvMulQuat( joint->frameA.q, joint->frameB.q ); + float tanThetaOver2 = sqrtf( ( relQ.v.x * relQ.v.x + relQ.v.y * relQ.v.y ) / ( relQ.v.z * relQ.v.z + relQ.s * relQ.s ) ); + + // todo verify this Jacobian using a finite difference, unit test? + b3Vec3 swingAxis = b3Normalize( b3Cross( coneAxis, twistAxis ) ); + b3Vec3 perpAxis = b3Cross( swingAxis, coneAxis ); + b3Vec3 twistJacobian = b3MulAdd( coneAxis, tanThetaOver2, perpAxis ); + float k = b3Dot( twistJacobian, b3MulMV( invInertiaSum, twistJacobian ) ); + joint->twistMass = k > 0.0f ? 1.0f / k : 0.0f; + joint->twistJacobian = twistJacobian; + } + + if ( base->fixedRotation == false ) + { + joint->rotationMass = b3InvertMatrix( invInertiaSum ); + } + else + { + joint->rotationMass = b3Mat3_zero; + } + + joint->springSoftness = b3MakeSoft( joint->hertz, joint->dampingRatio, context->h ); + + if ( context->enableWarmStarting == false ) + { + joint->linearImpulse = b3Vec3_zero; + joint->motorImpulse = b3Vec3_zero; + joint->springImpulse = b3Vec3_zero; + joint->swingImpulse = 0.0f; + joint->lowerTwistImpulse = 0.0f; + joint->upperTwistImpulse = 0.0f; + } +} + +void b3WarmStartSphericalJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_sphericalJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3SphericalJoint* joint = &base->sphericalJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 angularImpulse = b3Add( joint->springImpulse, joint->motorImpulse ); + angularImpulse = b3MulSub( angularImpulse, joint->swingImpulse, joint->swingAxis ); + angularImpulse = b3MulAdd( angularImpulse, joint->lowerTwistImpulse - joint->upperTwistImpulse, joint->twistJacobian ); + + vA = b3MulSub( vA, mA, joint->linearImpulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Add( b3Cross( rA, joint->linearImpulse ), angularImpulse ) ) ); + + vB = b3MulAdd( vB, mB, joint->linearImpulse ); + wB = b3Add( wB, b3MulMV( iB, b3Add( b3Cross( rB, joint->linearImpulse ), angularImpulse ) ) ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3SolveSphericalJoint( b3JointSim* base, b3StepContext* context, bool useBias ) +{ + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3SphericalJoint* joint = &base->sphericalJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + bool fixedRotation = base->fixedRotation; + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + // Solve spring + if ( joint->enableSpring && fixedRotation == false ) + { + // Rotation constraint error + b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, joint->targetRotation ); + b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) ); + + b3Vec3 bias = b3MulSV( joint->springSoftness.biasRate, c ); + float massScale = joint->springSoftness.massScale; + float impulseScale = joint->springSoftness.impulseScale; + b3Vec3 cdot = b3Sub( wB, wA ); + + b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b3MulMV( joint->rotationMass, b3Add( cdot, bias ) ) ), + impulseScale, joint->springImpulse ); + joint->springImpulse = b3Add( joint->springImpulse, impulse ); + + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + + if ( joint->enableMotor && fixedRotation == false ) + { + b3Vec3 cdot = b3Sub( wB, wA ); + + b3Vec3 lambda = b3Neg( b3MulMV( joint->rotationMass, b3Sub( cdot, joint->motorVelocity ) ) ); + b3Vec3 newImpulse = b3Add( joint->motorImpulse, lambda ); + float length = b3Length( newImpulse ); + float maxImpulse = joint->maxMotorTorque * context->h; + if ( length > maxImpulse ) + { + newImpulse = b3MulSV( maxImpulse / length, newImpulse ); + } + + lambda = b3Sub( newImpulse, joint->motorImpulse ); + joint->motorImpulse = newImpulse; + + wA = b3Sub( wA, b3MulMV( iA, lambda ) ); + wB = b3Add( wB, b3MulMV( iB, lambda ) ); + } + + if ( joint->enableTwistLimit && fixedRotation == false ) + { + float twistAngle = b3GetTwistAngle( relQ ); + + // todo does an updated twist axis help? + + b3Vec3 twistJacobian = joint->twistJacobian; + + // Lower limit + { + float c = twistAngle - joint->lowerTwistAngle; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + float cdot = b3Dot( b3Sub( wB, wA ), twistJacobian ); + float oldImpulse = joint->lowerTwistImpulse; + float deltaImpulse = -massScale * joint->twistMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->lowerTwistImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + deltaImpulse = joint->lowerTwistImpulse - oldImpulse; + + wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, twistJacobian ) ); + wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, twistJacobian ) ); + } + + // Upper limit + { + float c = joint->upperTwistAngle - twistAngle; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // sign flipped on Cdot + float cdot = b3Dot( b3Sub( wA, wB ), twistJacobian ); + float oldImpulse = joint->upperTwistImpulse; + float deltaImpulse = -massScale * joint->twistMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->upperTwistImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + deltaImpulse = joint->upperTwistImpulse - oldImpulse; + + // sign flipped on applied impulse + wA = b3MulAdd( wA, deltaImpulse, b3MulMV( iA, twistJacobian ) ); + wB = b3MulSub( wB, deltaImpulse, b3MulMV( iB, twistJacobian ) ); + } + } + + if ( joint->enableConeLimit && fixedRotation == false ) + { + float swingAngle = b3GetSwingAngle( relQ ); + + // todo does an updated swing axis help? + // b3Vec3 axisA = b3RotateVector( quatA, b3Vec3_axisZ ); + // b3Vec3 axisB = b3RotateVector( quatB, b3Vec3_axisZ ); + // b3Vec3 swingAxis = b3Normalize( b3Cross( axisA, axisB ) ); + // joint->swingAxis = swingAxis; + + b3Vec3 swingAxis = joint->swingAxis; + + float c = joint->coneAngle - swingAngle; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // sign flipped on Cdot + float cdot = b3Dot( b3Sub( wA, wB ), swingAxis ); + float oldImpulse = joint->swingImpulse; + float deltaImpulse = -massScale * joint->swingMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->swingImpulse = b3MaxFloat( oldImpulse + deltaImpulse, 0.0f ); + deltaImpulse = joint->swingImpulse - oldImpulse; + + // sign flipped on applied impulse + wA = b3MulAdd( wA, deltaImpulse, b3MulMV( iA, swingAxis ) ); + wB = b3MulSub( wB, deltaImpulse, b3MulMV( iB, swingAxis ) ); + } + + // Solve point-to-point constraint + { + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 cdot = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, rA ) ); + + b3Vec3 bias = b3Vec3_zero; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias ) + { + b3Vec3 dcA = stateA->deltaPosition; + b3Vec3 dcB = stateB->deltaPosition; + + b3Vec3 separation = b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ); + separation = b3Add( separation, joint->deltaCenter ); + + bias = b3MulSV( base->constraintSoftness.biasRate, separation ); + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + //// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + b3Matrix3 sA = b3Skew( rA ); + b3Matrix3 sB = b3Skew( rB ); + b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) ); + b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) ); + b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) ); + k.cx.x += mA + mB; + k.cy.y += mA + mB; + k.cz.z += mA + mB; + + b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) ); + + b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b ), impulseScale, joint->linearImpulse ); + joint->linearImpulse = b3Add( joint->linearImpulse, impulse ); + + vA = b3MulSub( vA, mA, impulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3DrawSphericalJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ) +{ + b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA ); + + float length1 = 0.1f * scale; + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisX ) ) ), b3_colorRed, + draw->context ); + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisY ) ) ), b3_colorGreen, + draw->context ); + draw->DrawSegmentFcn( frameA.p, b3OffsetPos( frameA.p, b3MulSV( length1, b3RotateVector( frameA.q, b3Vec3_axisZ ) ) ), b3_colorBlue, + draw->context ); + + b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB ); + + float length2 = 0.2f * scale; + draw->DrawSegmentFcn( frameB.p, b3OffsetPos( frameB.p, b3MulSV( length2, b3RotateVector( frameB.q, b3Vec3_axisZ ) ) ), b3_colorOrange, + draw->context ); + + b3SphericalJoint* joint = &base->sphericalJoint; + enum { kSliceCount = 16 }; + + // Twist limit + if ( joint->enableTwistLimit ) + { + b3Quat quatA = frameA.q; + b3Quat quatB = frameB.q; + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the twist angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + const float wedgeRadius = 0.1f * scale; + for ( int index = 0; index < kSliceCount; ++index ) + { + float t1 = (float)( index + 0 ) / kSliceCount; + float alpha1 = ( 1.0f - t1 ) * joint->lowerTwistAngle + t1 * joint->upperTwistAngle; + float t2 = (float)( index + 1 ) / kSliceCount; + float alpha2 = ( 1.0f - t2 ) * joint->lowerTwistAngle + t2 * joint->upperTwistAngle; + + b3Vec3 vertex1 = { wedgeRadius * b3Cos( alpha1 ), wedgeRadius * b3Sin( alpha1 ), 0.0f }; + b3Vec3 vertex2 = { wedgeRadius * b3Cos( alpha2 ), wedgeRadius * b3Sin( alpha2 ), 0.0f }; + + if ( index == 0 ) + { + draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, vertex1 ), b3_colorCyan, draw->context ); + } + + if ( index == kSliceCount - 1 ) + { + draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex2 ), frameA.p, b3_colorCyan, draw->context ); + } + draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex1 ), b3TransformWorldPoint( frameA, vertex2 ), b3_colorCyan, + draw->context ); + } + + float twistAngle = b3GetTwistAngle( relQ ); + b3Vec3 p2 = { wedgeRadius * b3Cos( twistAngle ), wedgeRadius * b3Sin( twistAngle ), 0.0f }; + draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, p2 ), b3_colorYellow, draw->context ); + } + + // Swing limit + if ( joint->enableConeLimit ) + { + const float radius = 0.1f * scale; + float coneRadius = radius * b3Sin( joint->coneAngle ); + float coneHeight = radius * b3Cos( joint->coneAngle ); + + for ( int index = 0; index < kSliceCount; ++index ) + { + float phi1 = 2.0f * ( index + 0 ) / kSliceCount * B3_PI; + float phi2 = 2.0f * ( index + 1 ) / kSliceCount * B3_PI; + + b3Vec3 vertex1 = { coneRadius * b3Cos( phi1 ), coneRadius * b3Sin( phi1 ), coneHeight }; + b3Vec3 vertex2 = { coneRadius * b3Cos( phi2 ), coneRadius * b3Sin( phi2 ), coneHeight }; + + draw->DrawSegmentFcn( frameA.p, b3TransformWorldPoint( frameA, vertex1 ), b3_colorCyan, draw->context ); + draw->DrawSegmentFcn( b3TransformWorldPoint( frameA, vertex1 ), b3TransformWorldPoint( frameA, vertex2 ), b3_colorCyan, + draw->context ); + } + } +} diff --git a/vendor/box3d/src/src/table.c b/vendor/box3d/src/src/table.c new file mode 100644 index 000000000..fb066b807 --- /dev/null +++ b/vendor/box3d/src/src/table.c @@ -0,0 +1,248 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "table.h" + +#include "bitset.h" +#include "core.h" +#include "ctz.h" +#include "platform.h" + +#include + +#if B3_DEBUG +b3AtomicInt b3_probeCount; +#endif + +_Static_assert( 2 * B3_SHAPE_POWER + B3_CHILD_POWER == 64, "compound power" ); +_Static_assert( B3_CHILD_POWER > 8, "compound child power" ); + +b3HashSet b3CreateSet( int32_t capacity ) +{ + b3HashSet set = { 0 }; + + // Capacity must be a power of 2 + if ( capacity > 16 ) + { + set.capacity = b3RoundUpPowerOf2( capacity ); + } + else + { + set.capacity = 16; + } + + set.count = 0; + set.items = (b3SetItem*)b3Alloc( set.capacity * sizeof( b3SetItem ) ); + memset( set.items, 0, set.capacity * sizeof( b3SetItem ) ); + + return set; +} + +void b3DestroySet( b3HashSet* set ) +{ + b3Free( set->items, set->capacity * sizeof( b3SetItem ) ); + set->items = NULL; + set->count = 0; + set->capacity = 0; +} + +void b3ClearSet( b3HashSet* set ) +{ + set->count = 0; + memset( set->items, 0, set->capacity * sizeof( b3SetItem ) ); +} + +// I need a good hash because the keys are built from pairs of increasing integers. +// A simple hash like hash = (integer1 XOR integer2) has many collisions. +// https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere/ +// https://preshing.com/20130107/this-hash-set-is-faster-than-a-judy-array/ +// todo try: https://www.jandrewrogers.com/2019/02/12/fast-perfect-hashing/ +// todo try: +// https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/ +static inline uint32_t b3KeyHash( uint64_t key ) +{ + uint64_t h = key; + h ^= h >> 33; + h *= 0xff51afd7ed558ccdL; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53L; + h ^= h >> 33; + + return (uint32_t)h; +} + +static int32_t b3FindSlot( const b3HashSet* set, uint64_t key, uint32_t hash ) +{ + uint32_t capacity = set->capacity; + int32_t index = hash & ( capacity - 1 ); + const b3SetItem* items = set->items; + while ( items[index].hash != 0 && items[index].key != key ) + { +#if B3_DEBUG + b3AtomicFetchAddInt( &b3_probeCount, 1 ); +#endif + index = ( index + 1 ) & ( capacity - 1 ); + } + + return index; +} + +static void b3AddKeyHaveCapacity( b3HashSet* set, uint64_t key, uint32_t hash ) +{ + int32_t index = b3FindSlot( set, key, hash ); + b3SetItem* items = set->items; + B3_ASSERT( items[index].hash == 0 ); + + items[index].key = key; + items[index].hash = hash; + set->count += 1; +} + +static void b3GrowTable( b3HashSet* set ) +{ + uint32_t oldCount = set->count; + B3_UNUSED( oldCount ); + + uint32_t oldCapacity = set->capacity; + b3SetItem* oldItems = set->items; + + set->count = 0; + // Capacity must be a power of 2 + set->capacity = 2 * oldCapacity; + set->items = (b3SetItem*)b3Alloc( set->capacity * sizeof( b3SetItem ) ); + memset( set->items, 0, set->capacity * sizeof( b3SetItem ) ); + + // Transfer items into new array + for ( uint32_t i = 0; i < oldCapacity; ++i ) + { + b3SetItem* item = oldItems + i; + if ( item->hash == 0 ) + { + // this item was empty + continue; + } + + b3AddKeyHaveCapacity( set, item->key, item->hash ); + } + + B3_ASSERT( set->count == oldCount ); + + b3Free( oldItems, oldCapacity * sizeof( b3SetItem ) ); +} + +bool b3ContainsKey( const b3HashSet* set, uint64_t key ) +{ + // key of zero is a sentinel + B3_ASSERT( key != 0 ); + uint32_t hash = b3KeyHash( key ); + int32_t index = b3FindSlot( set, key, hash ); + return set->items[index].key == key; +} + +int b3GetHashSetBytes( b3HashSet* set ) +{ + return set->capacity * (int)sizeof( b3SetItem ); +} + +bool b3AddKey( b3HashSet* set, uint64_t key ) +{ + // key of zero is a sentinel + B3_ASSERT( key != 0 ); + + uint32_t hash = b3KeyHash( key ); + B3_ASSERT( hash != 0 ); + + int32_t index = b3FindSlot( set, key, hash ); + if ( set->items[index].hash != 0 ) + { + // Already in set + B3_ASSERT( set->items[index].hash == hash && set->items[index].key == key ); + return true; + } + + if ( 2 * set->count >= set->capacity ) + { + b3GrowTable( set ); + } + + b3AddKeyHaveCapacity( set, key, hash ); + return false; +} + +// See https://en.wikipedia.org/wiki/Open_addressing +bool b3RemoveKey( b3HashSet* set, uint64_t key ) +{ + uint32_t hash = b3KeyHash( key ); + int32_t i = b3FindSlot( set, key, hash ); + b3SetItem* items = set->items; + if ( items[i].hash == 0 ) + { + // Not in set + return false; + } + + // Mark item i as unoccupied + items[i].key = 0; + items[i].hash = 0; + + B3_ASSERT( set->count > 0 ); + set->count -= 1; + + // Attempt to fill item i + int32_t j = i; + uint32_t capacity = set->capacity; + for ( ;; ) + { + j = ( j + 1 ) & ( capacity - 1 ); + if ( items[j].hash == 0 ) + { + break; + } + + // k is the first item for the hash of j + int32_t k = items[j].hash & ( capacity - 1 ); + + // determine if k lies cyclically in (i,j] + // i <= j: | i..k..j | + // i > j: |.k..j i....| or |....j i..k.| + if ( i <= j ) + { + if ( i < k && k <= j ) + { + continue; + } + } + else + { + if ( i < k || k <= j ) + { + continue; + } + } + + // Move j into i + items[i] = items[j]; + + // Mark item j as unoccupied + items[j].key = 0; + items[j].hash = 0; + + i = j; + } + + return true; +} + +// This function is here because ctz.h is included by +// this file but not in bitset.c +int b3CountSetBits( b3BitSet* bitSet ) +{ + int popCount = 0; + uint32_t blockCount = bitSet->blockCount; + for ( uint32_t i = 0; i < blockCount; ++i ) + { + popCount += b3PopCount64( bitSet->bits[i] ); + } + + return popCount; +} diff --git a/vendor/box3d/src/src/table.h b/vendor/box3d/src/src/table.h new file mode 100644 index 000000000..ec8f39782 --- /dev/null +++ b/vendor/box3d/src/src/table.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "box3d/constants.h" + +#include +#include + +typedef struct b3SetItem +{ + uint64_t key; + uint32_t hash; +} b3SetItem; + +typedef struct b3HashSet +{ + b3SetItem* items; + uint32_t capacity; + uint32_t count; +} b3HashSet; + +#define B3_SHAPE_MASK ( B3_MAX_SHAPES - 1 ) +#define B3_CHILD_MASK ( B3_MAX_CHILD_SHAPES - 1 ) + +static inline uint64_t b3ShapePairKey( int s1, int s2, int c ) +{ + if (s1 < s2) + { + return ( (uint64_t)( B3_SHAPE_MASK & s1 ) << ( 64 - B3_SHAPE_POWER ) ) | + ( (uint64_t)( B3_SHAPE_MASK & s2 ) << ( 64 - 2 * B3_SHAPE_POWER ) ) | ( (uint64_t)( B3_CHILD_MASK & c ) ); + } + + return ( (uint64_t)( B3_SHAPE_MASK & s2 ) << ( 64 - B3_SHAPE_POWER ) ) | + ( (uint64_t)( B3_SHAPE_MASK & s1 ) << ( 64 - 2 * B3_SHAPE_POWER ) ) | + ( (uint64_t)( B3_CHILD_MASK & c ) ); +} + +b3HashSet b3CreateSet( int32_t capacity ); +void b3DestroySet( b3HashSet* set ); + +void b3ClearSet( b3HashSet* set ); + +// Returns true if key was already in set +bool b3AddKey( b3HashSet* set, uint64_t key ); + +// Returns true if the key was found +bool b3RemoveKey( b3HashSet* set, uint64_t key ); + +bool b3ContainsKey( const b3HashSet* set, uint64_t key ); + +int b3GetHashSetBytes( b3HashSet* set ); diff --git a/vendor/box3d/src/src/timer.c b/vendor/box3d/src/src/timer.c new file mode 100644 index 000000000..826b1495c --- /dev/null +++ b/vendor/box3d/src/src/timer.c @@ -0,0 +1,700 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +// Required on Linux to expose pthread_setname_np. Must be defined before any +// system header is included. +#if defined( __linux__ ) && !defined( _GNU_SOURCE ) +#define _GNU_SOURCE +#endif + +#include "core.h" + +#include "box3d/base.h" + +#include +#include +#include + +#define NAME_LENGTH 16 + +#if defined( _WIN32 ) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif + +#include +#include + +static double s_invFrequency = 0.0; + +uint64_t b3GetTicks( void ) +{ + LARGE_INTEGER counter; + QueryPerformanceCounter( &counter ); + return (uint64_t)counter.QuadPart; +} + +float b3GetMilliseconds( uint64_t ticks ) +{ + if ( s_invFrequency == 0.0 ) + { + LARGE_INTEGER frequency; + QueryPerformanceFrequency( &frequency ); + + s_invFrequency = (double)frequency.QuadPart; + if ( s_invFrequency > 0.0 ) + { + s_invFrequency = 1000.0 / s_invFrequency; + } + } + + uint64_t ticksNow = b3GetTicks(); + return (float)( s_invFrequency * ( ticksNow - ticks ) ); +} + +float b3GetMillisecondsAndReset( uint64_t* ticks ) +{ + if ( s_invFrequency == 0.0 ) + { + LARGE_INTEGER frequency; + QueryPerformanceFrequency( &frequency ); + + s_invFrequency = (double)frequency.QuadPart; + if ( s_invFrequency > 0.0 ) + { + s_invFrequency = 1000.0 / s_invFrequency; + } + } + + uint64_t ticksNow = b3GetTicks(); + float ms = (float)( s_invFrequency * ( ticksNow - *ticks ) ); + *ticks = ticksNow; + return ms; +} + +void b3Yield( void ) +{ + SwitchToThread(); +} + +void b3Sleep( int milliseconds ) +{ + Sleep( (DWORD)milliseconds ); +} + +typedef struct b3Mutex +{ + CRITICAL_SECTION cs; +} b3Mutex; + +b3Mutex* b3CreateMutex( void ) +{ + b3Mutex* m = b3Alloc( sizeof( b3Mutex ) ); + InitializeCriticalSection( &m->cs ); + return m; +} + +void b3DestroyMutex( b3Mutex* m ) +{ + DeleteCriticalSection( &m->cs ); + *m = (b3Mutex){ 0 }; + b3Free( m, sizeof( b3Mutex ) ); +} + +void b3LockMutex( b3Mutex* m ) +{ + EnterCriticalSection( &m->cs ); +} + +void b3UnlockMutex( b3Mutex* m ) +{ + LeaveCriticalSection( &m->cs ); +} + +typedef struct b3Semaphore +{ + HANDLE semaphore; +} b3Semaphore; + +b3Semaphore* b3CreateSemaphore( int initCount ) +{ + b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) ); + s->semaphore = CreateSemaphoreExW( NULL, initCount, INT_MAX, NULL, 0, SEMAPHORE_ALL_ACCESS ); + return s; +} + +void b3DestroySemaphore( b3Semaphore* s ) +{ + CloseHandle( s->semaphore ); + *s = (b3Semaphore){ 0 }; + b3Free( s, sizeof( b3Semaphore ) ); +} + +void b3WaitSemaphore( b3Semaphore* s ) +{ + WaitForSingleObjectEx( s->semaphore, INFINITE, FALSE ); +} + +void b3SignalSemaphore( b3Semaphore* s ) +{ + ReleaseSemaphore( s->semaphore, 1, NULL ); +} + +typedef struct b3Thread +{ + HANDLE thread; + b3ThreadFunction* function; + void* context; + char name[NAME_LENGTH]; +} b3Thread; + +typedef HRESULT( WINAPI* b3SetThreadDescriptionFn )( HANDLE, PCWSTR ); + +// SetThreadDescription exists on Windows 10 1607+. Resolve it dynamically so +// older Windows versions still link. Resolved once, cached for subsequent calls. +static void b3SetCurrentThreadName( const char* name ) +{ + if ( name == NULL || name[0] == 0 ) + { + return; + } + + static b3SetThreadDescriptionFn pfn = NULL; + static int resolved = 0; + + if ( resolved == 0 ) + { + HMODULE kernel = GetModuleHandleW( L"kernel32.dll" ); + if ( kernel != NULL ) + { + // MSVC /Wall warns C4191 on every FARPROC function-pointer cast. + // This is the intended use of GetProcAddress, so suppress locally. +#pragma warning( push ) +#pragma warning( disable : 4191 ) + pfn = (b3SetThreadDescriptionFn)GetProcAddress( kernel, "SetThreadDescription" ); +#pragma warning( pop ) + } + resolved = 1; + } + + if ( pfn == NULL ) + { + return; + } + + wchar_t wide[NAME_LENGTH]; + int n = MultiByteToWideChar( CP_UTF8, 0, name, -1, wide, (int)( sizeof( wide ) / sizeof( wide[0] ) ) ); + if ( n > 0 ) + { + pfn( GetCurrentThread(), wide ); + } +} + +static DWORD WINAPI b3ThreadStart( LPVOID param ) +{ + b3Thread* t = (b3Thread*)param; + b3SetCurrentThreadName( t->name ); + t->function( t->context ); + return 0; +} + +b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name ) +{ + b3Thread* t = b3Alloc( sizeof( b3Thread ) ); + t->function = function; + t->context = context; + if ( name != NULL ) + { + snprintf( t->name, sizeof( t->name ), "%s", name ); + } + else + { + t->name[0] = 0; + } + t->thread = CreateThread( NULL, 0, b3ThreadStart, t, 0, NULL ); + return t; +} + +void b3JoinThread( b3Thread* t ) +{ + WaitForSingleObject( t->thread, INFINITE ); + CloseHandle( t->thread ); + *t = (b3Thread){ 0 }; + b3Free( t, sizeof( b3Thread ) ); +} + +#elif defined( __linux__ ) || defined( __EMSCRIPTEN__ ) + +#include +#include + +uint64_t b3GetTicks( void ) +{ + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC, &ts ); + return ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +float b3GetMilliseconds( uint64_t ticks ) +{ + uint64_t ticksNow = b3GetTicks(); + return (float)( ( ticksNow - ticks ) / 1000000.0 ); +} + +float b3GetMillisecondsAndReset( uint64_t* ticks ) +{ + uint64_t ticksNow = b3GetTicks(); + float ms = (float)( ( ticksNow - *ticks ) / 1000000.0 ); + *ticks = ticksNow; + return ms; +} + +void b3Yield( void ) +{ + sched_yield(); +} + +void b3Sleep( int milliseconds ) +{ + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = ( milliseconds % 1000 ) * 1000000L; + nanosleep( &ts, NULL ); +} + +#include +typedef struct b3Mutex +{ + pthread_mutex_t mtx; +} b3Mutex; + +b3Mutex* b3CreateMutex( void ) +{ + b3Mutex* m = b3Alloc( sizeof( b3Mutex ) ); + pthread_mutex_init( &m->mtx, NULL ); + return m; +} + +void b3DestroyMutex( b3Mutex* m ) +{ + pthread_mutex_destroy( &m->mtx ); + *m = (b3Mutex){ 0 }; + b3Free( m, sizeof( b3Mutex ) ); +} + +void b3LockMutex( b3Mutex* m ) +{ + pthread_mutex_lock( &m->mtx ); +} + +void b3UnlockMutex( b3Mutex* m ) +{ + pthread_mutex_unlock( &m->mtx ); +} + +#include + +typedef struct b3Semaphore +{ + sem_t semaphore; +} b3Semaphore; + +b3Semaphore* b3CreateSemaphore( int initCount ) +{ + b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) ); + sem_init( &s->semaphore, 0, (unsigned int)initCount ); + return s; +} + +void b3DestroySemaphore( b3Semaphore* s ) +{ + sem_destroy( &s->semaphore ); + *s = (b3Semaphore){ 0 }; + b3Free( s, sizeof( b3Semaphore ) ); +} + +void b3WaitSemaphore( b3Semaphore* s ) +{ + sem_wait( &s->semaphore ); +} + +void b3SignalSemaphore( b3Semaphore* s ) +{ + sem_post( &s->semaphore ); +} + +typedef struct b3Thread +{ + pthread_t thread; + b3ThreadFunction* function; + void* context; + char name[NAME_LENGTH]; +} b3Thread; + +static void b3SetCurrentThreadName( const char* name ) +{ + if ( name == NULL || name[0] == 0 ) + { + return; + } + +#if defined( __linux__ ) + // Linux caps thread names at 15 chars + null terminator. + char truncated[NAME_LENGTH]; + snprintf( truncated, sizeof( truncated ), "%s", name ); + pthread_setname_np( pthread_self(), truncated ); +#else + (void)name; +#endif +} + +static void* b3ThreadStart( void* param ) +{ + b3Thread* t = (b3Thread*)param; + b3SetCurrentThreadName( t->name ); + t->function( t->context ); + return NULL; +} + +b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name ) +{ + b3Thread* t = b3Alloc( sizeof( b3Thread ) ); + t->function = function; + t->context = context; + if ( name != NULL ) + { + snprintf( t->name, sizeof( t->name ), "%s", name ); + } + else + { + t->name[0] = 0; + } + pthread_create( &t->thread, NULL, b3ThreadStart, t ); + return t; +} + +void b3JoinThread( b3Thread* t ) +{ + pthread_join( t->thread, NULL ); + *t = (b3Thread){ 0 }; + b3Free( t, sizeof( b3Thread ) ); +} + +#elif defined( __APPLE__ ) + +#include +#include +#include +#include + +static double s_invFrequency = 0.0; + +uint64_t b3GetTicks( void ) +{ + return mach_absolute_time(); +} + +float b3GetMilliseconds( uint64_t ticks ) +{ + if ( s_invFrequency == 0 ) + { + mach_timebase_info_data_t timebase; + mach_timebase_info( &timebase ); + + // convert to ns then to ms + s_invFrequency = 1e-6 * (double)timebase.numer / (double)timebase.denom; + } + + uint64_t ticksNow = b3GetTicks(); + return (float)( s_invFrequency * ( ticksNow - ticks ) ); +} + +float b3GetMillisecondsAndReset( uint64_t* ticks ) +{ + if ( s_invFrequency == 0 ) + { + mach_timebase_info_data_t timebase; + mach_timebase_info( &timebase ); + + // convert to ns then to ms + s_invFrequency = 1e-6 * (double)timebase.numer / (double)timebase.denom; + } + + uint64_t ticksNow = b3GetTicks(); + float ms = (float)( s_invFrequency * ( ticksNow - *ticks ) ); + *ticks = ticksNow; + return ms; +} + +void b3Yield( void ) +{ + sched_yield(); +} + +void b3Sleep( int milliseconds ) +{ + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = ( milliseconds % 1000 ) * 1000000L; + nanosleep( &ts, NULL ); +} + +#include +typedef struct b3Mutex +{ + pthread_mutex_t mtx; +} b3Mutex; + +b3Mutex* b3CreateMutex( void ) +{ + b3Mutex* m = b3Alloc( sizeof( b3Mutex ) ); + pthread_mutex_init( &m->mtx, NULL ); + return m; +} + +void b3DestroyMutex( b3Mutex* m ) +{ + pthread_mutex_destroy( &m->mtx ); + *m = (b3Mutex){ 0 }; + b3Free( m, sizeof( b3Mutex ) ); +} + +void b3LockMutex( b3Mutex* m ) +{ + pthread_mutex_lock( &m->mtx ); +} + +void b3UnlockMutex( b3Mutex* m ) +{ + pthread_mutex_unlock( &m->mtx ); +} + +#include + +typedef struct b3Semaphore +{ + dispatch_semaphore_t semaphore; + int initialCount; +} b3Semaphore; + +b3Semaphore* b3CreateSemaphore( int initCount ) +{ + b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) ); + s->semaphore = dispatch_semaphore_create( (long)initCount ); + s->initialCount = initCount; + return s; +} + +void b3DestroySemaphore( b3Semaphore* s ) +{ + // libdispatch aborts if the current count is less than the initial count at release time. + // Pad with signals so the invariant always holds; no one is waiting at this point. + for ( int i = 0; i < s->initialCount; ++i ) + { + dispatch_semaphore_signal( s->semaphore ); + } + dispatch_release( s->semaphore ); + *s = (b3Semaphore){ 0 }; + b3Free( s, sizeof( b3Semaphore ) ); +} + +void b3WaitSemaphore( b3Semaphore* s ) +{ + dispatch_semaphore_wait( s->semaphore, DISPATCH_TIME_FOREVER ); +} + +void b3SignalSemaphore( b3Semaphore* s ) +{ + dispatch_semaphore_signal( s->semaphore ); +} + +typedef struct b3Thread +{ + pthread_t thread; + b3ThreadFunction* function; + void* context; + char name[NAME_LENGTH]; +} b3Thread; + +// macOS pthread_setname_np takes only the name — it always names the calling thread. +static void b3SetCurrentThreadName( const char* name ) +{ + if ( name == NULL || name[0] == 0 ) + { + return; + } + pthread_setname_np( name ); +} + +static void* b3ThreadStart( void* param ) +{ + b3Thread* t = (b3Thread*)param; + b3SetCurrentThreadName( t->name ); + t->function( t->context ); + return NULL; +} + +b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name ) +{ + b3Thread* t = b3Alloc( sizeof( b3Thread ) ); + t->function = function; + t->context = context; + if ( name != NULL ) + { + snprintf( t->name, sizeof( t->name ), "%s", name ); + } + else + { + t->name[0] = 0; + } + pthread_create( &t->thread, NULL, b3ThreadStart, t ); + return t; +} + +void b3JoinThread( b3Thread* t ) +{ + pthread_join( t->thread, NULL ); + *t = (b3Thread){ 0 }; + b3Free( t, sizeof( b3Thread ) ); +} + +#else + +uint64_t b3GetTicks( void ) +{ + return 0; +} + +float b3GetMilliseconds( uint64_t ticks ) +{ + ( (void)( ticks ) ); + return 0.0f; +} + +float b3GetMillisecondsAndReset( uint64_t* ticks ) +{ + ( (void)( ticks ) ); + return 0.0f; +} + +void b3Yield( void ) +{ +} + +void b3Sleep( int milliseconds ) +{ + ( (void)( milliseconds ) ); +} + +typedef struct b3Mutex +{ + int dummy; +} b3Mutex; + +b3Mutex* b3CreateMutex( void ) +{ + b3Mutex* m = b3Alloc( sizeof( b3Mutex ) ); + m->dummy = 42; + return m; +} + +void b3DestroyMutex( b3Mutex* m ) +{ + *m = (b3Mutex){ 0 }; + b3Free( m, sizeof( b3Mutex ) ); +} + +void b3LockMutex( b3Mutex* m ) +{ + (void)m; +} + +void b3UnlockMutex( b3Mutex* m ) +{ + (void)m; +} + +typedef struct b3Semaphore +{ + int dummy; +} b3Semaphore; + +b3Semaphore* b3CreateSemaphore( int initCount ) +{ + b3Semaphore* s = b3Alloc( sizeof( b3Semaphore ) ); + (void)initCount; + s->dummy = 42; + return s; +} + +void b3DestroySemaphore( b3Semaphore* s ) +{ + *s = (b3Semaphore){ 0 }; + b3Free( s, sizeof( b3Semaphore ) ); +} + +void b3WaitSemaphore( b3Semaphore* s ) +{ + (void)s; +} + +void b3SignalSemaphore( b3Semaphore* s ) +{ + (void)s; +} + +typedef struct b3Thread +{ + int dummy; +} b3Thread; + +b3Thread* b3CreateThread( b3ThreadFunction* function, void* context, const char* name ) +{ + (void)name; + function( context ); + b3Thread* t = b3Alloc( sizeof( b3Thread ) ); + t->dummy = 42; + return t; +} + +void b3JoinThread( b3Thread* t ) +{ + *t = (b3Thread){ 0 }; + b3Free( t, sizeof( b3Thread ) ); +} + +#endif + +// djb2 hash, folded 8 bytes per iteration to shorten the dependency chain. +// memcpy lowers to a single load on most targets; on big-endian we byte-swap so +// the hash value is identical across endianness (preserving cross-platform determinism). +// Equivalent to byte-wise djb2 only in spirit; values differ from the original recurrence. +uint32_t b3Hash( uint32_t hash, const uint8_t* data, int count ) +{ + uint32_t result = hash; + int i = 0; + + while ( i + 8 <= count ) + { + uint64_t word; + memcpy( &word, data + i, sizeof( word ) ); +#if defined( __BYTE_ORDER__ ) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + word = ( ( word & 0x00000000000000FFULL ) << 56 ) | ( ( word & 0x000000000000FF00ULL ) << 40 ) | + ( ( word & 0x0000000000FF0000ULL ) << 24 ) | ( ( word & 0x00000000FF000000ULL ) << 8 ) | + ( ( word & 0x000000FF00000000ULL ) >> 8 ) | ( ( word & 0x0000FF0000000000ULL ) >> 24 ) | + ( ( word & 0x00FF000000000000ULL ) >> 40 ) | ( ( word & 0xFF00000000000000ULL ) >> 56 ); +#endif + result = ( result << 5 ) + result + (uint32_t)word; + result = ( result << 5 ) + result + (uint32_t)( word >> 32 ); + i += 8; + } + + while ( i < count ) + { + result = ( result << 5 ) + result + data[i]; + i++; + } + + return result; +} diff --git a/vendor/box3d/src/src/triangle_manifold.c b/vendor/box3d/src/src/triangle_manifold.c new file mode 100644 index 000000000..b87973add --- /dev/null +++ b/vendor/box3d/src/src/triangle_manifold.c @@ -0,0 +1,1274 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "algorithm.h" +#include "contact.h" +#include "core.h" +#include "manifold.h" +#include "shape.h" + +#include "box3d/base.h" +#include "box3d/collision.h" +#include "box3d/constants.h" + +#include +#include + +typedef struct b3TriangleData +{ + b3Vec3 v1, v2, v3; + b3Vec3 e1, e2, e3; + b3Vec3 center; + b3Plane plane; + int flags; +} b3TriangleData; + +// Indexed by the 3-bit vertex mask +static const b3TriangleFeature s_triangleFeatures[8] = { + b3_featureNone, // 000 (unreachable) + b3_featureVertex1, // 001 + b3_featureVertex2, // 010 + b3_featureEdge1, // 011 v1,v2 + b3_featureVertex3, // 100 + b3_featureEdge3, // 101 v1,v3 + b3_featureEdge2, // 110 v2,v3 + b3_featureTriangleFace, // 111 +}; + +static b3TriangleFeature b3GetTriangleFeature( const b3SimplexCache* cache ) +{ + int count = cache->count; + B3_ASSERT( 0 < count && count < 4 ); + + // Bit i set means triangle vertex i participates in the simplex. + int mask = 0; + for ( int i = 0; i < count; ++i ) + { + B3_ASSERT( cache->indexA[i] < 3 ); + mask |= 1 << cache->indexA[i]; + } + + return s_triangleFeatures[mask]; +} + +void b3CollideSphereAndTriangle( b3LocalManifold* manifold, int capacity, const b3Sphere* sphereA, const b3Vec3* triangleB ) +{ + manifold->pointCount = 0; + + if ( capacity == 0 ) + { + return; + } + + b3Vec3 center = sphereA->center; + b3Vec3 v1 = triangleB[0], v2 = triangleB[1], v3 = triangleB[2]; + b3Plane plane = b3MakePlaneFromPoints( v1, v2, v3 ); + + float offset = b3PlaneSeparation( plane, center ); + if ( offset < 0.0f ) + { + // Cull back side collision + return; + } + + // Closest point on triangle to sphere center + b3TrianglePoint closest = b3ClosestPointOnTriangle( v1, v2, v3, center ); + + // Test separating axis + float squaredDistance = b3DistanceSquared( closest.point, center ); + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + float maxDistance = sphereA->radius + speculativeDistance; + if ( squaredDistance > maxDistance * maxDistance ) + { + return; + } + + float distance = sqrtf( squaredDistance ); + b3Vec3 normal; + if ( distance * distance > 1000.0f * FLT_MIN ) + { + normal = b3MulSV( 1.0f / distance, b3Sub( center, closest.point ) ); + } + else + { + normal = b3Normalize( b3Cross( b3Sub( v2, v1 ), b3Sub( v3, v1 ) ) ); + } + + // contact point mid-way + b3Vec3 contactPoint = b3MulSV( 0.5f, b3Add( b3Sub( center, b3MulSV( sphereA->radius, normal ) ), closest.point ) ); + + manifold->normal = normal; + manifold->pointCount = 1; + manifold->feature = closest.feature; + manifold->squaredDistance = squaredDistance; + + b3LocalManifoldPoint* mp = manifold->points + 0; + mp->point = contactPoint; + mp->separation = distance - sphereA->radius; + mp->pair = b3FeaturePair_single; +} + +static bool b3ClipSegmentToTriangleFace( b3ClipVertex segment[2], const b3Vec3* points, b3Plane plane ) +{ + b3Vec3 vertex1 = points[2]; + for ( int i = 0; i < 3; ++i ) + { + b3Vec3 vertex2 = points[i]; + b3Vec3 tangent = b3Normalize( b3Sub( vertex2, vertex1 ) ); + b3Vec3 binormal = b3Cross( tangent, plane.normal ); + + b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( binormal, vertex1 ); + + int vertexCount = 0; + b3ClipVertex p1 = segment[0]; + b3ClipVertex p2 = segment[1]; + + float distance1 = b3PlaneSeparation( clipPlane, p1.position ); + float distance2 = b3PlaneSeparation( clipPlane, p2.position ); + + // If the points are behind the plane + if ( distance1 <= 0.0f ) + { + segment[vertexCount++] = p1; + } + if ( distance2 <= 0.0f ) + { + segment[vertexCount++] = p2; + } + + // If the points are on different sides of the plane + if ( distance1 * distance2 < 0.0f ) + { + // Find intersection point of edge and plane + float t = distance1 / ( distance1 - distance2 ); + segment[vertexCount].position = b3Lerp( p1.position, p2.position, t ); + segment[vertexCount].pair = distance1 > 0.0f ? p1.pair : p2.pair; + vertexCount++; + } + + if ( vertexCount != 2 ) + { + return false; + } + + vertex1 = vertex2; + } + + return true; +} + +static b3FaceQuery b3QueryTriangleFaceAndCapsule( b3Plane plane, const b3Capsule* capsule ) +{ + float separation1 = b3PlaneSeparation( plane, capsule->center1 ); + float separation2 = b3PlaneSeparation( plane, capsule->center2 ); + + if ( separation1 < separation2 ) + { + return (b3FaceQuery){ + .separation = separation1, + .faceIndex = 0, + .vertexIndex = 0, + }; + } + + return (b3FaceQuery){ + .separation = separation2, + .faceIndex = 0, + .vertexIndex = 1, + }; +} + +static b3EdgeQuery b3QueryTriangleAndCapsuleEdges( const b3Vec3* vertices, const b3Capsule* capsule ) +{ + // Work in the local space of the capsule + b3Vec3 p1 = capsule->center1; + b3Vec3 p2 = capsule->center2; + b3Vec3 capsuleEdge = b3Sub( p2, p1 ); + + b3Vec3 capsuleCenter = b3Lerp( p1, p2, 0.5f ); + + b3Vec3 triangleCenter = b3MulSV( 1.0f / 3.0f, b3Add( vertices[0], b3Add( vertices[1], vertices[2] ) ) ); + + // Find axis of minimum penetration + float maxSeparation = -FLT_MAX; + int maxIndex1 = UINT8_MAX; + int maxIndex2 = UINT8_MAX; + + int edgeIndex = 2; + b3Vec3 v1 = vertices[2]; + for ( int index = 0; index < 3; ++index ) + { + b3Vec3 v2 = vertices[index]; + + b3Vec3 triangleEdge = b3Sub( v2, v1 ); + + float separation = b3EdgeEdgeSeparation( p1, capsuleEdge, capsuleCenter, v1, triangleEdge, triangleCenter ); + if ( separation > maxSeparation ) + { + // Note: We don't exit early if we find a separating axis here since we want to + // find the best one for caching and account for the convex radius later. + maxSeparation = separation; + maxIndex1 = edgeIndex; + maxIndex2 = 0; + } + + v1 = v2; + edgeIndex = index; + } + + // Save result + return (b3EdgeQuery){ + .separation = maxSeparation, + .indexA = (uint8_t)maxIndex1, + .indexB = (uint8_t)maxIndex2, + }; +} + +static void b3BuildTriangleAndCapsuleFaceContact( b3LocalManifold* manifold, const b3Vec3* triangle, b3Plane plane, + const b3Capsule* capsule ) +{ + B3_ASSERT( manifold->pointCount == 0 ); + + b3ClipVertex segment[2]; + segment[0].position = capsule->center1; + segment[0].separation = 0.0f; + segment[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 ); + segment[1].position = capsule->center2; + segment[1].separation = 0.0f; + segment[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 ); + + bool havePoints = b3ClipSegmentToTriangleFace( segment, triangle, plane ); + if ( havePoints == false ) + { + return; + } + + float radius = capsule->radius; + float distance1 = b3PlaneSeparation( plane, segment[0].position ); + float distance2 = b3PlaneSeparation( plane, segment[1].position ); + + float speculativeDistance = B3_SPECULATIVE_DISTANCE; + if ( distance1 > speculativeDistance + radius && distance2 > speculativeDistance + radius ) + { + return; + } + + // Average points. Half-way between capsule bottom and triangle plane. + b3Vec3 point1 = b3MulSub( segment[0].position, 0.5f * ( distance1 + capsule->radius ), plane.normal ); + b3Vec3 point2 = b3MulSub( segment[1].position, 0.5f * ( distance2 + capsule->radius ), plane.normal ); + + manifold->normal = plane.normal; + manifold->feature = b3_featureTriangleFace; + manifold->pointCount = 2; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point1; + pt->separation = distance1 - capsule->radius; + pt->pair = segment[0].pair; + + pt = manifold->points + 1; + pt->point = point2; + pt->separation = distance2 - capsule->radius; + pt->pair = segment[1].pair; +} + +static void b3BuildTriangleAndCapsuleEdgeContact( b3LocalManifold* manifold, const b3Vec3* triangle, const b3Capsule* capsule, + b3EdgeQuery query ) +{ + B3_ASSERT( 0 <= query.indexA && query.indexA < 3 ); + + b3Vec3 p1 = capsule->center1; + b3Vec3 p2 = capsule->center2; + b3Vec3 capsuleEdge = b3Sub( p2, p1 ); + + const b3Vec3* vs = triangle; + + b3Vec3 triangleCenter = b3MulSV( 1.0f / 3.0f, b3Add( vs[0], b3Add( vs[1], vs[2] ) ) ); + b3Vec3 v1 = vs[query.indexA]; + b3Vec3 v2 = vs[( query.indexA + 1 ) % 3]; + b3Vec3 triangleEdge = b3Sub( v2, v1 ); + + b3Vec3 normal = b3Cross( capsuleEdge, triangleEdge ); + normal = b3Normalize( normal ); + + // Normal should point away from triangle center + if ( b3Dot( normal, b3Sub( v1, triangleCenter ) ) < 0.0f ) + { + normal = b3Neg( normal ); + } + + b3SegmentDistanceResult result = b3LineDistance( v1, triangleEdge, p1, capsuleEdge ); + + if ( result.fraction1 < 0.0f || 1.0f < result.fraction1 || result.fraction2 < 0.0f || 1.0f < result.fraction2 ) + { + // closest point beyond end points + return; + } + + b3Vec3 point = b3Lerp( b3MulSub( result.point1, capsule->radius, normal ), result.point2, 0.5f ); + + float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); + B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); + + manifold->normal = normal; + manifold->pointCount = 1; + + b3TriangleFeature edgesFeatures[] = { b3_featureEdge1, b3_featureEdge2, b3_featureEdge3 }; + manifold->feature = edgesFeatures[query.indexA]; + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = separation - capsule->radius; + pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB ); +} + +void b3CollideCapsuleAndTriangle( b3LocalManifold* manifold, int capacity, const b3Capsule* capsuleA, const b3Vec3* triangleB, + b3SimplexCache* cache ) +{ + manifold->pointCount = 0; + + if ( capacity < 2 ) + { + return; + } + + b3Vec3 v1 = triangleB[0], v2 = triangleB[1], v3 = triangleB[2]; + b3Plane plane = b3MakePlaneFromPoints( v1, v2, v3 ); + b3Vec3 capsuleCenter = b3Lerp( capsuleA->center1, capsuleA->center2, 0.5f ); + + float offset = b3PlaneSeparation( plane, capsuleCenter ); + if ( offset < 0.0f ) + { + // Cull back side collision + return; + } + + b3DistanceInput distanceInput; + distanceInput.proxyA = (b3ShapeProxy){ triangleB, 3, 0.0f }; + distanceInput.proxyB = (b3ShapeProxy){ &capsuleA->center1, 2, 0.0f }; + distanceInput.transform = b3Transform_identity; + distanceInput.useRadii = false; + + b3DistanceOutput distanceOutput = b3ShapeDistance( &distanceInput, cache, NULL, 0 ); + + float radius = capsuleA->radius; + if ( distanceOutput.distance > radius + B3_SPECULATIVE_DISTANCE ) + { + // Shapes are separated, persist the cache + return; + } + + if ( distanceOutput.distance > 100.0f * FLT_EPSILON ) + { + // Shallow penetration + b3Vec3 delta = b3Normalize( b3Sub( distanceOutput.pointB, distanceOutput.pointA ) ); + + // Try to create two contact points if closest points difference is nearly parallel to face normal + const float kTolerance = 0.2f; + float cosAngle = b3AbsFloat( b3Dot( plane.normal, delta ) ); + if ( cosAngle > kTolerance ) + { + // Clip capsule segment against side planes of reference face + b3ClipVertex segment[2]; + segment[0].position = capsuleA->center1; + segment[0].separation = 0.0f; + segment[0].pair = b3MakeFeaturePair( b3_featureShapeA, 0, b3_featureShapeA, 0 ); + segment[1].position = capsuleA->center2; + segment[1].separation = 0.0f; + segment[1].pair = b3MakeFeaturePair( b3_featureShapeA, 1, b3_featureShapeA, 1 ); + + bool havePoints = b3ClipSegmentToTriangleFace( segment, triangleB, plane ); + + if ( havePoints == true ) + { + float distance1 = b3PlaneSeparation( plane, segment[0].position ); + float distance2 = b3PlaneSeparation( plane, segment[1].position ); + + b3Vec3 normal = plane.normal; + b3Vec3 point1 = b3MulSub( segment[0].position, 0.5f * ( radius + distance1 ), normal ); + b3Vec3 point2 = b3MulSub( segment[1].position, 0.5f * ( radius + distance2 ), normal ); + + manifold->normal = normal; + manifold->feature = b3_featureTriangleFace; + manifold->pointCount = 2; + + b3LocalManifoldPoint* mp = manifold->points + 0; + mp->point = point1; + mp->separation = distance1 - radius; + mp->pair = segment[0].pair; + + mp = manifold->points + 1; + mp->point = point2; + mp->separation = distance2 - radius; + mp->pair = segment[1].pair; + + return; + } + } + + // Create contact from closest points + b3Vec3 point = b3MulSV( 0.5f, b3Add( b3Sub( distanceOutput.pointA, b3MulSV( radius, delta ) ), distanceOutput.pointB ) ); + + manifold->normal = delta; + manifold->pointCount = 1; + manifold->feature = b3GetTriangleFeature( cache ); + + b3LocalManifoldPoint* mp = manifold->points + 0; + mp->point = point; + mp->separation = distanceOutput.distance - radius; + mp->pair = b3FeaturePair_single; + + return; + } + + // Deep penetration + + b3FaceQuery faceQuery = b3QueryTriangleFaceAndCapsule( plane, capsuleA ); + if ( faceQuery.separation > radius ) + { + // Shapes are separated + return; + } + + b3EdgeQuery edgeQuery = b3QueryTriangleAndCapsuleEdges( triangleB, capsuleA ); + if ( edgeQuery.separation > radius ) + { + // Shapes are separated + return; + } + + // Create face contact + float faceSeparation = faceQuery.separation - radius; + b3BuildTriangleAndCapsuleFaceContact( manifold, triangleB, plane, capsuleA ); + if ( manifold->pointCount == 2 ) + { + faceSeparation = b3MinFloat( manifold->points[0].separation, manifold->points[1].separation ); + } + B3_VALIDATE( faceSeparation <= 0.0f ); + + // Face contact can be empty if it does not realize the axis of minimum penetration. + // Create edge contact if face contact fails or edge contact is significantly better! + const float kRelEdgeTolerance = 0.50f; + const float kAbsTolerance = 1.0f * B3_LINEAR_SLOP; + float edgeSeparation = edgeQuery.separation - radius; + if ( manifold->pointCount == 0 || edgeSeparation > kRelEdgeTolerance * faceSeparation + kAbsTolerance ) + { + // Edge contact + b3BuildTriangleAndCapsuleEdgeContact( manifold, triangleB, capsuleA, edgeQuery ); + } +} + +static inline int b3GetTriangleSupport( b3Vec3* points, b3Vec3 direction ) +{ + int index = 0; + float distance = b3Dot( points[0], direction ); + + float d = b3Dot( points[1], direction ); + if ( d > distance ) + { + distance = d; + index = 1; + } + + d = b3Dot( points[2], direction ); + if ( d > distance ) + { + return 2; + } + + return index; +} + +static b3FaceQuery b3QueryTriangleFace( const b3TriangleData* triangle, const b3HullData* hull ) +{ + const b3Vec3* hullPoints = b3GetHullPoints( hull ); + b3Plane plane = triangle->plane; + int vertexIndex = b3FindHullSupportVertex( hull, b3Neg( plane.normal ) ); + b3Vec3 support = hullPoints[vertexIndex]; + float separation = b3PlaneSeparation( plane, support ); + + return (b3FaceQuery){ + .separation = separation, + .faceIndex = 0, + .vertexIndex = (uint8_t)vertexIndex, + }; +} + +static b3FaceQuery b3QueryHullFace( const b3TriangleData* triangle, const b3HullData* hull ) +{ + const b3Plane* hullPlanes = b3GetHullPlanes( hull ); + int faceCount = hull->faceCount; + + b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 }; + + int maxFaceIndex = -1; + int maxVertexIndex = -1; + float maxFaceSeparation = -FLT_MAX; + + for ( int faceIndex = 0; faceIndex < faceCount; ++faceIndex ) + { + b3Plane plane = hullPlanes[faceIndex]; + + int vertexIndex = b3GetTriangleSupport( trianglePoints, b3Neg( plane.normal ) ); + b3Vec3 support = trianglePoints[vertexIndex]; + float separation = b3PlaneSeparation( plane, support ); + if ( separation > maxFaceSeparation ) + { + maxFaceIndex = faceIndex; + maxVertexIndex = vertexIndex; + maxFaceSeparation = separation; + } + } + + return (b3FaceQuery){ + .separation = maxFaceSeparation, + .faceIndex = maxFaceIndex, + .vertexIndex = maxVertexIndex, + }; +} + +static b3EdgeQuery b3TestEdgePairs( const b3TriangleData* triangle, const b3HullData* hull ) +{ + b3EdgeQuery result = { + .separation = -FLT_MAX, + .indexA = B3_NULL_INDEX, + .indexB = B3_NULL_INDEX, + }; + + b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 }; + b3Vec3 triangleEdges[] = { triangle->e1, triangle->e2, triangle->e3 }; + // int edgeFlags[] = { b3_concaveEdge1, b3_concaveEdge1, b3_concaveEdge3 }; + +#if B3_FORCE_GHOST_COLLISIONS + int triangleFlags = 0xFF; +#else + int triangleFlags = triangle->flags; +#endif + (void)triangleFlags; + + b3Vec3 triNormal = triangle->plane.normal; + + const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull ); + const b3Vec3* hullPoints = b3GetHullPoints( hull ); + const b3Plane* hullPlanes = b3GetHullPlanes( hull ); + int edgeCount = hull->edgeCount; + + for ( int i = 0; i < edgeCount; i += 2 ) + { + const b3HullHalfEdge* edge = hullEdges + i; + const b3HullHalfEdge* twin = hullEdges + i + 1; + B3_ASSERT( edge->twin == i + 1 && twin->twin == i ); + + b3Vec3 hullPoint = hullPoints[edge->origin]; + b3Vec3 hullEdge = b3Sub( hullPoints[twin->origin], hullPoint ); + + b3Vec3 hullNormal1 = hullPlanes[edge->face].normal; + b3Vec3 hullNormal2 = hullPlanes[twin->face].normal; + + for ( int j = 0; j < 3; ++j ) + { + b3Vec3 triEdge = triangleEdges[j]; + + float cab = b3Dot( hullNormal1, triEdge ); + float dab = b3Dot( hullNormal2, triEdge ); + float bcd = b3Dot( triNormal, hullEdge ); + if ( cab * dab >= 0.0f || cab * bcd <= 0.0f ) + { + continue; + } + + b3Vec3 triPoint = trianglePoints[j]; + float separation = b3EdgeEdgeSeparation( triPoint, triEdge, triangle->center, hullPoint, hullEdge, hull->center ); + + // if ( separation > result.separation && ( edgeFlags[j] & triangleFlags ) == 0 ) + if ( separation > result.separation ) + { + // Note: We don't exit early if we find a separating axis here since we want to + // find the best one for caching. + result.separation = separation; + result.indexA = j; + result.indexB = i; + } + } + } + + return result; +} + +static float b3CollideHullFace( b3LocalManifold* manifold, int pointCapacity, const b3TriangleData* triangle, + const b3HullData* hull, b3FaceQuery query, b3SATCache* cache, bool enableSpeculative ) +{ + manifold->pointCount = 0; + + const b3HullFace* hullFaces = b3GetHullFaces( hull ); + const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull ); + const b3Plane* hullPlanes = b3GetHullPlanes( hull ); + const b3Vec3* hullPoints = b3GetHullPoints( hull ); + + // Reference hull face + int refFace = query.faceIndex; + b3Plane refPlane = hullPlanes[refFace]; + + // Build clip polygon from triangle face (the incident face) + b3ClipVertex buffer1[B3_MAX_CLIP_POINTS], buffer2[B3_MAX_CLIP_POINTS]; + + b3Vec3 v1 = triangle->v1; + b3Vec3 v2 = triangle->v2; + b3Vec3 v3 = triangle->v3; + buffer1[0].position = v1; + buffer1[0].separation = b3PlaneSeparation( refPlane, v1 ); + buffer1[0].pair = b3MakeFeaturePair( b3_featureShapeB, 2, b3_featureShapeB, 0 ); + buffer1[1].position = v2; + buffer1[1].separation = b3PlaneSeparation( refPlane, v2 ); + buffer1[1].pair = b3MakeFeaturePair( b3_featureShapeB, 0, b3_featureShapeB, 1 ); + buffer1[2].position = v3; + buffer1[2].separation = b3PlaneSeparation( refPlane, v3 ); + buffer1[2].pair = b3MakeFeaturePair( b3_featureShapeB, 1, b3_featureShapeB, 2 ); + int pointCount = 3; + + // Clip triangle face against side planes of reference face + b3ClipVertex* input = buffer1; + b3ClipVertex* output = buffer2; + + const b3HullFace* face = hullFaces + refFace; + int edgeIndex = face->edge; + + do + { + const b3HullHalfEdge* edge = hullEdges + edgeIndex; + int nextEdgeIndex = edge->next; + const b3HullHalfEdge* next = hullEdges + nextEdgeIndex; + b3Vec3 vertex1 = hullPoints[edge->origin]; + b3Vec3 vertex2 = hullPoints[next->origin]; + b3Vec3 tangent = b3Normalize( b3Sub( vertex2, vertex1 ) ); + b3Vec3 binormal = b3Cross( tangent, refPlane.normal ); + + b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( binormal, vertex1 ); + + pointCount = b3ClipPolygon( output, input, pointCount, clipPlane, edgeIndex, refPlane ); + B3_ASSERT( pointCount <= B3_MAX_CLIP_POINTS ); + + if ( pointCount < 3 ) + { + // Using a stale cache + *cache = (b3SATCache){ 0 }; + return query.separation; + } + + // Swap buffers, output becomes input for the next clipping plane + B3_SWAP( output, input ); + edgeIndex = nextEdgeIndex; + } + while ( edgeIndex != face->edge ); + + pointCount = b3MinInt( pointCount, pointCapacity ); + float minSeparation = FLT_MAX; + int finalPointCount = 0; + + for ( int i = 0; i < pointCount; ++i ) + { + b3ClipVertex* clipPoint = input + i; + minSeparation = b3MinFloat( minSeparation, clipPoint->separation ); + + if ( enableSpeculative == false && clipPoint->separation > 0.0f ) + { + continue; + } + + // Move point onto hull face improved culling + b3Vec3 point = b3MulSub( clipPoint->position, clipPoint->separation, refPlane.normal ); + + b3LocalManifoldPoint* pt = manifold->points + finalPointCount; + pt->point = point; + pt->separation = clipPoint->separation; + pt->pair = b3FlipPair( clipPoint->pair ); + + finalPointCount += 1; + } + + float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f; + if ( minSeparation > speculativeDistance ) + { + // This can occur with a stale SAT cache + manifold->pointCount = 0; + *cache = (b3SATCache){ 0 }; + return minSeparation; + } + + manifold->pointCount = finalPointCount; + manifold->normal = b3Neg( refPlane.normal ); + manifold->feature = b3_featureHullFace; + + // Save cache + cache->separation = minSeparation; + cache->type = b3_faceAxisB; + cache->indexA = (uint8_t)query.vertexIndex; + cache->indexB = (uint8_t)query.faceIndex; + return minSeparation; +} + +static float b3CollideTriangleFace( b3LocalManifold* manifold, int pointCapacity, const b3TriangleData* triangle, + const b3HullData* hull, b3FaceQuery query, b3SATCache* cache, bool enableSpeculative ) +{ + B3_VALIDATE( manifold->pointCount == 0 ); + + const b3HullFace* hullFaces = b3GetHullFaces( hull ); + const b3HullHalfEdge* hullEdges = b3GetHullEdges( hull ); + const b3Vec3* hullPoints = b3GetHullPoints( hull ); + + // Find incident face + B3_ASSERT( query.faceIndex == 0 ); + b3Plane refPlane = triangle->plane; + + int incFace = b3FindIncidentFace( hull, refPlane.normal, query.vertexIndex ); + + // Build clip polygon from incident face + b3ClipVertex buffer1[2 * B3_MAX_CLIP_POINTS], buffer2[2 * B3_MAX_CLIP_POINTS]; + int pointCount = 0; + const b3HullFace* face = hullFaces + incFace; + int hullEdgeIndex = face->edge; + + do + { + const b3HullHalfEdge* edge = hullEdges + hullEdgeIndex; + + int nextEdgeIndex = edge->next; + const b3HullHalfEdge* next = hullEdges + nextEdgeIndex; + + b3Vec3 hullPoint = hullPoints[next->origin]; + buffer1[pointCount].position = hullPoint; + buffer1[pointCount].separation = b3PlaneSeparation( refPlane, hullPoint ); + buffer1[pointCount].pair = b3MakeFeaturePair( b3_featureShapeB, hullEdgeIndex, b3_featureShapeB, nextEdgeIndex ); + + pointCount += 1; + hullEdgeIndex = nextEdgeIndex; + } + while ( hullEdgeIndex != face->edge && pointCount < 2 * B3_MAX_CLIP_POINTS ); + + B3_ASSERT( pointCount >= 3 ); + + // Clip incident face against side planes of reference face (triangle) + b3ClipVertex* input = buffer1; + b3ClipVertex* output = buffer2; + + b3Vec3 trianglePoints[] = { triangle->v1, triangle->v2, triangle->v3 }; + b3Vec3 triangleEdges[] = { triangle->e1, triangle->e2, triangle->e3 }; + + for ( int i = 0; i < 3 && pointCount > 0; ++i ) + { + b3Vec3 sideNormal = b3Cross( triangleEdges[i], refPlane.normal ); + sideNormal = b3Normalize( sideNormal ); + + b3Plane clipPlane = b3MakePlaneFromNormalAndPoint( sideNormal, trianglePoints[i] ); + + pointCount = b3ClipPolygon( output, input, pointCount, clipPlane, i, refPlane ); + B3_ASSERT( pointCount <= 2 * B3_MAX_CLIP_POINTS ); + + B3_SWAP( output, input ); + } + + if ( pointCount == 0 ) + { + // Triangle face clipped away. Invalidate cache. + *cache = (b3SATCache){ 0 }; + return FLT_MAX; + } + + pointCount = b3MinInt( pointCount, pointCapacity ); + + float minSeparation = FLT_MAX; + + int finalPointCount = 0; + for ( int i = 0; i < pointCount; ++i ) + { + b3ClipVertex* clipPoint = input + i; + minSeparation = b3MinFloat( minSeparation, clipPoint->separation ); + + if ( enableSpeculative == false && clipPoint->separation > 0.0f ) + { + continue; + } + + // Move point onto triangle surface for improved culling + // b3Vec3 point = b3MulSub( clipPoint->position, clipPoint->separation, refPlane.normal ); + b3Vec3 point = clipPoint->position; + + b3LocalManifoldPoint* pt = manifold->points + finalPointCount; + pt->point = point; + pt->separation = clipPoint->separation; + pt->pair = clipPoint->pair; + + finalPointCount += 1; + } + + float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f; + if ( minSeparation >= speculativeDistance ) + { + // This can happens if the objects move a part while re-using a cached axis + *cache = (b3SATCache){ 0 }; + return minSeparation; + } + + manifold->pointCount = finalPointCount; + manifold->normal = refPlane.normal; + manifold->feature = b3_featureTriangleFace; + + // Save cache + cache->separation = minSeparation; + cache->type = b3_faceAxisA; + cache->indexA = (uint8_t)query.faceIndex; + cache->indexB = (uint8_t)query.vertexIndex; + return minSeparation; +} + +static void b3CollideHullAndTriangleEdges( b3LocalManifold* manifold, int capacity, b3Vec3 trianglePoint, b3Vec3 triangleEdge, + b3Vec3 triangleCenter, const b3HullData* hull, b3EdgeQuery query, b3SATCache* cache ) +{ + B3_VALIDATE( query.separation <= 2.0f * B3_SPECULATIVE_DISTANCE ); + B3_ASSERT( query.indexA < 3 ); + + b3Vec3 cA = triangleCenter; + b3Vec3 pA = trianglePoint; + b3Vec3 eA = triangleEdge; + + const b3HullHalfEdge* edgesB = b3GetHullEdges( hull ); + const b3Vec3* pointsB = b3GetHullPoints( hull ); + const b3HullHalfEdge* edgeB = edgesB + query.indexB; + const b3HullHalfEdge* twinB = edgesB + edgeB->twin; + b3Vec3 pB = pointsB[edgeB->origin]; + b3Vec3 qB = pointsB[twinB->origin]; + b3Vec3 eB = b3Sub( qB, pB ); + + b3Vec3 normal = b3Cross( eA, eB ); + normal = b3Normalize( normal ); + + // Ensure normal points outward from triangle center + float outwardA = b3Dot( normal, b3Sub( pA, cA ) ); + + // Ensure normal points towards hull center + float outwardB = b3Dot( normal, b3Sub( hull->center, pB ) ); + + // Use the largest magnitude. The triangle outward value + // may be unreliable as some angles. + if ( b3AbsFloat( outwardA ) > b3AbsFloat( outwardB ) ) + { + if ( outwardA < 0.0f ) + { + normal = b3Neg( normal ); + } + } + else + { + if ( outwardB < 0.0f ) + { + normal = b3Neg( normal ); + } + } + + // Get the closest points between the infinite edge lines + b3SegmentDistanceResult result = b3LineDistance( pA, eA, pB, eB ); + + // Is one of the closest points outside of the associated edge segment? + if ( capacity == 0 || result.fraction1 < 0.0f || 1.0f < result.fraction1 || result.fraction2 < 0.0f || + 1.0f < result.fraction2 ) + { + // Invalid edge pair, no points generated + B3_ASSERT( manifold->pointCount == 0 ); + *cache = (b3SATCache){ 0 }; + return; + } + + // This can slide off the end from caching + float separation = b3Dot( normal, b3Sub( result.point2, result.point1 ) ); + B3_VALIDATE( b3AbsFloat( separation - query.separation ) < B3_LINEAR_SLOP ); + + b3Vec3 point = b3MulSV( 0.5f, b3Add( result.point1, result.point2 ) ); + + b3LocalManifoldPoint* pt = manifold->points + 0; + pt->point = point; + pt->separation = separation; + pt->pair = b3MakeFeaturePair( b3_featureShapeA, query.indexA, b3_featureShapeB, query.indexB ); + + // Save cache + cache->separation = separation; + cache->type = b3_edgePairAxis; + cache->indexA = (uint8_t)query.indexA; + cache->indexB = (uint8_t)query.indexB; + + manifold->normal = normal; + manifold->pointCount = 1; + + b3TriangleFeature edgesFeatures[] = { b3_featureEdge1, b3_featureEdge2, b3_featureEdge3 }; + manifold->feature = edgesFeatures[query.indexA]; +} + +// See "Collision Detection of Convex Polyhedra Based on Duality Transformation" +// Simplified for triangle versus hull +static inline bool b3IsTriangleMinkowskiFace( b3Vec3 triNormal, b3Vec3 triEdge, b3Vec3 hullNormal1, b3Vec3 hullNormal2, + b3Vec3 hullEdge ) +{ + float cab = b3Dot( hullNormal1, triEdge ); + float dab = b3Dot( hullNormal2, triEdge ); + float bcd = b3Dot( triNormal, hullEdge ); + return cab * dab < 0.0f && cab * bcd > 0.0f; +} + +b3AtomicInt b3_triangleConvexCalls; +b3AtomicInt b3_triangleCacheHits; + +// Computes the manifold in the local space of the hull +void b3CollideHullAndTriangle( b3LocalManifold* manifold, int capacity, const b3HullData* hullA, b3Vec3 v1, b3Vec3 v2, b3Vec3 v3, + int triangleFlags, b3SATCache* cache, bool enableSpeculative ) +{ + manifold->pointCount = 0; + manifold->feature = b3_featureNone; + + if ( capacity < 4 ) + { + return; + } + + b3Plane trianglePlane = b3MakePlaneFromPoints( v1, v2, v3 ); + float linearSlop = B3_LINEAR_SLOP; + + float offset = b3PlaneSeparation( trianglePlane, hullA->center ); + if ( cache->type == b3_backsideAxis ) + { + // Use hysteresis to avoid jitter on wavy meshes + if ( b3AbsFloat( cache->separation - offset ) < linearSlop ) + { + return; + } + + cache->type = b3_invalidAxis; + } + + if ( offset < -linearSlop ) + { + // Cull back side collision. Cache offset to add hysteresis. + cache->type = b3_backsideAxis; + cache->separation = offset; + return; + } + + b3Vec3 triangleCenter = b3MulSV( 1.0f / 3.0f, b3Add( v1, b3Add( v2, v3 ) ) ); + b3Vec3 trianglePoints[] = { v1, v2, v3 }; + b3Vec3 triangleEdges[] = { b3Sub( v2, v1 ), b3Sub( v3, v2 ), b3Sub( v1, v3 ) }; + + b3TriangleData triangle = { + .v1 = v1, + .v2 = v2, + .v3 = v3, + .e1 = triangleEdges[0], + .e2 = triangleEdges[1], + .e3 = triangleEdges[2], + .center = triangleCenter, + .plane = trianglePlane, + .flags = triangleFlags, + }; + + const b3HullHalfEdge* edges = b3GetHullEdges( hullA ); + const b3Plane* hullPlanes = b3GetHullPlanes( hullA ); + const b3Vec3* hullPoints = b3GetHullPoints( hullA ); + + float speculativeDistance = enableSpeculative ? B3_SPECULATIVE_DISTANCE : 0.0f; + cache->hit = 1; + + // Attempt to use the cache to speed up collision + switch ( cache->type ) + { + case b3_faceAxisA: + { + B3_ASSERT( cache->indexA == 0 ); + + int vertexIndex = b3FindHullSupportVertex( hullA, b3Neg( trianglePlane.normal ) ); + b3Vec3 support = hullPoints[vertexIndex]; + float separation = b3PlaneSeparation( trianglePlane, support ); + if ( separation > speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } + + b3FaceQuery faceQuery; + faceQuery.separation = separation; + faceQuery.faceIndex = cache->indexA; + faceQuery.vertexIndex = vertexIndex; + + // Read cache but don't modify it + b3SATCache localCache = *cache; + float clippedSeparation = + b3CollideTriangleFace( manifold, capacity, &triangle, hullA, faceQuery, &localCache, enableSpeculative ); + + if ( manifold->pointCount > 0 && b3AbsFloat( cache->separation - clippedSeparation ) < linearSlop ) + { + // Cache hit, contact points generated + return; + } + + // Invalidate cache and fall through + manifold->pointCount = 0; + *cache = (b3SATCache){ 0 }; + } + break; + + case b3_faceAxisB: + { + B3_ASSERT( cache->indexB < hullA->faceCount ); + + b3Plane plane = hullPlanes[cache->indexB]; + + // Get triangle support point + int vertexIndex = 0; + float distance = -b3Dot( v1, plane.normal ); + for ( int i = 1; i < 3; ++i ) + { + float d = -b3Dot( trianglePoints[i], plane.normal ); + if ( d > distance ) + { + distance = d; + vertexIndex = i; + } + } + + b3Vec3 support = trianglePoints[vertexIndex]; + + // Separation of triangle support point with hull plane + float separation = b3PlaneSeparation( plane, support ); + if ( separation > speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } + + // Deep overlap may lead to an invalid cache + // todo confirm + bool isDeep = separation < -2.0f * linearSlop; + + // Don't persist deep cache or allow separation to change too much + if ( isDeep == false ) + { + // Try to rebuild contact from last features + b3FaceQuery faceQuery; + faceQuery.separation = separation; + faceQuery.faceIndex = cache->indexB; + faceQuery.vertexIndex = vertexIndex; + + // Read cache but don't modify it + b3SATCache localCache = *cache; + float clippedSeparation = + b3CollideHullFace( manifold, capacity, &triangle, hullA, faceQuery, &localCache, enableSpeculative ); + + // Cache reuse is only successful if it creates contact points and the clipped separation didn't change much. + if ( manifold->pointCount > 0 && b3AbsFloat( cache->separation - clippedSeparation ) < linearSlop ) + { + // Cache hit, contact points generated + return; + } + } + + // Invalidate cache and fall through + manifold->pointCount = 0; + *cache = (b3SATCache){ 0 }; + } + break; + + case b3_edgePairAxis: + { + B3_ASSERT( cache->indexA < 3 ); + int indexA = cache->indexA; + + b3Vec3 triPoint = trianglePoints[indexA]; + b3Vec3 triEdge = triangleEdges[indexA]; + + B3_ASSERT( cache->indexB < hullA->edgeCount - 1 ); + int indexB = cache->indexB; + + const b3HullHalfEdge* edge2 = edges + indexB; + const b3HullHalfEdge* twin2 = edges + indexB + 1; + B3_ASSERT( edge2->twin == indexB + 1 && twin2->twin == indexB ); + + b3Vec3 hullPoint = hullPoints[edge2->origin]; + b3Vec3 hullEdge = b3Sub( hullPoints[twin2->origin], hullPoint ); + b3Vec3 hullNormal1 = hullPlanes[edge2->face].normal; + b3Vec3 hullNormal2 = hullPlanes[twin2->face].normal; + + // Confirm the edge pair is still a Minkowski face + bool isMinkowski = b3IsTriangleMinkowskiFace( trianglePlane.normal, triEdge, hullNormal1, hullNormal2, hullEdge ); + if ( isMinkowski ) + { + // Transform reference center of the first hull into local space of the second hull + float separation = b3EdgeEdgeSeparation( triPoint, triEdge, triangleCenter, hullPoint, hullEdge, hullA->center ); + if ( separation > speculativeDistance ) + { + // Cache hit, shapes are separated + return; + } + + if ( b3AbsFloat( cache->separation - separation ) < linearSlop ) + { + // Try to rebuild contact from last features + b3EdgeQuery edgeQuery; + edgeQuery.indexA = indexA; + edgeQuery.indexB = indexB; + edgeQuery.separation = separation; + + // Read cache but don't modify it + b3SATCache localCache = *cache; + b3CollideHullAndTriangleEdges( manifold, capacity, triPoint, triEdge, triangleCenter, hullA, edgeQuery, + &localCache ); + + if ( manifold->pointCount > 0 ) + { + // Cache hit, contact point generated + return; + } + } + } + + // Invalidate cache and fall through + *cache = (b3SATCache){ 0 }; + } + break; + + // This case is for testing + case b3_manualFaceAxisA: + { + b3FaceQuery faceQueryA = b3QueryTriangleFace( &triangle, hullA ); + b3CollideTriangleFace( manifold, capacity, &triangle, hullA, faceQueryA, cache, enableSpeculative ); + return; + } + + // This case is for testing + case b3_manualFaceAxisB: + { + b3FaceQuery faceQueryB = b3QueryHullFace( &triangle, hullA ); + b3CollideHullFace( manifold, capacity, &triangle, hullA, faceQueryB, cache, enableSpeculative ); + return; + } + + // This case is for testing + case b3_manualEdgePairAxis: + { + b3EdgeQuery edgeQuery = b3TestEdgePairs( &triangle, hullA ); + if ( edgeQuery.indexA != B3_NULL_INDEX ) + { + b3Vec3 trianglePoint = trianglePoints[edgeQuery.indexA]; + b3Vec3 triangleEdge = triangleEdges[edgeQuery.indexA]; + b3CollideHullAndTriangleEdges( manifold, capacity, trianglePoint, triangleEdge, triangleCenter, hullA, edgeQuery, + cache ); + } + return; + } + + default: + B3_ASSERT( cache->type == b3_invalidAxis ); + break; + } + + // Cache miss + cache->hit = 0; + + // Find axis of minimum penetration + b3FaceQuery faceQueryA = b3QueryTriangleFace( &triangle, hullA ); + if ( faceQueryA.separation > speculativeDistance ) + { + // Separating axis found + cache->separation = faceQueryA.separation; + cache->type = b3_faceAxisA; + cache->indexA = 0; + cache->indexB = UINT8_MAX; + return; + } + + b3FaceQuery faceQueryB = b3QueryHullFace( &triangle, hullA ); + if ( faceQueryB.separation > speculativeDistance ) + { + // Separating axis found + cache->separation = faceQueryB.separation; + cache->type = b3_faceAxisB; + cache->indexA = UINT8_MAX; + cache->indexB = (uint8_t)faceQueryB.faceIndex; + return; + } + + b3EdgeQuery edgeQuery = b3TestEdgePairs( &triangle, hullA ); + if ( edgeQuery.separation > speculativeDistance ) + { + // Separating axis found + cache->separation = edgeQuery.separation; + cache->type = b3_edgePairAxis; + cache->indexA = (uint8_t)edgeQuery.indexA; + cache->indexB = (uint8_t)edgeQuery.indexB; + return; + } + + float clippedFaceSeparation; + + // Don't admit a hull face significantly opposed to the triangle face. + // Need a tolerance to avoid ghost collisions. + // todo hull query skips faces that point along the triangle normal + b3Vec3 hullNormal = hullPlanes[faceQueryB.faceIndex].normal; + bool pushingDown = b3Dot( hullNormal, trianglePlane.normal ) > 0.25f; + if ( faceQueryB.separation > faceQueryA.separation + linearSlop && pushingDown == false ) + { + clippedFaceSeparation = b3CollideHullFace( manifold, capacity, &triangle, hullA, faceQueryB, cache, enableSpeculative ); + } + else + { + clippedFaceSeparation = + b3CollideTriangleFace( manifold, capacity, &triangle, hullA, faceQueryA, cache, enableSpeculative ); + } + + // Does an edge axis exist? + if ( edgeQuery.indexA != B3_NULL_INDEX ) + { + // When axes are aligned the edge separation can be garbage. + // If a face axis has positive separation there may be no points. + float maxFaceSeparation = b3MaxFloat( faceQueryA.separation, faceQueryB.separation ); + + if ( ( manifold->pointCount == 0 && edgeQuery.separation > maxFaceSeparation ) || + ( manifold->pointCount == 1 && edgeQuery.separation > clippedFaceSeparation + linearSlop ) ) + { + B3_ASSERT( 0 <= edgeQuery.indexA && edgeQuery.indexA < 3 ); + b3Vec3 trianglePoint = trianglePoints[edgeQuery.indexA]; + b3Vec3 triangleEdge = triangleEdges[edgeQuery.indexA]; + manifold->pointCount = 0; + b3CollideHullAndTriangleEdges( manifold, capacity, trianglePoint, triangleEdge, triangleCenter, hullA, edgeQuery, + cache ); + } + } + + // Using the speculative distance means that sometimes there are no valid contact points from SAT. + // In this fall back to GJK. This is important to prevent tunneling in rare cases. + if ( manifold->pointCount == 0 ) + { + b3Vec3 triangleB[] = { v1, v2, v3 }; + b3DistanceInput input = { 0 }; + input.proxyA = (b3ShapeProxy){ + .points = triangleB, + .count = 3, + .radius = 0.0f, + }; + input.proxyB = (b3ShapeProxy){ .points = hullPoints, .count = hullA->vertexCount, .radius = 0.0f }; + input.transform = b3Transform_identity; + input.useRadii = false; + + b3SimplexCache simplexCache = { 0 }; + b3DistanceOutput output = b3ShapeDistance( &input, &simplexCache, NULL, 0 ); + + if ( output.distance > 0.0f ) + { + B3_ASSERT( 0 < simplexCache.count && simplexCache.count <= 3 ); + + manifold->pointCount = 1; + manifold->feature = b3GetTriangleFeature( &simplexCache ); + manifold->normal = output.normal; + manifold->points[0].point = output.pointB; + manifold->points[0].separation = output.distance; + + // This feature pair not accurate but maybe it doesn't matter + manifold->points[0].pair = b3FeaturePair_single; + } + } +} diff --git a/vendor/box3d/src/src/types.c b/vendor/box3d/src/src/types.c new file mode 100644 index 000000000..b199cd15d --- /dev/null +++ b/vendor/box3d/src/src/types.c @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "box3d/types.h" + +#include "core.h" + +#include "box3d/constants.h" + +b3WorldDef b3DefaultWorldDef( void ) +{ + float lengthUnits = b3GetLengthUnitsPerMeter(); + + b3WorldDef def = { 0 }; + def.gravity.x = 0.0f; + def.gravity.y = -10.0f; + def.hitEventThreshold = 1.0f * lengthUnits; + def.restitutionThreshold = 1.0f * lengthUnits; + def.contactSpeed = 3.0f * lengthUnits; + def.contactHertz = 30.0f; + def.contactDampingRatio = 10.0f; + + // 400 meters per second, faster than the speed of sound + def.maximumLinearSpeed = 400.0f * lengthUnits; + + def.enableSleep = true; + def.enableContinuous = true; + def.internalValue = B3_SECRET_COOKIE; + return def; +} + +b3BodyDef b3DefaultBodyDef( void ) +{ + b3BodyDef def = { 0 }; + def.type = b3_staticBody; + def.rotation = b3Quat_identity; + def.sleepThreshold = 0.05f * b3GetLengthUnitsPerMeter(); + def.gravityScale = 1.0f; + def.enableSleep = true; + def.isAwake = true; + def.isEnabled = true; + def.enableContactRecycling = true; + def.internalValue = B3_SECRET_COOKIE; + return def; +} + +b3Filter b3DefaultFilter( void ) +{ + b3Filter filter = { B3_DEFAULT_CATEGORY_BITS, B3_DEFAULT_MASK_BITS, 0 }; + return filter; +} + +b3QueryFilter b3DefaultQueryFilter( void ) +{ + b3QueryFilter filter = { B3_DEFAULT_CATEGORY_BITS, B3_DEFAULT_MASK_BITS, 0, NULL }; + return filter; +} + +b3SurfaceMaterial b3DefaultSurfaceMaterial( void ) +{ + b3SurfaceMaterial surfaceMaterial = { 0 }; + surfaceMaterial.friction = 0.6f; + return surfaceMaterial; +} + +b3ShapeDef b3DefaultShapeDef( void ) +{ + float lengthUnits = b3GetLengthUnitsPerMeter(); + + b3ShapeDef def = { 0 }; + def.baseMaterial = b3DefaultSurfaceMaterial(); + // density of water + def.density = 1000.0f / ( lengthUnits * lengthUnits * lengthUnits ); + def.explosionScale = 1.0f; + def.filter = b3DefaultFilter(); + def.updateBodyMass = true; + def.invokeContactCreation = true; + def.enableSpeculativeContact = true; + def.internalValue = B3_SECRET_COOKIE; + return def; +} + +static bool b3EmptyDrawShape( void* userShape, b3WorldTransform transform, b3HexColor color, void* context ) +{ + B3_UNUSED( userShape, transform, color, context ); + return false; +} + +static void b3EmptyDrawSegment( b3Pos p1, b3Pos p2, b3HexColor color, void* context ) +{ + B3_UNUSED( p1, p2, color, context ); +} + +static void b3EmptyDrawTransform( b3WorldTransform transform, void* context ) +{ + B3_UNUSED( transform, context ); +} + +static void b3EmptyDrawPoint( b3Pos p, float size, b3HexColor color, void* context ) +{ + B3_UNUSED( p, size, color, context ); +} + +static void b3EmptyDrawSphere( b3Pos p, float radius, b3HexColor color, float alpha, void* context ) +{ + B3_UNUSED( p, radius, color, alpha, context ); +} + +static void b3EmptyDrawCapsule( b3Pos p1, b3Pos p2, float radius, b3HexColor color, float alpha, void* context ) +{ + B3_UNUSED( p1, p2, radius, color, alpha, context ); +} + +static void b3EmptyDrawBounds( b3AABB aabb, b3HexColor color, void* context ) +{ + B3_UNUSED( aabb, color, context ); +} + +static void b3EmptyDrawBox( b3Vec3 extents, b3WorldTransform transform, b3HexColor color, void* context ) +{ + B3_UNUSED( extents, transform, color, context ); +} + +static void b3EmptyDrawString( b3Pos p, const char* s, b3HexColor color, void* context ) +{ + B3_UNUSED( p, s, color, context ); +} + +b3DebugDraw b3DefaultDebugDraw( void ) +{ + b3DebugDraw draw = { 0 }; + + // These allow the user to skip some implementations and not hit null exceptions. + draw.DrawShapeFcn = b3EmptyDrawShape; + draw.DrawSegmentFcn = b3EmptyDrawSegment; + draw.DrawTransformFcn = b3EmptyDrawTransform; + draw.DrawPointFcn = b3EmptyDrawPoint; + draw.DrawSphereFcn = b3EmptyDrawSphere; + draw.DrawCapsuleFcn = b3EmptyDrawCapsule; + draw.DrawBoundsFcn = b3EmptyDrawBounds; + draw.DrawBoxFcn = b3EmptyDrawBox; + draw.DrawStringFcn = b3EmptyDrawString; + + // Not too small, not too big. + float h = 100.0f * b3GetLengthUnitsPerMeter(); + draw.drawingBounds = (b3AABB){ + .lowerBound = { -h, -h, -h }, + .upperBound = { h, h, h }, + }; + + draw.jointScale = 1.0f; + draw.forceScale = 1.0f; + + return draw; +} diff --git a/vendor/box3d/src/src/verstable.h b/vendor/box3d/src/src/verstable.h new file mode 100644 index 000000000..c8601e6d4 --- /dev/null +++ b/vendor/box3d/src/src/verstable.h @@ -0,0 +1,2069 @@ +/*------------------------------------------------- VERSTABLE v2.2.1 --------------------------------------------------- + +Verstable is a C99-compatible, open-addressing hash table using quadratic probing and the following additions: + +* All keys that hash (i.e. "belong") to the same bucket (their "home bucket") are linked together by an 11-bit integer + specifying the quadratic displacement, relative to that bucket, of the next key in the chain. + +* If a chain of keys exists for a given bucket, then it always begins at that bucket. To maintain this policy, a 1-bit + flag is used to mark whether the key occupying a bucket belongs there. When inserting a new key, if the bucket it + belongs to is occupied by a key that does not belong there, then the occupying key is evicted and the new key takes + the bucket. + +* A 4-bit fragment of each key's hash code is also stored. + +* The aforementioned metadata associated with each bucket (the 4-bit hash fragment, the 1-bit flag, and the 11-bit link + to the next key in the chain) are stored together in a uint16_t array rather than in the bucket alongside the key and + (optionally) the value. + +One way to conceptualize this scheme is as a chained hash table in which overflowing keys are stored not in separate +memory allocations but in otherwise unused buckets. In this regard, it shares similarities with Malte Skarupke's Bytell +hash table (https://www.youtube.com/watch?v=M2fKMP47slQ) and traditional "coalesced hashing". + +Advantages of this scheme include: + +* Fast lookups impervious to load factor: If the table contains any key belonging to the lookup key's home bucket, then + that bucket contains the first in a traversable chain of all keys belonging to it. Hence, only the home bucket and + other buckets containing keys belonging to it are ever probed. Moreover, the stored hash fragments allow skipping most + non-matching keys in the chain without accessing the actual buckets array or calling the (potentially expensive) key + comparison function. + +* Fast insertions: Insertions are faster than they are in other schemes that move keys around (e.g. Robin Hood) because + they only move, at most, one existing key. + +* Fast, tombstone-free deletions: Deletions, which usually require tombstones in quadratic-probing hash tables, are + tombstone-free and only move, at most, one existing key. + +* Fast iteration: The separate metadata array allows keys in sparsely populated tables to be found without incurring the + frequent cache misses that would result from traversing the buckets array. + +Usage example: + + +---------------------------------------------------------+----------------------------------------------------------+ + | Using the generic macro API (C11 and later): | Using the prefixed functions API (C99 and later): | + +---------------------------------------------------------+----------------------------------------------------------+ + | #include | #include | + | | | + | // Instantiating a set template. | // Instantiating a set template. | + | #define NAME int_set | #define NAME int_set | + | #define KEY_TY int | #define KEY_TY int | + | #include "verstable.h" | #define HASH_FN vt_hash_integer | + | | #define CMPR_FN vt_cmpr_integer | + | // Instantiating a map template. | #include "verstable.h" | + | #define NAME int_int_map | | + | #define KEY_TY int | // Instantiating a map template. | + | #define VAL_TY int | #define NAME int_int_map | + | #include "verstable.h" | #define KEY_TY int | + | | #define VAL_TY int | + | int main( void ) | #define HASH_FN vt_hash_integer | + | { | #define CMPR_FN vt_cmpr_integer | + | // Set. | #include "verstable.h" | + | | | + | int_set our_set; | int main( void ) | + | vt_init( &our_set ); | { | + | | // Set. | + | // Inserting keys. | | + | for( int i = 0; i < 10; ++i ) | int_set our_set; | + | { | int_set_init( &our_set ); | + | int_set_itr itr = vt_insert( &our_set, i ); | | + | if( vt_is_end( itr ) ) | // Inserting keys. | + | { | for( int i = 0; i < 10; ++i ) | + | // Out of memory, so abort. | { | + | vt_cleanup( &our_set ); | int_set_itr itr = | + | return 1; | int_set_insert( &our_set, i ); | + | } | if( int_set_is_end( itr ) ) | + | } | { | + | | // Out of memory, so abort. | + | // Erasing keys. | int_set_cleanup( &our_set ); | + | for( int i = 0; i < 10; i += 3 ) | return 1; | + | vt_erase( &our_set, i ); | } | + | | } | + | // Retrieving keys. | | + | for( int i = 0; i < 10; ++i ) | // Erasing keys. | + | { | for( int i = 0; i < 10; i += 3 ) | + | int_set_itr itr = vt_get( &our_set, i ); | int_set_erase( &our_set, i ); | + | if( !vt_is_end( itr ) ) | | + | printf( "%d ", itr.data->key ); | // Retrieving keys. | + | } | for( int i = 0; i < 10; ++i ) | + | // Printed: 1 2 4 5 7 8 | { | + | | int_set_itr itr = int_set_get( &our_set, i ); | + | // Iteration. | if( !int_set_is_end( itr ) ) | + | for( | printf( "%d ", itr.data->key ); | + | int_set_itr itr = vt_first( &our_set ); | } | + | !vt_is_end( itr ); | // Printed: 1 2 4 5 7 8 | + | itr = vt_next( itr ) | | + | ) | // Iteration. | + | printf( "%d ", itr.data->key ); | for( | + | // Printed: 2 4 7 1 5 8 | int_set_itr itr = | + | | int_set_first( &our_set ); | + | vt_cleanup( &our_set ); | !int_set_is_end( itr ); | + | | itr = int_set_next( itr ) | + | // Map. | ) | + | | printf( "%d ", itr.data->key ); | + | int_int_map our_map; | // Printed: 2 4 7 1 5 8 | + | vt_init( &our_map ); | | + | | int_set_cleanup( &our_set ); | + | // Inserting keys and values. | | + | for( int i = 0; i < 10; ++i ) | // Map. | + | { | | + | int_int_map_itr itr = | int_int_map our_map; | + | vt_insert( &our_map, i, i + 1 ); | int_int_map_init( &our_map ); | + | if( vt_is_end( itr ) ) | | + | { | // Inserting keys and values. | + | // Out of memory, so abort. | for( int i = 0; i < 10; ++i ) | + | vt_cleanup( &our_map ); | { | + | return 1; | int_int_map_itr itr = | + | } | int_int_map_insert( &our_map, i, i + 1 ); | + | } | if( int_int_map_is_end( itr ) ) | + | | { | + | // Erasing keys and values. | // Out of memory, so abort. | + | for( int i = 0; i < 10; i += 3 ) | int_int_map_cleanup( &our_map ); | + | vt_erase( &our_map, i ); | return 1; | + | | } | + | // Retrieving keys and values. | } | + | for( int i = 0; i < 10; ++i ) | | + | { | // Erasing keys and values. | + | int_int_map_itr itr = vt_get( &our_map, i ); | for( int i = 0; i < 10; i += 3 ) | + | if( !vt_is_end( itr ) ) | int_int_map_erase( &our_map, i ); | + | printf( | | + | "%d:%d ", | // Retrieving keys and values. | + | itr.data->key, | for( int i = 0; i < 10; ++i ) | + | itr.data->val | { | + | ); | int_int_map_itr itr = | + | } | int_int_map_get( &our_map, i ); | + | // Printed: 1:2 2:3 4:5 5:6 7:8 8:9 | if( !int_int_map_is_end( itr ) ) | + | | printf( | + | // Iteration. | "%d:%d ", | + | for( | itr.data->key, | + | int_int_map_itr itr = vt_first( &our_map ); | itr.data->val | + | !vt_is_end( itr ); | ); | + | itr = vt_next( itr ) | } | + | ) | // Printed: 1:2 2:3 4:5 5:6 7:8 8:9 | + | printf( | | + | "%d:%d ", | // Iteration. | + | itr.data->key, | for( | + | itr.data->val | int_int_map_itr itr = | + | ); | int_int_map_first( &our_map ); | + | // Printed: 2:3 4:5 7:8 1:2 5:6 8:9 | !int_int_map_is_end( itr ); | + | | itr = int_int_map_next( itr ) | + | vt_cleanup( &our_map ); | ) | + | } | printf( | + | | "%d:%d ", | + | | itr.data->key, | + | | itr.data->val | + | | ); | + | | // Printed: 2:3 4:5 7:8 1:2 5:6 8:9 | + | | | + | | int_int_map_cleanup( &our_map ); | + | | } | + | | | + +---------------------------------------------------------+----------------------------------------------------------+ + +API: + + Instantiating a hash table template: + + Create a new hash table type in the following manner: + + #define NAME + #define KEY_TY + #include "verstable.h" + + The NAME macro specifies the name of hash table type that the library will declare, the prefix for the functions + associated with it, and the prefix for the associated iterator type. + + The KEY_TY macro specifies the key type. + + In C99, it is also always necessary to define HASH_FN and CMPR_FN (see below) before including the header. + + The following macros may also be defined before including the header: + + #define VAL_TY + + The type of the value associated with each key. + If this macro is defined, the hash table acts as a map associating keys with values. + Otherwise, it acts as a set containing only keys. + + #define HASH_FN + + The name of the existing function used to hash each key. + The function should have the signature uint64_t ( KEY_TY key ) and return a 64-bit hash code. + For best performance, the hash function should provide a high level of entropy across all bits. + There are two default hash functions: vt_hash_integer for all integer types up to 64 bits in size, and + vt_hash_string for NULL-terminated strings (i.e. char *). + When KEY_TY is one of such types and the compiler is in C11 mode or later, HASH_FN may be left undefined, in + which case the appropriate default function is inferred from KEY_TY. + Otherwise, HASH_FN must be defined. + + #define CMPR_FN + + The name of the existing function used to compare two keys. + The function should have the signature bool ( KEY_TY key_1, KEY_TY key_2 ) and return true if the two keys are + equal. + There are two default comparison functions: vt_cmpr_integer for all integer types up to 64 bits in size, and + vt_cmpr_string for NULL-terminated strings (i.e. char *). + As with the default hash functions, in C11 or later the appropriate default comparison function is inferred if + KEY_TY is one of such types and CMPR_FN is left undefined. + Otherwise, CMPR_FN must be defined. + + #define MAX_LOAD + + The floating-point load factor at which the hash table automatically doubles the size of its internal buckets + array. + The default is 0.9, i.e. 90%. + + #define KEY_DTOR_FN + + The name of the existing destructor function, with the signature void ( KEY_TY key ), called on a key when it is + erased from the table or replaced by a newly inserted key. + The API functions that may call the key destructor are NAME_insert, NAME_erase, NAME_erase_itr, NAME_clear, + and NAME_cleanup. + + #define VAL_DTOR_FN + + The name of the existing destructor function, with the signature void ( VAL_TY val ), called on a value when it + is erased from the table or replaced by a newly inserted value. + The API functions that may call the value destructor are NAME_insert, NAME_erase, NAME_erase_itr, NAME_clear, + and NAME_cleanup. + + #define CTX_TY + + The type of the hash table type's ctx (context) member. + This member only exists if CTX_TY was defined. + It is intended to be used in conjunction with MALLOC_FN and FREE_FN (see below). + + #define MALLOC_FN + + The name of the existing function used to allocate memory. + If CTX_TY was defined, the signature should be void *( size_t size, CTX_TY *ctx ), where size is the number of + bytes to allocate and ctx points to the table's ctx member. + Otherwise, the signature should be void *( size_t size ). + The default wraps stdlib.h's malloc. + + #define FREE_FN + + The name of the existing function used to free memory. + If CTX_TY was defined, the signature should be void ( void *ptr, size_t size, CTX_TY *ctx ), where ptr points to + the memory to free, size is the number of bytes that were allocated, and ctx points to the table's ctx member. + Otherwise, the signature should be void ( void *ptr, size_t size ). + The default wraps stdlib.h's free. + + #define HEADER_MODE + #define IMPLEMENTATION_MODE + + By default, all hash table functions are defined as static inline functions, the intent being that a given hash + table template should be instantiated once per translation unit; for best performance, this is the recommended + way to use the library. + However, it is also possible to separate the struct definitions and function declarations from the function + definitions such that one implementation can be shared across all translation units (as in a traditional header + and source file pair). + In that case, instantiate a template wherever it is needed by defining HEADER_MODE, along with only NAME, + KEY_TY, and (optionally) VAL_TY, CTX_TY, and header guards, and including the library, e.g.: + + #ifndef INT_INT_MAP_H + #define INT_INT_MAP_H + #define NAME int_int_map + #define KEY_TY int + #define VAL_TY int + #define HEADER_MODE + #include "verstable.h" + #endif + + In one source file, define IMPLEMENTATION_MODE, along with NAME, KEY_TY, and any of the aforementioned optional + macros, and include the library, e.g.: + + #define NAME int_int_map + #define KEY_TY int + #define VAL_TY int + #define HASH_FN vt_hash_integer // C99. + #define CMPR_FN vt_cmpr_integer // C99. + #define MAX_LOAD 0.8 + #define IMPLEMENTATION_MODE + #include "verstable.h" + + Including the library automatically undefines all the aforementioned macros after they have been used to instantiate + the template. + + Functions: + + The functions associated with a hash table type are all prefixed with the name the user supplied via the NAME macro. + In C11 and later, the generic "vt_"-prefixed macros may be used to automatically select the correct version of the + specified function based on the arguments. + + void NAME_init( NAME *table ) + void NAME_init( NAME *table, CTX_TY ctx ) + // C11 generic macro: vt_init. + + Initializes the table for use. + If CTX_TY was defined, ctx sets the table's ctx member. + + bool NAME_init_clone( NAME *table, NAME *source ) + bool NAME_init_clone( NAME *table, NAME *source, CTX_TY ctx ) + // C11 generic macro: vt_init_clone. + + Initializes the table as a shallow copy of the specified source table. + If CTX_TY was defined, ctx sets the table's ctx member. + Returns false in the case of memory allocation failure. + + size_t NAME_size( NAME *table ) // C11 generic macro: vt_size. + + Returns the number of keys currently in the table. + + size_t NAME_bucket_count( NAME *table ) // C11 generic macro: vt_bucket_count. + + Returns the table's current bucket count. + + NAME_itr NAME_insert( NAME *table, KEY_TY key ) + NAME_itr NAME_insert( NAME *table, KEY_TY key, VAL_TY val ) + // C11 generic macro: vt_insert. + + Inserts the specified key (and value, if VAL_TY was defined) into the hash table. + If the same key already exists, then the new key (and value) replaces the existing key (and value). + Returns an iterator to the new key, or an end iterator in the case of memory allocation failure. + + NAME_itr NAME_get_or_insert( NAME *table, KEY_TY key ) + NAME_itr NAME_get_or_insert( NAME *table, KEY_TY key, VAL_TY val ) + // C11 generic macro: vt_get_or_insert. + + Inserts the specified key (and value, if VAL_TY was defined) if it does not already exist in the table. + Returns an iterator to the new key if it was inserted, or an iterator to the existing key, or an end iterator if + the key did not exist but the new key could not be inserted because of memory allocation failure. + Determine whether the key was inserted by comparing the table's size before and after the call. + + NAME_itr NAME_get( NAME *table, KEY_TY key ) // C11 generic macro: vt_get. + + Returns a iterator to the specified key, or an end iterator if no such key exists. + + bool NAME_erase( NAME *table, KEY_TY key ) // C11 generic macro: vt_erase. + + Erases the specified key (and associated value, if VAL_TY was defined), if it exists. + Returns true if a key was erased. + + NAME_itr NAME_erase_itr( NAME *table, NAME_itr itr ) // C11 generic macro: vt_erase_itr. + + Erases the key (and associated value, if VAL_TY was defined) pointed to by the specified iterator. + Returns an iterator to the next key in the table, or an end iterator if the erased key was the last one. + + bool NAME_reserve( NAME *table, size_t size ) // C11 generic macro: vt_reserve. + + Ensures that the bucket count is large enough to support the specified key count (i.e. size) without rehashing. + Returns false if unsuccessful due to memory allocation failure. + + bool NAME_shrink( NAME *table ) // C11 generic macro: vt_shrink. + + Shrinks the bucket count to best accommodate the current size. + Returns false if unsuccessful due to memory allocation failure. + + NAME_itr NAME_first( NAME *table ) // C11 generic macro: vt_first. + + Returns an iterator to the first key in the table, or an end iterator if the table is empty. + + bool NAME_is_end( NAME_itr itr ) // C11 generic macro: vt_is_end. + + Returns true if the iterator is an end iterator. + + NAME_itr NAME_next( NAME_itr itr ) // C11 generic macro: vt_next. + + Returns an iterator to the key after the one pointed to by the specified iterator, or an end iterator if the + specified iterator points to the last key in the table. + + void NAME_clear( NAME *table ) // C11 generic macro: vt_clear. + + Erases all keys (and values, if VAL_TY was defined) in the table. + + void NAME_cleanup( NAME *table ) // C11 generic macro: vt_cleanup. + + Erases all keys (and values, if VAL_TY was defined) in the table, frees all memory associated with it, and + initializes it for reuse. + + Iterators: + + Access the key (and value, if VAL_TY was defined) that an iterator points to using the NAME_itr struct's data + member: + + itr.data->key + itr.data->val + + Functions that may insert new keys (NAME_insert and NAME_get_or_insert), erase keys (NAME_erase and NAME_erase_itr), + or reallocate the internal bucket array (NAME_reserve and NAME_shrink) invalidate all existing iterators. + To delete keys during iteration and resume iterating, use the return value of NAME_erase_itr. + +Version history: + + 06/05/2025 2.2.1: Fixed incorrect signature of NAME_is_end in documentation. + 18/04/2025 2.2.0: Added const qualifier to the table parameter of NAME_size, NAME_bucket_count, NAME_get, and + NAME_first and to the source parameter of NAME_init_clone. + Added default support for const char * strings. + Added support for -Wextra. + Replaced FNV-1a with Wyhash as the default string hash function. + 18/06/2024 2.1.1: Fixed a bug affecting iteration on big-endian platforms under MSVC. + 27/05/2024 2.1.0: Replaced the Murmur3 mixer with the fast-hash mixer as the default integer hash function. + Fixed a bug that could theoretically cause a crash on rehash (triggerable in testing using + NAME_shrink with a maximum load factor significantly higher than 1.0). + 06/02/2024 2.0.0: Improved custom allocator support by introducing the CTX_TY option and allowing user-supplied free + functions to receive the allocation size. + Improved documentation. + Introduced various optimizations, including storing the buckets-array size mask instead of the + bucket count, eliminating empty-table checks, combining the buckets memory and metadata memory into + one allocation, and adding branch prediction macros. + Fixed a bug that caused a key to be used after destruction during erasure. + 12/12/2023 1.0.0: Initial release. + +License (MIT): + + Copyright (c) 2023-2025 Jackson L. Allan + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Common header section */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#ifndef VERSTABLE_H +#define VERSTABLE_H + +#include +#include +#include +#include +#include +#include + +// Two-way concatenation macro. +#define VT_CAT_( a, b ) a##b +#define VT_CAT( a, b ) VT_CAT_( a, b ) + +// Branch optimization macros. +#ifdef __GNUC__ +#define VT_LIKELY( expression ) __builtin_expect( (bool)( expression ), true ) +#define VT_UNLIKELY( expression ) __builtin_expect( (bool)( expression ), false ) +#else +#define VT_LIKELY( expression ) ( expression ) +#define VT_UNLIKELY( expression ) ( expression ) +#endif + +// Masks for manipulating and extracting data from a bucket's uint16_t metadatum. +#define VT_EMPTY 0x0000 +#define VT_HASH_FRAG_MASK 0xF000 // 0b1111000000000000. +#define VT_IN_HOME_BUCKET_MASK 0x0800 // 0b0000100000000000. +#define VT_DISPLACEMENT_MASK 0x07FF // 0b0000011111111111, also denotes the displacement limit. Set to VT_LOAD to 1.0 + // to test proper handling of encroachment on the displacement limit during + // inserts. + +// Extracts a hash fragment from a uint64_t hash code. +// We take the highest four bits so that keys that map (via modulo) to the same bucket have distinct hash fragments. +static inline uint16_t vt_hashfrag( uint64_t hash ) +{ + return ( hash >> 48 ) & VT_HASH_FRAG_MASK; +} + +// Standard quadratic probing formula that guarantees that all buckets are visited when the bucket count is a power of +// two (at least in theory, because the displacement limit could terminate the search early when the bucket count is +// high). +static inline size_t vt_quadratic( uint16_t displacement ) +{ + return ( (size_t)displacement * displacement + displacement ) / 2; +} + +#define VT_MIN_NONZERO_BUCKET_COUNT 8 // Must be a power of two. + +// Function to find the left-most non-zero uint16_t in a uint64_t. +// This function is used when we scan four buckets at a time while iterating and relies on compiler intrinsics wherever +// possible. + +#if defined( __GNUC__ ) && ULLONG_MAX == 0xFFFFFFFFFFFFFFFF + +static inline int vt_first_nonzero_uint16( uint64_t val ) +{ + const uint16_t endian_checker = 0x0001; + if( *(const char *)&endian_checker ) // Little-endian (the compiler will optimize away the check at -O1 and above). + return __builtin_ctzll( val ) / 16; + + return __builtin_clzll( val ) / 16; +} + +#elif defined( _MSC_VER ) && ( defined( _M_X64 ) || defined( _M_ARM64 ) ) + +#include +#pragma intrinsic(_BitScanForward64) +#pragma intrinsic(_BitScanReverse64) + +static inline int vt_first_nonzero_uint16( uint64_t val ) +{ + unsigned long result; + + const uint16_t endian_checker = 0x0001; + if( *(const char *)&endian_checker ) + _BitScanForward64( &result, val ); + else + { + _BitScanReverse64( &result, val ); + result = 63 - result; + } + + return result / 16; +} + +#else + +static inline int vt_first_nonzero_uint16( uint64_t val ) +{ + int result = 0; + + uint32_t half; + memcpy( &half, &val, sizeof( uint32_t ) ); + if( !half ) + result += 2; + + uint16_t quarter; + memcpy( &quarter, (char *)&val + result * sizeof( uint16_t ), sizeof( uint16_t ) ); + if( !quarter ) + result += 1; + + return result; +} + +#endif + +// When the bucket count is zero, setting the metadata pointer to point to a VT_EMPTY placeholder, rather than NULL, +// allows us to avoid checking for a zero bucket count during insertion and lookup. +static const uint16_t vt_empty_placeholder_metadatum = VT_EMPTY; + +// Default hash and comparison functions. + +// Fast-hash, as described by https://jonkagstrom.com/bit-mixer-construction and +// https://code.google.com/archive/p/fast-hash. +// In testing, this hash function provided slightly better performance than the Murmur3 mixer. +static inline uint64_t vt_hash_integer( uint64_t key ) +{ + key ^= key >> 23; + key *= 0x2127599bf4325c37ull; + key ^= key >> 47; + return key; +} + +// For hashing strings, we use the public-domain Wyhash +// (https://github.com/wangyi-fudan/wyhash) with the following modifications: +// * We use a fixed seed and secret (the defaults suggested in the Wyhash repository). +// * We do not handle endianness, so the result will differ depending on the platform. +// * We omit the code optimized for 32-bit platforms. + +static inline void vt_wymum( uint64_t *a, uint64_t *b ) +{ +#if defined( __SIZEOF_INT128__ ) + __uint128_t r = *a; + r *= *b; + *a = (uint64_t)r; + *b = (uint64_t)( r >> 64 ); +#elif defined( _MSC_VER ) && defined( _M_X64 ) + *a = _umul128( *a, *b, b ); +#else + uint64_t ha = *a >> 32; + uint64_t hb = *b >> 32; + uint64_t la = (uint32_t)*a; + uint64_t lb = (uint32_t)*b; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + ( rm0 << 32 ); + uint64_t c = t < rl; + uint64_t lo = t + ( rm1 << 32 ); + c += lo < t; + uint64_t hi = rh + ( rm0 >> 32 ) + ( rm1 >> 32 ) + c; + *a = lo; + *b = hi; +#endif +} + +static inline uint64_t vt_wymix( uint64_t a, uint64_t b ) +{ + vt_wymum( &a, &b ); + return a ^ b; +} + +static inline uint64_t vt_wyr8( const unsigned char *p ) +{ + uint64_t v; + memcpy( &v, p, 8 ); + return v; +} + +static inline uint64_t vt_wyr4( const unsigned char *p ) +{ + uint32_t v; + memcpy( &v, p, 4 ); + return v; +} + +static inline uint64_t vt_wyr3( const unsigned char *p, size_t k ) +{ + return ( ( (uint64_t)p[ 0 ] ) << 16 ) | ( ( (uint64_t)p[ k >> 1 ] ) << 8 ) | p[ k - 1 ]; +} + +static inline size_t vt_wyhash( const void *key, size_t len ) +{ + const unsigned char *p = (const unsigned char *)key; + uint64_t seed = 0xca813bf4c7abf0a9ull; + uint64_t a; + uint64_t b; + if( VT_LIKELY( len <= 16 ) ) + { + if( VT_LIKELY( len >= 4 ) ) + { + a = ( vt_wyr4( p ) << 32 ) | vt_wyr4( p + ( ( len >> 3 ) << 2 ) ); + b = ( vt_wyr4( p + len - 4 ) << 32 ) | vt_wyr4( p + len - 4 - ( ( len >> 3 ) << 2 ) ); + } + else if( VT_LIKELY( len > 0 ) ) + { + a = vt_wyr3( p, len ); + b = 0; + } + else + { + a = 0; + b = 0; + } + } + else + { + size_t i = len; + if( VT_UNLIKELY( i >= 48 ) ) + { + uint64_t see1 = seed; + uint64_t see2 = seed; + do{ + seed = vt_wymix( vt_wyr8( p ) ^ 0x8bb84b93962eacc9ull, vt_wyr8( p + 8 ) ^ seed ); + see1 = vt_wymix( vt_wyr8( p + 16 ) ^ 0x4b33a62ed433d4a3ull, vt_wyr8( p + 24 ) ^ see1 ); + see2 = vt_wymix( vt_wyr8( p + 32 ) ^ 0x4d5a2da51de1aa47ull, vt_wyr8( p + 40 ) ^ see2 ); + p += 48; + i -= 48; + } + while( VT_LIKELY( i >= 48 ) ); + seed ^= see1 ^ see2; + } + + while( VT_UNLIKELY( i > 16 ) ) + { + seed = vt_wymix( vt_wyr8( p ) ^ 0x8bb84b93962eacc9ull, vt_wyr8( p + 8 ) ^ seed ); + i -= 16; + p += 16; + } + + a = vt_wyr8( p + i - 16 ); + b = vt_wyr8( p + i - 8 ); + } + + a ^= 0x8bb84b93962eacc9ull; + b ^= seed; + vt_wymum( &a, &b ); + return (size_t)vt_wymix( a ^ 0x2d358dccaa6c78a5ull ^ len, b ^ 0x8bb84b93962eacc9ull ); +} + +static inline uint64_t vt_hash_string( const char *key ) +{ + return vt_wyhash( key, strlen( key ) ); +} + +static inline bool vt_cmpr_integer( uint64_t key_1, uint64_t key_2 ) +{ + return key_1 == key_2; +} + +static inline bool vt_cmpr_string( const char *key_1, const char *key_2 ) +{ + return strcmp( key_1, key_2 ) == 0; +} + +// Default allocation and free functions. + +static inline void *vt_malloc( size_t size ) +{ + return malloc( size ); +} + +static inline void vt_free( void *ptr, size_t size ) +{ + (void)size; + free( ptr ); +} + +static inline void *vt_malloc_with_ctx( size_t size, void *ctx ) +{ + (void)ctx; + return malloc( size ); +} + +static inline void vt_free_with_ctx( void *ptr, size_t size, void *ctx ) +{ + (void)size; + (void)ctx; + free( ptr ); +} + +// The rest of the common header section pertains to the C11 generic macro API. +// This interface is based on the extendible-_Generic mechanism documented in detail at +// https://github.com/JacksonAllan/CC/blob/main/articles/Better_C_Generics_Part_1_The_Extendible_Generic.md. +// In summary, instantiating a template also defines wrappers for the template's types and functions with names in the +// pattern of vt_table_NNNN and vt_init_NNNN, where NNNN is an automatically generated integer unique to the template +// instance in the current translation unit. +// These wrappers plug into _Generic-based API macros, which use preprocessor magic to automatically generate _Generic +// slots for every existing template instance. +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined( VT_NO_C11_GENERIC_API ) + +// Octal counter that supports up to 511 hash table templates. +#define VT_TEMPLATE_COUNT_D1 0 // Digit 1, i.e. least significant digit. +#define VT_TEMPLATE_COUNT_D2 0 +#define VT_TEMPLATE_COUNT_D3 0 + +// Four-way concatenation macro. +#define VT_CAT_4_( a, b, c, d ) a##b##c##d +#define VT_CAT_4( a, b, c, d ) VT_CAT_4_( a, b, c, d ) + +// Provides the current value of the counter as a three-digit octal number preceded by 0. +#define VT_TEMPLATE_COUNT VT_CAT_4( 0, VT_TEMPLATE_COUNT_D3, VT_TEMPLATE_COUNT_D2, VT_TEMPLATE_COUNT_D1 ) + +// _Generic-slot generation macros. + +#define VT_GENERIC_SLOT( ty, fn, n ) , VT_CAT( ty, n ): VT_CAT( fn, n ) +#define VT_R1_0( ty, fn, d3, d2 ) +#define VT_R1_1( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 0 ) ) +#define VT_R1_2( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 1 ) ) VT_R1_1( ty, fn, d3, d2 ) +#define VT_R1_3( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 2 ) ) VT_R1_2( ty, fn, d3, d2 ) +#define VT_R1_4( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 3 ) ) VT_R1_3( ty, fn, d3, d2 ) +#define VT_R1_5( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 4 ) ) VT_R1_4( ty, fn, d3, d2 ) +#define VT_R1_6( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 5 ) ) VT_R1_5( ty, fn, d3, d2 ) +#define VT_R1_7( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 6 ) ) VT_R1_6( ty, fn, d3, d2 ) +#define VT_R1_8( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 7 ) ) VT_R1_7( ty, fn, d3, d2 ) +#define VT_R2_0( ty, fn, d3 ) +#define VT_R2_1( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 0 ) +#define VT_R2_2( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 1 ) VT_R2_1( ty, fn, d3 ) +#define VT_R2_3( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 2 ) VT_R2_2( ty, fn, d3 ) +#define VT_R2_4( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 3 ) VT_R2_3( ty, fn, d3 ) +#define VT_R2_5( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 4 ) VT_R2_4( ty, fn, d3 ) +#define VT_R2_6( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 5 ) VT_R2_5( ty, fn, d3 ) +#define VT_R2_7( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 6 ) VT_R2_6( ty, fn, d3 ) +#define VT_R2_8( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 7 ) VT_R2_7( ty, fn, d3 ) +#define VT_R3_0( ty, fn ) +#define VT_R3_1( ty, fn ) VT_R2_8( ty, fn, 0 ) +#define VT_R3_2( ty, fn ) VT_R2_8( ty, fn, 1 ) VT_R3_1( ty, fn ) +#define VT_R3_3( ty, fn ) VT_R2_8( ty, fn, 2 ) VT_R3_2( ty, fn ) +#define VT_R3_4( ty, fn ) VT_R2_8( ty, fn, 3 ) VT_R3_3( ty, fn ) +#define VT_R3_5( ty, fn ) VT_R2_8( ty, fn, 4 ) VT_R3_4( ty, fn ) +#define VT_R3_6( ty, fn ) VT_R2_8( ty, fn, 5 ) VT_R3_5( ty, fn ) +#define VT_R3_7( ty, fn ) VT_R2_8( ty, fn, 6 ) VT_R3_6( ty, fn ) + +#define VT_GENERIC_SLOTS( ty, fn ) \ +VT_CAT( VT_R1_, VT_TEMPLATE_COUNT_D1 )( ty, fn, VT_TEMPLATE_COUNT_D3, VT_TEMPLATE_COUNT_D2 ) \ +VT_CAT( VT_R2_, VT_TEMPLATE_COUNT_D2 )( ty, fn, VT_TEMPLATE_COUNT_D3 ) \ +VT_CAT( VT_R3_, VT_TEMPLATE_COUNT_D3 )( ty, fn ) \ + +// Actual generic API macros. + +// vt_init must be handled as a special case because it could take one or two arguments, depending on whether CTX_TY +// was defined. +#define VT_ARG_3( _1, _2, _3, ... ) _3 +#define vt_init( ... ) VT_ARG_3( __VA_ARGS__, vt_init_with_ctx, vt_init_without_ctx, )( __VA_ARGS__ ) +#define vt_init_without_ctx( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_init_ ) )( table ) +#define vt_init_with_ctx( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_init_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_init_clone( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_init_clone_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_size( table )_Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_size_ ) )( table ) + +#define vt_bucket_count( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_bucket_count_ ) )( table ) + +#define vt_is_end( itr ) _Generic( itr VT_GENERIC_SLOTS( vt_table_itr_, vt_is_end_ ) )( itr ) + +#define vt_insert( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_insert_ ) )( table, __VA_ARGS__ ) + +#define vt_get_or_insert( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_get_or_insert_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_get( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_get_ ) )( table, __VA_ARGS__ ) + +#define vt_erase( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_erase_ ) )( table, __VA_ARGS__ ) + +#define vt_next( itr ) _Generic( itr VT_GENERIC_SLOTS( vt_table_itr_, vt_next_ ) )( itr ) + +#define vt_erase_itr( table, ... ) _Generic( *( table ) \ + VT_GENERIC_SLOTS( vt_table_, vt_erase_itr_ ) \ +)( table, __VA_ARGS__ ) \ + +#define vt_reserve( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_reserve_ ) )( table, __VA_ARGS__ ) + +#define vt_shrink( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_shrink_ ) )( table ) + +#define vt_first( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_first_ ) )( table ) + +#define vt_clear( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_clear_ ) )( table ) + +#define vt_cleanup( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_cleanup_ ) )( table ) + +#endif + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Prefixed structs */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#ifndef IMPLEMENTATION_MODE + +typedef struct +{ + KEY_TY key; + #ifdef VAL_TY + VAL_TY val; + #endif +} VT_CAT( NAME, _bucket ); + +typedef struct +{ + VT_CAT( NAME, _bucket ) *data; + uint16_t *metadatum; + uint16_t *metadata_end; // Iterators carry an internal end pointer so that NAME_is_end does not need the table to be + // passed in as an argument. + // This also allows for the zero-bucket-count check to occur once in NAME_first, rather than + // repeatedly in NAME_is_end. + size_t home_bucket; // SIZE_MAX if home bucket is unknown. +} VT_CAT( NAME, _itr ); + +typedef struct +{ + size_t key_count; + size_t buckets_mask; // Rather than storing the bucket count directly, we store the bit mask used to reduce a hash + // code or displacement-derived bucket index to the buckets array, i.e. the bucket count minus + // one. + // Consequently, a zero bucket count (i.e. when .metadata points to the placeholder) constitutes + // a special case, represented by all bits unset (i.e. zero). + VT_CAT( NAME, _bucket ) *buckets; + uint16_t *metadata; // As described above, each metadatum consists of a 4-bit hash-code fragment (X), a 1-bit flag + // indicating whether the key in this bucket begins a chain associated with the bucket (Y), and + // an 11-bit value indicating the quadratic displacement of the next key in the chain (Z): + // XXXXYZZZZZZZZZZZ. + #ifdef CTX_TY + CTX_TY ctx; + #endif +} NAME; + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Function prototypes */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#if defined( HEADER_MODE ) || defined( IMPLEMENTATION_MODE ) +#define VT_API_FN_QUALIFIERS +#else +#define VT_API_FN_QUALIFIERS static inline +#endif + +#ifndef IMPLEMENTATION_MODE + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _init )( + NAME * + #ifdef CTX_TY + , CTX_TY + #endif +); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _init_clone )( + NAME *, + const NAME * + #ifdef CTX_TY + , CTX_TY + #endif +); + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _size )( const NAME * ); + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _bucket_count )( const NAME * ); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _is_end )( VT_CAT( NAME, _itr ) ); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert )( + NAME *, + KEY_TY + #ifdef VAL_TY + , VAL_TY + #endif +); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get_or_insert )( + NAME *, + KEY_TY + #ifdef VAL_TY + , VAL_TY + #endif +); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get )( + const NAME *table, + KEY_TY key +); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase )( NAME *, KEY_TY ); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _next )( VT_CAT( NAME, _itr ) ); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _reserve )( NAME *, size_t ); + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _shrink )( NAME * ); + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _first )( const NAME * ); + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _clear )( NAME * ); + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _cleanup )( NAME * ); + +// Not an API function, but must be prototyped anyway because it is called by the inline NAME_erase_itr below. +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase_itr_raw ) ( NAME *, VT_CAT( NAME, _itr ) ); + +// Erases the key pointed to by itr and returns an iterator to the next key in the table. +// This function must be inlined to ensure that the compiler optimizes away the NAME_fast_forward call if the returned +// iterator is discarded. +#ifdef __GNUC__ +static inline __attribute__((always_inline)) +#elif defined( _MSC_VER ) +static __forceinline +#else +static inline +#endif +VT_CAT( NAME, _itr ) VT_CAT( NAME, _erase_itr )( NAME *table, VT_CAT( NAME, _itr ) itr ) +{ + if( VT_CAT( NAME, _erase_itr_raw )( table, itr ) ) + return VT_CAT( NAME, _next )( itr ); + + return itr; +} + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Function implementations */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#ifndef HEADER_MODE + +// Default settings. + +#ifndef MAX_LOAD +#define MAX_LOAD 0.9 +#endif + +#ifndef MALLOC_FN +#ifdef CTX_TY +#define MALLOC_FN vt_malloc_with_ctx +#else +#define MALLOC_FN vt_malloc +#endif +#endif + +#ifndef FREE_FN +#ifdef CTX_TY +#define FREE_FN vt_free_with_ctx +#else +#define FREE_FN vt_free +#endif +#endif + +#ifndef HASH_FN +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#ifdef _MSC_VER // In MSVC, the compound literal in the _Generic triggers a warning about unused local variables at /W4. +#define HASH_FN \ +_Pragma( "warning( push )" ) \ +_Pragma( "warning( disable: 4189 )" ) \ +_Generic( ( KEY_TY ){ 0 }, char *: vt_hash_string, const char*: vt_hash_string, default: vt_hash_integer ) \ +_Pragma( "warning( pop )" ) +#else +#define HASH_FN _Generic( ( KEY_TY ){ 0 }, \ + char *: vt_hash_string, \ + const char*: vt_hash_string, \ + default: vt_hash_integer \ +) +#endif +#else +#error Hash function inference is only available in C11 and later. In C99, you need to define HASH_FN manually to \ +vt_hash_integer, vt_hash_string, or your own custom function with the signature uint64_t ( KEY_TY ). +#endif +#endif + +#ifndef CMPR_FN +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#ifdef _MSC_VER +#define CMPR_FN \ +_Pragma( "warning( push )" ) \ +_Pragma( "warning( disable: 4189 )" ) \ +_Generic( ( KEY_TY ){ 0 }, char *: vt_cmpr_string, const char*: vt_cmpr_string, default: vt_cmpr_integer ) \ +_Pragma( "warning( pop )" ) +#else +#define CMPR_FN _Generic( ( KEY_TY ){ 0 }, \ + char *: vt_cmpr_string, \ + const char*: vt_cmpr_string, \ + default: vt_cmpr_integer \ +) +#endif +#else +#error Comparison function inference is only available in C11 and later. In C99, you need to define CMPR_FN manually \ +to vt_cmpr_integer, vt_cmpr_string, or your own custom function with the signature bool ( KEY_TY, KEY_TY ). +#endif +#endif + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _init )( + NAME *table + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + table->key_count = 0; + table->buckets_mask = 0x0000000000000000ull; + table->buckets = NULL; + table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; + #ifdef CTX_TY + table->ctx = ctx; + #endif +} + +// For efficiency, especially in the case of a small table, the buckets array and metadata share the same dynamic memory +// allocation: +// +-----------------------------+-----+----------------+--------+ +// | Buckets | Pad | Metadata | Excess | +// +-----------------------------+-----+----------------+--------+ +// Any allocated metadata array requires four excess elements to ensure that iteration functions, which read four +// metadata at a time, never read beyond the end of it. +// This function returns the offset of the beginning of the metadata, i.e. the size of the buckets array plus the +// (usually zero) padding. +// It assumes that the bucket count is not zero. +static inline size_t VT_CAT( NAME, _metadata_offset )( NAME *table ) +{ + // Use sizeof, rather than alignof, for C99 compatibility. + return ( ( ( table->buckets_mask + 1 ) * sizeof( VT_CAT( NAME, _bucket ) ) + sizeof( uint16_t ) - 1 ) / + sizeof( uint16_t ) ) * sizeof( uint16_t ); +} + +// Returns the total allocation size, including the buckets array, padding, metadata, and excess metadata. +// As above, this function assumes that the bucket count is not zero. +static inline size_t VT_CAT( NAME, _total_alloc_size )( NAME *table ) +{ + return VT_CAT( NAME, _metadata_offset )( table ) + ( table->buckets_mask + 1 + 4 ) * sizeof( uint16_t ); +} + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _init_clone )( + NAME *table, + const NAME *source + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + table->key_count = source->key_count; + table->buckets_mask = source->buckets_mask; + #ifdef CTX_TY + table->ctx = ctx; + #endif + + if( !source->buckets_mask ) + { + table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; + table->buckets = NULL; + return true; + } + + void *allocation = MALLOC_FN( + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + if( VT_UNLIKELY( !allocation ) ) + return false; + + table->buckets = (VT_CAT( NAME, _bucket ) *)allocation; + table->metadata = (uint16_t *)( (unsigned char *)allocation + VT_CAT( NAME, _metadata_offset )( table ) ); + memcpy( allocation, source->buckets, VT_CAT( NAME, _total_alloc_size )( table ) ); + + return true; +} + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _size )( const NAME *table ) +{ + return table->key_count; +} + +VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _bucket_count )( const NAME *table ) +{ + // If the bucket count is zero, buckets_mask will be zero, not the bucket count minus one. + // We account for this special case by adding (bool)buckets_mask rather than one. + return table->buckets_mask + (bool)table->buckets_mask; +} + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _is_end )( VT_CAT( NAME, _itr ) itr ) +{ + return itr.metadatum == itr.metadata_end; +} + +// Finds the earliest empty bucket in which a key belonging to home_bucket can be placed, assuming that home_bucket +// is already occupied. +// The reason to begin the search at home_bucket, rather than the end of the existing chain, is that keys deleted from +// other chains might have freed up buckets that could fall in this chain before the final key. +// Returns true if an empty bucket within the range of the displacement limit was found, in which case the final two +// pointer arguments contain the index of the empty bucket and its quadratic displacement from home_bucket. +static inline bool VT_CAT( NAME, _find_first_empty )( + NAME *table, + size_t home_bucket, + size_t *empty, + uint16_t *displacement +) +{ + *displacement = 1; + size_t linear_dispacement = 1; + + while( true ) + { + *empty = ( home_bucket + linear_dispacement ) & table->buckets_mask; + if( table->metadata[ *empty ] == VT_EMPTY ) + return true; + + if( VT_UNLIKELY( ++*displacement == VT_DISPLACEMENT_MASK ) ) + return false; + + linear_dispacement += *displacement; + } +} + +// Finds the key in the chain beginning in home_bucket after which to link a new key with displacement_to_empty +// quadratic displacement and returns the index of the bucket containing that key. +// Although the new key could simply be linked to the end of the chain, keeping the chain ordered by displacement +// theoretically improves cache locality during lookups. +static inline size_t VT_CAT( NAME, _find_insert_location_in_chain )( + NAME *table, + size_t home_bucket, + uint16_t displacement_to_empty +) +{ + size_t candidate = home_bucket; + while( true ) + { + uint16_t displacement = table->metadata[ candidate ] & VT_DISPLACEMENT_MASK; + + if( displacement > displacement_to_empty ) + return candidate; + + candidate = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + } +} + +// Frees up a bucket occupied by a key not belonging there so that a new key belonging there can be placed there as the +// beginning of a new chain. +// This requires: +// * Finding the previous key in the chain to which the occupying key belongs by rehashing it and then traversing the +// chain. +// * Disconnecting the key from the chain. +// * Finding the appropriate empty bucket to which to move the key. +// * Moving the key (and value) data to the empty bucket. +// * Re-linking the key to the chain. +// Returns true if the eviction succeeded, or false if no empty bucket to which to evict the occupying key could be +// found within the displacement limit. +static inline bool VT_CAT( NAME, _evict )( NAME *table, size_t bucket ) +{ + // Find the previous key in chain. + size_t home_bucket = HASH_FN( table->buckets[ bucket ].key ) & table->buckets_mask; + size_t prev = home_bucket; + while( true ) + { + size_t next = ( home_bucket + vt_quadratic( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ) ) & + table->buckets_mask; + + if( next == bucket ) + break; + + prev = next; + } + + // Disconnect the key from chain. + table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | ( table->metadata[ bucket ] & + VT_DISPLACEMENT_MASK ); + + // Find the empty bucket to which to move the key. + size_t empty; + uint16_t displacement; + if( VT_UNLIKELY( !VT_CAT( NAME, _find_first_empty )( table, home_bucket, &empty, &displacement ) ) ) + return false; + + // Find the key in the chain after which to link the moved key. + prev = VT_CAT( NAME, _find_insert_location_in_chain )( table, home_bucket, displacement ); + + // Move the key (and value) data. + table->buckets[ empty ] = table->buckets[ bucket ]; + + // Re-link the key to the chain from its new bucket. + table->metadata[ empty ] = ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) | ( table->metadata[ prev ] & + VT_DISPLACEMENT_MASK ); + table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | displacement; + + return true; +} + +// Returns an end iterator, i.e. any iterator for which .metadatum == .metadata_end. +// This function just cleans up the library code in functions that return an end iterator as a failure indicator. +static inline VT_CAT( NAME, _itr ) VT_CAT( NAME, _end_itr )( void ) +{ + VT_CAT( NAME, _itr ) itr = { NULL, NULL, NULL, 0 }; + return itr; +} + +// Inserts a key, optionally replacing the existing key if it already exists. +// There are two main cases that must be handled: +// * If the key's home bucket is empty or occupied by a key that does not belong there, then the key is inserted there, +// evicting the occupying key if there is one. +// * Otherwise, the chain of keys beginning at the home bucket is (if unique is false) traversed in search of a matching +// key. +// If none is found, then the new key is inserted at the earliest available bucket, per quadratic probing from the +// home bucket, and then linked to the chain in a manner that maintains its quadratic order. +// The unique argument tells the function whether to skip searching for the key before inserting it (on rehashing, this +// step is unnecessary). +// The replace argument tells the function whether to replace an existing key. +// If replace is true, the function returns an iterator to the inserted key, or an end iterator if the key was not +// inserted because of the maximum load factor or displacement limit constraints. +// If replace is false, then the return value is as described above, except that if the key already exists, the function +// returns an iterator to the existing key. +static inline VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert_raw )( + NAME *table, + KEY_TY key, + #ifdef VAL_TY + VAL_TY *val, + #endif + bool unique, + bool replace +) +{ + uint64_t hash = HASH_FN( key ); + uint16_t hashfrag = vt_hashfrag( hash ); + size_t home_bucket = hash & table->buckets_mask; + + // Case 1: The home bucket is empty or contains a key that doesn't belong there. + // This case also implicitly handles the case of a zero bucket count, since home_bucket will be zero and metadata[ 0 ] + // will be the empty placeholder. + // In that scenario, the zero buckets_mask triggers the below load-factor check. + if( !( table->metadata[ home_bucket ] & VT_IN_HOME_BUCKET_MASK ) ) + { + if( + // Load-factor check. + VT_UNLIKELY( table->key_count + 1 > VT_CAT( NAME, _bucket_count )( table ) * MAX_LOAD ) || + // Vacate the home bucket if it contains a key. + ( table->metadata[ home_bucket ] != VT_EMPTY && VT_UNLIKELY( !VT_CAT( NAME, _evict )( table, home_bucket ) ) ) + ) + return VT_CAT( NAME, _end_itr )(); + + table->buckets[ home_bucket ].key = key; + #ifdef VAL_TY + table->buckets[ home_bucket ].val = *val; + #endif + table->metadata[ home_bucket ] = hashfrag | VT_IN_HOME_BUCKET_MASK | VT_DISPLACEMENT_MASK; + + ++table->key_count; + + VT_CAT( NAME, _itr ) itr = { + table->buckets + home_bucket, + table->metadata + home_bucket, + table->metadata + table->buckets_mask + 1, // Iteration stopper (i.e. the first of the four excess metadata). + home_bucket + }; + return itr; + } + + // Case 2: The home bucket contains the beginning of a chain. + + // Optionally, check the existing chain. + if( !unique ) + { + size_t bucket = home_bucket; + while( true ) + { + if( + ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) == hashfrag && + VT_LIKELY( CMPR_FN( table->buckets[ bucket ].key, key ) ) + ) + { + if( replace ) + { + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ bucket ].key ); + #endif + table->buckets[ bucket ].key = key; + + #ifdef VAL_TY + #ifdef VAL_DTOR_FN + VAL_DTOR_FN( table->buckets[ bucket ].val ); + #endif + table->buckets[ bucket ].val = *val; + #endif + } + + VT_CAT( NAME, _itr ) itr = { + table->buckets + bucket, + table->metadata + bucket, + table->metadata + table->buckets_mask + 1, + home_bucket + }; + return itr; + } + + uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; + if( displacement == VT_DISPLACEMENT_MASK ) + break; + + bucket = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + } + } + + size_t empty; + uint16_t displacement; + if( + VT_UNLIKELY( + // Load-factor check. + table->key_count + 1 > VT_CAT( NAME, _bucket_count )( table ) * MAX_LOAD || + // Find the earliest empty bucket, per quadratic probing. + !VT_CAT( NAME, _find_first_empty )( table, home_bucket, &empty, &displacement ) + ) + ) + return VT_CAT( NAME, _end_itr )(); + + // Insert the new key (and value) in the empty bucket and link it to the chain. + + size_t prev = VT_CAT( NAME, _find_insert_location_in_chain )( table, home_bucket, displacement ); + + table->buckets[ empty ].key = key; + #ifdef VAL_TY + table->buckets[ empty ].val = *val; + #endif + table->metadata[ empty ] = hashfrag | ( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ); + table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | displacement; + + ++table->key_count; + + VT_CAT( NAME, _itr ) itr = { + table->buckets + empty, + table->metadata + empty, + table->metadata + table->buckets_mask + 1, + home_bucket + }; + return itr; +} + +// Resizes the bucket array. +// This function assumes that bucket_count is a power of two and large enough to accommodate all keys without violating +// the maximum load factor. +// Returns false in the case of allocation failure. +// As this function is called very rarely in _insert and _get_or_insert, ideally it should not be inlined into those +// functions. +// In testing, the no-inline approach showed a performance benefit when inserting existing keys (i.e. replacing). +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" // Silence warning about combining noinline with static inline. +__attribute__((noinline)) static inline +#elif defined( _MSC_VER ) +__declspec(noinline) static inline +#else +static inline +#endif +bool VT_CAT( NAME, _rehash )( NAME *table, size_t bucket_count ) +{ + // The attempt to resize the bucket array and rehash the keys must occur inside a loop that incrementally doubles the + // target bucket count because a failure could theoretically occur at any load factor due to the displacement limit. + while( true ) + { + NAME new_table = { + 0, + bucket_count - 1, + NULL, + NULL + #ifdef CTX_TY + , table->ctx + #endif + }; + + void *allocation = MALLOC_FN( + VT_CAT( NAME, _total_alloc_size )( &new_table ) + #ifdef CTX_TY + , &new_table.ctx + #endif + ); + + if( VT_UNLIKELY( !allocation ) ) + return false; + + new_table.buckets = (VT_CAT( NAME, _bucket ) *)allocation; + new_table.metadata = (uint16_t *)( (unsigned char *)allocation + VT_CAT( NAME, _metadata_offset )( &new_table ) ); + + memset( new_table.metadata, 0x00, ( bucket_count + 4 ) * sizeof( uint16_t ) ); + + // Iteration stopper at the end of the actual metadata array (i.e. the first of the four excess metadata). + new_table.metadata[ bucket_count ] = 0x01; + + for( size_t bucket = 0; bucket < VT_CAT( NAME, _bucket_count )( table ); ++bucket ) + if( table->metadata[ bucket ] != VT_EMPTY ) + { + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( + &new_table, + table->buckets[ bucket ].key, + #ifdef VAL_TY + &table->buckets[ bucket ].val, + #endif + true, + false + ); + + if( VT_UNLIKELY( VT_CAT( NAME, _is_end )( itr ) ) ) + break; + } + + // If a key could not be reinserted due to the displacement limit, double the bucket count and retry. + if( VT_UNLIKELY( new_table.key_count < table->key_count ) ) + { + FREE_FN( + new_table.buckets, + VT_CAT( NAME, _total_alloc_size )( &new_table ) + #ifdef CTX_TY + , &new_table.ctx + #endif + ); + + bucket_count *= 2; + continue; + } + + if( table->buckets_mask ) + FREE_FN( + table->buckets, + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + *table = new_table; + return true; + } +} +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Inserts a key, replacing the existing key if it already exists. +// This function wraps insert_raw in a loop that handles growing and rehashing the table if a new key cannot be inserted +// because of the maximum load factor or displacement limit constraints. +// Returns an iterator to the inserted key, or an end iterator in the case of allocation failure. +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + while( true ) + { + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( + table, + key, + #ifdef VAL_TY + &val, + #endif + false, + true + ); + + if( + // Lookup succeeded, in which case itr points to the found key. + VT_LIKELY( !VT_CAT( NAME, _is_end )( itr ) ) || + // Lookup failed and rehash also fails, in which case itr is an end iterator. + VT_UNLIKELY( + !VT_CAT( NAME, _rehash )( + table, table->buckets_mask ? VT_CAT( NAME, _bucket_count )( table ) * 2 : VT_MIN_NONZERO_BUCKET_COUNT + ) + ) + ) + return itr; + } +} + +// Same as NAME_insert, except that if the key already exists, no insertion occurs and the function returns an iterator +// to the existing key. +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get_or_insert )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + while( true ) + { + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( + table, + key, + #ifdef VAL_TY + &val, + #endif + false, + false + ); + + if( + // Lookup succeeded, in which case itr points to the found key. + VT_LIKELY( !VT_CAT( NAME, _is_end )( itr ) ) || + // Lookup failed and rehash also fails, in which case itr is an end iterator. + VT_UNLIKELY( + !VT_CAT( NAME, _rehash )( + table, table->buckets_mask ? VT_CAT( NAME, _bucket_count )( table ) * 2 : VT_MIN_NONZERO_BUCKET_COUNT + ) + ) + ) + return itr; + } +} + +// Returns an iterator pointing to the specified key, or an end iterator if the key does not exist. +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get )( const NAME *table, KEY_TY key ) +{ + uint64_t hash = HASH_FN( key ); + size_t home_bucket = hash & table->buckets_mask; + + // If the home bucket is empty or contains a key that does not belong there, then our key does not exist. + // This check also implicitly handles the case of a zero bucket count, since home_bucket will be zero and + // metadata[ 0 ] will be the empty placeholder. + if( !( table->metadata[ home_bucket ] & VT_IN_HOME_BUCKET_MASK ) ) + return VT_CAT( NAME, _end_itr )(); + + // Traverse the chain of keys belonging to the home bucket. + uint16_t hashfrag = vt_hashfrag( hash ); + size_t bucket = home_bucket; + while( true ) + { + if( + ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) == hashfrag && + VT_LIKELY( CMPR_FN( table->buckets[ bucket ].key, key ) ) + ) + { + VT_CAT( NAME, _itr ) itr = { + table->buckets + bucket, + table->metadata + bucket, + table->metadata + table->buckets_mask + 1, + home_bucket + }; + return itr; + } + + uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; + if( displacement == VT_DISPLACEMENT_MASK ) + return VT_CAT( NAME, _end_itr )(); + + bucket = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + } +} + +// Erases the key pointed to by the specified iterator. +// The erasure always occurs at the end of the chain to which the key belongs. +// If the key to be erased is not the last in the chain, it is swapped with the last so that erasure occurs at the end. +// This helps keep a chain's keys close to their home bucket for the sake of cache locality. +// Returns true if, in the case of iteration from first to end, NAME_next should now be called on the iterator to find +// the next key. +// This return value is necessary because at the iterator location, the erasure could result in an empty bucket, a +// bucket containing a moved key already visited during the iteration, or a bucket containing a moved key not yet +// visited. +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase_itr_raw )( NAME *table, VT_CAT( NAME, _itr ) itr ) +{ + --table->key_count; + size_t itr_bucket = itr.metadatum - table->metadata; + + // For now, we only call the value's destructor because the key may need to be hashed below to determine the home + // bucket. + #ifdef VAL_DTOR_FN + VAL_DTOR_FN( table->buckets[ itr_bucket ].val ); + #endif + + // Case 1: The key is the only one in its chain, so just remove it. + if( + table->metadata[ itr_bucket ] & VT_IN_HOME_BUCKET_MASK && + ( table->metadata[ itr_bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK + ) + { + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ itr_bucket ].key ); + #endif + table->metadata[ itr_bucket ] = VT_EMPTY; + return true; + } + + // Case 2 and 3 require that we know the key's home bucket, which the iterator may not have recorded. + if( itr.home_bucket == SIZE_MAX ) + { + if( table->metadata[ itr_bucket ] & VT_IN_HOME_BUCKET_MASK ) + itr.home_bucket = itr_bucket; + else + itr.home_bucket = HASH_FN( table->buckets[ itr_bucket ].key ) & table->buckets_mask; + } + + // The key can now be safely destructed for cases 2 and 3. + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ itr_bucket ].key ); + #endif + + // Case 2: The key is the last in a multi-key chain. + // Traverse the chain from the beginning and find the penultimate key. + // Then disconnect the key and erase. + if( ( table->metadata[ itr_bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) + { + size_t bucket = itr.home_bucket; + while( true ) + { + uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; + size_t next = ( itr.home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; + if( next == itr_bucket ) + { + table->metadata[ bucket ] |= VT_DISPLACEMENT_MASK; + table->metadata[ itr_bucket ] = VT_EMPTY; + return true; + } + + bucket = next; + } + } + + // Case 3: The chain has multiple keys, and the key is not the last one. + // Traverse the chain from the key to be erased and find the last and penultimate keys. + // Disconnect the last key from the chain, and swap it with the key to erase. + size_t bucket = itr_bucket; + while( true ) + { + size_t prev = bucket; + bucket = ( itr.home_bucket + vt_quadratic( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ) ) & + table->buckets_mask; + + if( ( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) + { + table->buckets[ itr_bucket ] = table->buckets[ bucket ]; + + table->metadata[ itr_bucket ] = ( table->metadata[ itr_bucket ] & ~VT_HASH_FRAG_MASK ) | ( + table->metadata[ bucket ] & VT_HASH_FRAG_MASK ); + + table->metadata[ prev ] |= VT_DISPLACEMENT_MASK; + table->metadata[ bucket ] = VT_EMPTY; + + // Whether the iterator should be advanced depends on whether the key moved to the iterator bucket came from + // before or after that bucket. + // In the former case, the iteration would already have hit the moved key, so the iterator should still be + // advanced. + if( bucket > itr_bucket ) + return false; + + return true; + } + } +} + +// Erases the specified key, if it exists. +// Returns true if a key was erased. +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase )( NAME *table, KEY_TY key ) +{ + VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _get)( table, key ); + if( VT_CAT( NAME, _is_end )( itr ) ) + return false; + + VT_CAT( NAME, _erase_itr_raw )( table, itr ); + return true; +} + +// Finds the first occupied bucket at or after the bucket pointed to by itr. +// This function scans four buckets at a time, ideally using intrinsics. +static inline void VT_CAT( NAME, _fast_forward )( VT_CAT( NAME, _itr ) *itr ) +{ + while( true ) + { + uint64_t metadata; + memcpy( &metadata, itr->metadatum, sizeof( uint64_t ) ); + if( metadata ) + { + int offset = vt_first_nonzero_uint16( metadata ); + itr->data += offset; + itr->metadatum += offset; + itr->home_bucket = SIZE_MAX; + return; + } + + itr->data += 4; + itr->metadatum += 4; + } +} + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _next )( VT_CAT( NAME, _itr ) itr ) +{ + ++itr.data; + ++itr.metadatum; + VT_CAT( NAME, _fast_forward )( &itr ); + return itr; +} + +// Returns the minimum bucket count required to accommodate a certain number of keys, which is governed by the maximum +// load factor. +static inline size_t VT_CAT( NAME, _min_bucket_count_for_size )( size_t size ) +{ + if( size == 0 ) + return 0; + + // Round up to a power of two. + size_t bucket_count = VT_MIN_NONZERO_BUCKET_COUNT; + while( size > bucket_count * MAX_LOAD ) + bucket_count *= 2; + + return bucket_count; +} + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _reserve )( NAME *table, size_t size ) +{ + size_t bucket_count = VT_CAT( NAME, _min_bucket_count_for_size )( size ); + + if( bucket_count <= VT_CAT( NAME, _bucket_count )( table ) ) + return true; + + return VT_CAT( NAME, _rehash )( table, bucket_count ); +} + +VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _shrink )( NAME *table ) +{ + size_t bucket_count = VT_CAT( NAME, _min_bucket_count_for_size )( table->key_count ); + + if( bucket_count == VT_CAT( NAME, _bucket_count )( table ) ) // Shrink unnecessary. + return true; + + if( bucket_count == 0 ) + { + FREE_FN( + table->buckets, + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + table->buckets_mask = 0x0000000000000000ull; + table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; + return true; + } + + return VT_CAT( NAME, _rehash )( table, bucket_count ); +} + +VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _first )( const NAME *table ) +{ + if( !table->key_count ) + return VT_CAT( NAME, _end_itr )(); + + VT_CAT( NAME, _itr ) itr = { table->buckets, table->metadata, table->metadata + table->buckets_mask + 1, SIZE_MAX }; + VT_CAT( NAME, _fast_forward )( &itr ); + return itr; +} + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _clear )( NAME *table ) +{ + if( !table->key_count ) + return; + + for( size_t i = 0; i < VT_CAT( NAME, _bucket_count )( table ); ++i ) + { + if( table->metadata[ i ] != VT_EMPTY ) + { + #ifdef KEY_DTOR_FN + KEY_DTOR_FN( table->buckets[ i ].key ); + #endif + #ifdef VAL_DTOR_FN + VAL_DTOR_FN( table->buckets[ i ].val ); + #endif + } + + table->metadata[ i ] = VT_EMPTY; + } + + table->key_count = 0; +} + +VT_API_FN_QUALIFIERS void VT_CAT( NAME, _cleanup )( NAME *table ) +{ + if( !table->buckets_mask ) + return; + + #if defined( KEY_DTOR_FN ) || defined( VAL_DTOR_FN ) + VT_CAT( NAME, _clear )( table ); + #endif + + FREE_FN( + table->buckets, + VT_CAT( NAME, _total_alloc_size )( table ) + #ifdef CTX_TY + , &table->ctx + #endif + ); + + VT_CAT( NAME, _init )( + table + #ifdef CTX_TY + , table->ctx + #endif + ); +} + +#endif + +/*--------------------------------------------------------------------------------------------------------------------*/ +/* Wrapper types and functions for the C11 generic API */ +/*--------------------------------------------------------------------------------------------------------------------*/ + +#if defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 201112L && \ + !defined( IMPLEMENTATION_MODE ) && \ + !defined( VT_NO_C11_GENERIC_API ) \ + +typedef NAME VT_CAT( vt_table_, VT_TEMPLATE_COUNT ); +typedef VT_CAT( NAME, _itr ) VT_CAT( vt_table_itr_, VT_TEMPLATE_COUNT ); + +static inline void VT_CAT( vt_init_, VT_TEMPLATE_COUNT )( + NAME *table + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + VT_CAT( NAME, _init )( + table + #ifdef CTX_TY + , ctx + #endif + ); +} + +static inline bool VT_CAT( vt_init_clone_, VT_TEMPLATE_COUNT )( + NAME *table, + const NAME* source + #ifdef CTX_TY + , CTX_TY ctx + #endif +) +{ + return VT_CAT( NAME, _init_clone )( + table, + source + #ifdef CTX_TY + , ctx + #endif + ); +} + +static inline size_t VT_CAT( vt_size_, VT_TEMPLATE_COUNT )( const NAME *table ) +{ + return VT_CAT( NAME, _size )( table ); +} + +static inline size_t VT_CAT( vt_bucket_count_, VT_TEMPLATE_COUNT )( const NAME *table ) +{ + return VT_CAT( NAME, _bucket_count )( table ); +} + +static inline bool VT_CAT( vt_is_end_, VT_TEMPLATE_COUNT )( VT_CAT( NAME, _itr ) itr ) +{ + return VT_CAT( NAME, _is_end )( itr ); +} + +static inline VT_CAT( NAME, _itr ) VT_CAT( vt_insert_, VT_TEMPLATE_COUNT )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + return VT_CAT( NAME, _insert )( + table, + key + #ifdef VAL_TY + , val + #endif + ); +} + +static inline VT_CAT( NAME, _itr ) VT_CAT( vt_get_or_insert_, VT_TEMPLATE_COUNT )( + NAME *table, + KEY_TY key + #ifdef VAL_TY + , VAL_TY val + #endif +) +{ + return VT_CAT( NAME, _get_or_insert )( + table, + key + #ifdef VAL_TY + , val + #endif + ); +} + +static inline VT_CAT( NAME, _itr ) VT_CAT( vt_get_, VT_TEMPLATE_COUNT )( const NAME *table, KEY_TY key ) +{ + return VT_CAT( NAME, _get )( table, key ); +} + +static inline bool VT_CAT( vt_erase_, VT_TEMPLATE_COUNT )( NAME *table, KEY_TY key ) +{ + return VT_CAT( NAME, _erase )( table, key ); +} + +static inline VT_CAT( NAME, _itr ) VT_CAT( vt_next_, VT_TEMPLATE_COUNT )( VT_CAT( NAME, _itr ) itr ) +{ + return VT_CAT( NAME, _next )( itr ); +} + +static inline VT_CAT( NAME, _itr ) VT_CAT( vt_erase_itr_, VT_TEMPLATE_COUNT )( NAME *table, VT_CAT( NAME, _itr ) itr ) +{ + return VT_CAT( NAME, _erase_itr )( table, itr ); +} + +static inline bool VT_CAT( vt_reserve_, VT_TEMPLATE_COUNT )( NAME *table, size_t bucket_count ) +{ + return VT_CAT( NAME, _reserve )( table, bucket_count ); +} + +static inline bool VT_CAT( vt_shrink_, VT_TEMPLATE_COUNT )( NAME *table ) +{ + return VT_CAT( NAME, _shrink )( table ); +} + +static inline VT_CAT( NAME, _itr ) VT_CAT( vt_first_, VT_TEMPLATE_COUNT )( const NAME *table ) +{ + return VT_CAT( NAME, _first )( table ); +} + +static inline void VT_CAT( vt_clear_, VT_TEMPLATE_COUNT )( NAME *table ) +{ + VT_CAT( NAME, _clear )( table ); +} + +static inline void VT_CAT( vt_cleanup_, VT_TEMPLATE_COUNT )( NAME *table ) +{ + VT_CAT( NAME, _cleanup )( table ); +} + +// Increment the template counter. +#if VT_TEMPLATE_COUNT_D1 == 0 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 1 +#elif VT_TEMPLATE_COUNT_D1 == 1 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 2 +#elif VT_TEMPLATE_COUNT_D1 == 2 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 3 +#elif VT_TEMPLATE_COUNT_D1 == 3 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 4 +#elif VT_TEMPLATE_COUNT_D1 == 4 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 5 +#elif VT_TEMPLATE_COUNT_D1 == 5 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 6 +#elif VT_TEMPLATE_COUNT_D1 == 6 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 7 +#elif VT_TEMPLATE_COUNT_D1 == 7 +#undef VT_TEMPLATE_COUNT_D1 +#define VT_TEMPLATE_COUNT_D1 0 +#if VT_TEMPLATE_COUNT_D2 == 0 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 1 +#elif VT_TEMPLATE_COUNT_D2 == 1 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 2 +#elif VT_TEMPLATE_COUNT_D2 == 2 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 3 +#elif VT_TEMPLATE_COUNT_D2 == 3 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 4 +#elif VT_TEMPLATE_COUNT_D2 == 4 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 5 +#elif VT_TEMPLATE_COUNT_D2 == 5 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 6 +#elif VT_TEMPLATE_COUNT_D2 == 6 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 7 +#elif VT_TEMPLATE_COUNT_D2 == 7 +#undef VT_TEMPLATE_COUNT_D2 +#define VT_TEMPLATE_COUNT_D2 0 +#if VT_TEMPLATE_COUNT_D3 == 0 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 1 +#elif VT_TEMPLATE_COUNT_D3 == 1 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 2 +#elif VT_TEMPLATE_COUNT_D3 == 2 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 3 +#elif VT_TEMPLATE_COUNT_D3 == 3 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 4 +#elif VT_TEMPLATE_COUNT_D3 == 4 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 5 +#elif VT_TEMPLATE_COUNT_D3 == 5 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 6 +#elif VT_TEMPLATE_COUNT_D3 == 6 +#undef VT_TEMPLATE_COUNT_D3 +#define VT_TEMPLATE_COUNT_D3 7 +#elif VT_TEMPLATE_COUNT_D3 == 7 +#error Sorry, the number of template instances is limited to 511. Define VT_NO_C11_GENERIC_API globally and use the \ +C99 prefixed function API to circumvent this restriction. +#endif +#endif +#endif + +#endif + +#undef NAME +#undef KEY_TY +#undef VAL_TY +#undef HASH_FN +#undef CMPR_FN +#undef MAX_LOAD +#undef KEY_DTOR_FN +#undef VAL_DTOR_FN +#undef CTX_TY +#undef MALLOC_FN +#undef FREE_FN +#undef HEADER_MODE +#undef IMPLEMENTATION_MODE +#undef VT_API_FN_QUALIFIERS diff --git a/vendor/box3d/src/src/weld_joint.c b/vendor/box3d/src/src/weld_joint.c new file mode 100644 index 000000000..611b714fa --- /dev/null +++ b/vendor/box3d/src/src/weld_joint.c @@ -0,0 +1,314 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "joint.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +void b3WeldJoint_SetLinearHertz( b3JointId jointId, float hertz ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WeldJointSetLinearHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + base->weldJoint.linearHertz = hertz; +} + +float b3WeldJoint_GetLinearHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + return base->weldJoint.linearHertz; +} + +void b3WeldJoint_SetLinearDampingRatio( b3JointId jointId, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WeldJointSetLinearDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + base->weldJoint.linearDampingRatio = dampingRatio; +} + +float b3WeldJoint_GetLinearDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + return base->weldJoint.linearDampingRatio; +} + +void b3WeldJoint_SetAngularHertz( b3JointId jointId, float hertz ) +{ + B3_ASSERT( b3IsValidFloat( hertz ) && hertz >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WeldJointSetAngularHertz, jointId, hertz ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + base->weldJoint.angularHertz = hertz; +} + +float b3WeldJoint_GetAngularHertz( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + return base->weldJoint.angularHertz; +} + +void b3WeldJoint_SetAngularDampingRatio( b3JointId jointId, float dampingRatio ) +{ + B3_ASSERT( b3IsValidFloat( dampingRatio ) && dampingRatio >= 0.0f ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WeldJointSetAngularDampingRatio, jointId, dampingRatio ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + base->weldJoint.angularDampingRatio = dampingRatio; +} + +float b3WeldJoint_GetAngularDampingRatio( b3JointId jointId ) +{ + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_weldJoint ); + return base->weldJoint.angularDampingRatio; +} + +b3Vec3 b3GetWeldJointForce( b3World* world, b3JointSim* base ) +{ + b3Vec3 force = b3MulSV( world->inv_h, base->weldJoint.linearImpulse ); + return force; +} + +b3Vec3 b3GetWeldJointTorque( b3World* world, b3JointSim* base ) +{ + return b3MulSV( world->inv_h, base->weldJoint.angularImpulse ); +} + +void b3PrepareWeldJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_weldJoint ); + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, base->bodyIdA ); + b3Body* bodyB = b3Array_Get( world->bodies, base->bodyIdB ); + + B3_ASSERT( bodyB->setIndex == b3_awakeSet ); + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3WeldJoint* joint = &base->weldJoint; + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + joint->angularMass = b3InvertMatrix( invInertiaSum ); + + if ( joint->linearHertz == 0.0f ) + { + joint->linearSpring = base->constraintSoftness; + } + else + { + joint->linearSpring = b3MakeSoft( joint->linearHertz, joint->linearDampingRatio, context->h ); + } + + if ( joint->angularHertz == 0.0f ) + { + joint->angularSpring = base->constraintSoftness; + } + else + { + joint->angularSpring = b3MakeSoft( joint->angularHertz, joint->angularDampingRatio, context->h ); + } + + if ( context->enableWarmStarting == false ) + { + joint->linearImpulse = b3Vec3_zero; + joint->angularImpulse = b3Vec3_zero; + } +} + +void b3WarmStartWeldJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_weldJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3WeldJoint* joint = &base->weldJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + vA = b3MulSub( vA, mA, joint->linearImpulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Add( b3Cross( rA, joint->linearImpulse ), joint->angularImpulse ) ) ); + + vB = b3MulAdd( vB, mB, joint->linearImpulse ); + wB = b3Add( wB, b3MulMV( iB, b3Add( b3Cross( rB, joint->linearImpulse ), joint->angularImpulse ) ) ); + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3SolveWeldJoint( b3JointSim* base, b3StepContext* context, bool useBias ) +{ + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3WeldJoint* joint = &base->weldJoint; + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + bool fixedRotation = base->fixedRotation; + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the rotation angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + // angular constraint + if ( fixedRotation == false ) + { + b3Vec3 bias = b3Vec3_zero; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias || joint->angularHertz > 0.0f ) + { + b3Quat targetQuat = b3Quat_identity; + b3Vec3 deltaRotation = b3DeltaQuatToRotation( relQ, targetQuat ); + b3Vec3 c = b3Neg( b3RotateVector( quatA, deltaRotation ) ); + + bias = b3MulSV( joint->angularSpring.biasRate, c ); + massScale = joint->angularSpring.massScale; + impulseScale = joint->angularSpring.impulseScale; + } + + b3Vec3 cdot = b3Sub( wB, wA ); + b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b3MulMV( joint->angularMass, b3Add( cdot, bias ) ) ), impulseScale, joint->angularImpulse ); + joint->angularImpulse = b3Add( joint->angularImpulse, impulse ); + + wA = b3Sub( wA, b3MulMV( iA, impulse ) ); + wB = b3Add( wB, b3MulMV( iB, impulse ) ); + } + + // linear constraint + { + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 cdot = b3Sub( b3Add( vB, b3Cross( wB, rB ) ), b3Add( vA, b3Cross( wA, rA ) ) ); + + b3Vec3 bias = b3Vec3_zero; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias || joint->linearHertz > 0.0f ) + { + b3Vec3 dcA = stateA->deltaPosition; + b3Vec3 dcB = stateB->deltaPosition; + + b3Vec3 separation = b3Add( b3Add( b3Sub( dcB, dcA ), b3Sub( rB, rA ) ), joint->deltaCenter ); + + bias = b3MulSV( joint->linearSpring.biasRate, separation ); + massScale = joint->linearSpring.massScale; + impulseScale = joint->linearSpring.impulseScale; + } + + //// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + b3Matrix3 sA = b3Skew( rA ); + b3Matrix3 sB = b3Skew( rB ); + b3Matrix3 kA = b3MulMM( sA, b3MulMM( base->invIA, sA ) ); + b3Matrix3 kB = b3MulMM( sB, b3MulMM( base->invIB, sB ) ); + b3Matrix3 k = b3NegateMat3( b3AddMM( kA, kB ) ); + k.cx.x += mA + mB; + k.cy.y += mA + mB; + k.cz.z += mA + mB; + + b3Vec3 b = b3Solve3( k, b3Add( cdot, bias ) ); + + b3Vec3 impulse = b3MulSub( b3MulSV( -massScale, b ), impulseScale, joint->linearImpulse ); + joint->linearImpulse = b3Add( joint->linearImpulse, impulse ); + + vA = b3MulSub( vA, mA, impulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Cross( rA, impulse ) ) ); + vB = b3MulAdd( vB, mB, impulse ); + wB = b3Add( wB, b3MulMV( iB, b3Cross( rB, impulse ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +void b3DrawWeldJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ) +{ + b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA ); + b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB ); + + b3Vec3 extents = { 0.1f * scale, 0.05f * scale, 0.025f * scale }; + draw->DrawBoxFcn( extents, frameA, b3_colorDarkOrange, draw->context ); + draw->DrawBoxFcn( extents, frameB, b3_colorDarkCyan, draw->context ); +} diff --git a/vendor/box3d/src/src/wheel_joint.c b/vendor/box3d/src/src/wheel_joint.c new file mode 100644 index 000000000..05665e9f0 --- /dev/null +++ b/vendor/box3d/src/src/wheel_joint.c @@ -0,0 +1,1105 @@ +// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-License-Identifier: MIT + +#include "body.h" +#include "core.h" +#include "joint.h" +#include "math_internal.h" +#include "physics_world.h" +#include "solver.h" +#include "solver_set.h" +#include "recording.h" + +// needed for dll export +#include "box3d/box3d.h" + +void b3WheelJoint_EnableSuspension( b3JointId jointId, bool enableSpring ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointEnableSuspension, jointId, enableSpring ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + + if ( enableSpring != joint->wheelJoint.enableSuspensionSpring ) + { + joint->wheelJoint.enableSuspensionSpring = enableSpring; + joint->wheelJoint.suspensionSpringImpulse = 0.0f; + } +} + +bool b3WheelJoint_IsSuspensionEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.enableSuspensionSpring; +} + +void b3WheelJoint_SetSuspensionHertz( b3JointId jointId, float hertz ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSuspensionHertz, jointId, hertz ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.suspensionHertz = hertz; +} + +float b3WheelJoint_GetSuspensionHertz( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.suspensionHertz; +} + +void b3WheelJoint_SetSuspensionDampingRatio( b3JointId jointId, float dampingRatio ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSuspensionDampingRatio, jointId, dampingRatio ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.suspensionDampingRatio = dampingRatio; +} + +float b3WheelJoint_GetSuspensionDampingRatio( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.suspensionDampingRatio; +} + +void b3WheelJoint_EnableSuspensionLimit( b3JointId jointId, bool enableLimit ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointEnableSuspensionLimit, jointId, enableLimit ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + if ( joint->wheelJoint.enableSuspensionLimit != enableLimit ) + { + joint->wheelJoint.lowerSuspensionImpulse = 0.0f; + joint->wheelJoint.upperSuspensionImpulse = 0.0f; + joint->wheelJoint.enableSuspensionLimit = enableLimit; + } +} + +bool b3WheelJoint_IsSuspensionLimitEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.enableSuspensionLimit; +} + +float b3WheelJoint_GetLowerSuspensionLimit( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.lowerSuspensionLimit; +} + +float b3WheelJoint_GetUpperSuspensionLimit( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.upperSuspensionLimit; +} + +void b3WheelJoint_SetSuspensionLimits( b3JointId jointId, float lower, float upper ) +{ + B3_ASSERT( lower <= upper ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSuspensionLimits, jointId, lower, upper ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + if ( lower != joint->wheelJoint.lowerSuspensionLimit || upper != joint->wheelJoint.upperSuspensionLimit ) + { + joint->wheelJoint.lowerSuspensionLimit = lower; + joint->wheelJoint.upperSuspensionLimit = upper; + joint->wheelJoint.lowerSuspensionImpulse = 0.0f; + joint->wheelJoint.upperSuspensionImpulse = 0.0f; + } +} + +void b3WheelJoint_EnableSpinMotor( b3JointId jointId, bool enableMotor ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointEnableSpinMotor, jointId, enableMotor ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + if ( joint->wheelJoint.enableSpinMotor != enableMotor ) + { + joint->wheelJoint.spinImpulse = 0.0f; + joint->wheelJoint.enableSpinMotor = enableMotor; + } +} + +bool b3WheelJoint_IsSpinMotorEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.enableSpinMotor; +} + +void b3WheelJoint_SetSpinMotorSpeed( b3JointId jointId, float motorSpeed ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSpinMotorSpeed, jointId, motorSpeed ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.spinSpeed = motorSpeed; +} + +float b3WheelJoint_GetSpinMotorSpeed( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.spinSpeed; +} + +void b3WheelJoint_SetMaxSpinTorque( b3JointId jointId, float torque ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetMaxSpinTorque, jointId, torque ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.maxSpinTorque = torque; +} + +float b3WheelJoint_GetMaxSpinTorque( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.maxSpinTorque; +} + +void b3WheelJoint_EnableSteering( b3JointId jointId, bool flag ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointEnableSteering, jointId, flag ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + if ( joint->wheelJoint.enableSteering != flag ) + { + joint->wheelJoint.angularImpulse = (b3Vec2){ 0.0f, 0.0f }; + joint->wheelJoint.enableSteering = flag; + } +} + +bool b3WheelJoint_IsSteeringEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.enableSteering; +} + +void b3WheelJoint_SetSteeringHertz( b3JointId jointId, float hertz ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSteeringHertz, jointId, hertz ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.steeringHertz = hertz; +} + +float b3WheelJoint_GetSteeringHertz( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.steeringHertz; +} + +void b3WheelJoint_SetSteeringDampingRatio( b3JointId jointId, float dampingRatio ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSteeringDampingRatio, jointId, dampingRatio ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.steeringDampingRatio = dampingRatio; +} + +float b3WheelJoint_GetSteeringDampingRatio( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.steeringDampingRatio; +} + +void b3WheelJoint_SetMaxSteeringTorque( b3JointId jointId, float maxTorque ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetMaxSteeringTorque, jointId, maxTorque ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.maxSteeringTorque = maxTorque; +} + +float b3WheelJoint_GetMaxSteeringTorque( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.maxSteeringTorque; +} + +void b3WheelJoint_EnableSteeringLimit( b3JointId jointId, bool flag ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointEnableSteeringLimit, jointId, flag ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + if ( joint->wheelJoint.enableSteeringLimit != flag ) + { + joint->wheelJoint.lowerSteeringImpulse = 0.0f; + joint->wheelJoint.upperSteeringImpulse = 0.0f; + joint->wheelJoint.enableSteeringLimit = flag; + } +} + +bool b3WheelJoint_IsSteeringLimitEnabled( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.enableSteeringLimit; +} + +float b3WheelJoint_GetLowerSteeringLimit( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.lowerSteeringLimit; +} + +float b3WheelJoint_GetUpperSteeringLimit( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.upperSteeringLimit; +} + +void b3WheelJoint_SetSteeringLimits( b3JointId jointId, float lowerRadians, float upperRadians ) +{ + B3_ASSERT( lowerRadians <= upperRadians ); + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetSteeringLimits, jointId, lowerRadians, upperRadians ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.lowerSteeringLimit = lowerRadians; + joint->wheelJoint.upperSteeringLimit = upperRadians; +} + +void b3WheelJoint_SetTargetSteeringAngle( b3JointId jointId, float radians ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + B3_REC( world, WheelJointSetTargetSteeringAngle, jointId, radians ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + joint->wheelJoint.targetSteeringAngle = radians; +} + +float b3WheelJoint_GetTargetSteeringAngle( b3JointId jointId ) +{ + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return joint->wheelJoint.targetSteeringAngle; +} + +float b3WheelJoint_GetSpinSpeed( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + + int idA = base->bodyIdA; + int idB = base->bodyIdB; + + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + int localIndexB = bodyB->localIndex; + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + b3Quat quatB = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + b3Vec3 spinAxis = b3RotateVector( quatB, b3Vec3_axisZ ); + + b3Vec3 wA = b3Vec3_zero; + b3BodyState* stateA = b3GetBodyState( world, bodyA ); + if ( stateA != NULL ) + { + wA = stateA->angularVelocity; + } + + b3Vec3 wB = b3Vec3_zero; + b3BodyState* stateB = b3GetBodyState( world, bodyB ); + if ( stateB != NULL ) + { + wB = stateB->angularVelocity; + } + + float speed = b3Dot( b3Sub( wB, wA ), spinAxis ); + return speed; +} + +float b3WheelJoint_GetSpinTorque( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return world->inv_h * joint->wheelJoint.spinImpulse; +} + +float b3WheelJoint_GetSteeringAngle( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* base = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + + int idA = base->bodyIdA; + int idB = base->bodyIdB; + + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + b3Quat quatA = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + b3Quat quatB = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + + b3Matrix3 matrixA = b3MakeMatrixFromQuat( quatA ); + b3Matrix3 matrixB = b3MakeMatrixFromQuat( quatB ); + + // Twist around x-axis + float cs = b3Dot( matrixB.cz, matrixA.cz ); + float ss = -b3Dot( matrixB.cz, matrixA.cy ); + + return b3Atan2( ss, cs ); +} + +float b3WheelJoint_GetSteeringTorque( b3JointId jointId ) +{ + b3World* world = b3GetWorld( jointId.world0 ); + b3JointSim* joint = b3GetJointSimCheckType( jointId, b3_wheelJoint ); + return world->inv_h * joint->wheelJoint.steeringSpringImpulse; +} + +b3Vec3 b3GetWheelJointForce( b3World* world, b3JointSim* base ) +{ + b3WorldTransform transformA = b3GetBodyTransform( world, base->bodyIdA ); + b3WheelJoint* joint = &base->wheelJoint; + + // impulse in joint space + b3Vec3 impulse = { + joint->linearImpulse.x, + joint->linearImpulse.y, + joint->lowerSuspensionLimit + joint->upperSuspensionImpulse + joint->suspensionSpringImpulse, + }; + + // convert impulse to force + b3Vec3 force = b3MulSV( world->inv_h, impulse ); + + // convert to body space + force = b3RotateVector( base->localFrameA.q, force ); + + // convert to world space + force = b3RotateVector( transformA.q, force ); + return force; +} + +b3Vec3 b3GetWheelJointTorque( b3World* world, b3JointSim* base ) +{ + B3_ASSERT( base->type == b3_wheelJoint ); + + // chase body id to the solver set where the body lives + int idA = base->bodyIdA; + // int idB = base->bodyIdB; + + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + // b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + // b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + // int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + // b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + b3Quat qA = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + + b3Matrix3 matrixA = b3MakeMatrixFromQuat( qA ); + + return b3MulSV( world->inv_h * base->wheelJoint.spinImpulse, matrixA.cz ); +} + +// See constraints.pdf + +void b3PrepareWheelJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_wheelJoint ); + + // chase body id to the solver set where the body lives + int idA = base->bodyIdA; + int idB = base->bodyIdB; + + b3World* world = context->world; + + b3Body* bodyA = b3Array_Get( world->bodies, idA ); + b3Body* bodyB = b3Array_Get( world->bodies, idB ); + + B3_ASSERT( bodyA->setIndex == b3_awakeSet || bodyB->setIndex == b3_awakeSet ); + b3SolverSet* setA = b3Array_Get( world->solverSets, bodyA->setIndex ); + b3SolverSet* setB = b3Array_Get( world->solverSets, bodyB->setIndex ); + + int localIndexA = bodyA->localIndex; + int localIndexB = bodyB->localIndex; + + b3BodySim* bodySimA = b3Array_Get( setA->bodySims, localIndexA ); + b3BodySim* bodySimB = b3Array_Get( setB->bodySims, localIndexB ); + + base->invMassA = bodySimA->invMass; + base->invMassB = bodySimB->invMass; + base->invIA = bodySimA->invInertiaWorld; + base->invIB = bodySimB->invInertiaWorld; + + b3Matrix3 invInertiaSum = b3AddMM( base->invIA, base->invIB ); + base->fixedRotation = b3Det( invInertiaSum ) < 1000.0f * FLT_MIN; + + b3WheelJoint* joint = &base->wheelJoint; + + joint->indexA = bodyA->setIndex == b3_awakeSet ? localIndexA : B3_NULL_INDEX; + joint->indexB = bodyB->setIndex == b3_awakeSet ? localIndexB : B3_NULL_INDEX; + + // Compute joint anchor frames with world space rotation, relative to center of mass + joint->frameA.q = b3MulQuat( bodySimA->transform.q, base->localFrameA.q ); + joint->frameA.p = b3RotateVector( bodySimA->transform.q, b3Sub( base->localFrameA.p, bodySimA->localCenter ) ); + joint->frameB.q = b3MulQuat( bodySimB->transform.q, base->localFrameB.q ); + joint->frameB.p = b3RotateVector( bodySimB->transform.q, b3Sub( base->localFrameB.p, bodySimB->localCenter ) ); + + // Compute the initial center delta. Incremental position updates are relative to this. + joint->deltaCenter = b3SubPos( bodySimB->center, bodySimA->center ); + + b3Vec3 rA = joint->frameA.p; + b3Vec3 rB = joint->frameB.p; + + b3Matrix3 matrixA = b3MakeMatrixFromQuat( joint->frameA.q ); + b3Matrix3 matrixB = b3MakeMatrixFromQuat( joint->frameB.q ); + + // todo use fresh effective masses in the sub-step to avoid divergence like I saw for the prismatic joint + + { + b3Vec3 suspensionAxis = matrixA.cx; + b3Vec3 rAn = b3Cross( rA, suspensionAxis ); + b3Vec3 rBn = b3Cross( rB, suspensionAxis ); + + float k = base->invMassA + base->invMassB + b3Dot( rAn, b3MulMV( base->invIA, rAn ) ) + + b3Dot( rBn, b3MulMV( base->invIB, rBn ) ); + joint->suspensionMass = k > 0.0f ? 1.0f / k : 0.0f; + } + + joint->suspensionSoftness = b3MakeSoft( joint->suspensionHertz, joint->suspensionDampingRatio, context->h ); + joint->steeringSoftness = b3MakeSoft( joint->steeringHertz, joint->steeringDampingRatio, context->h ); + + { + // Rotation axis is the z-axis of body A. + b3Vec3 spinAxis = matrixB.cz; + float k = b3Dot( spinAxis, b3MulMV( invInertiaSum, spinAxis ) ); + joint->spinMass = k > 0.0f ? 1.0f / k : 0.0f; + } + + { + // Twist constraint around x-axis + float cs = b3Dot( matrixB.cz, matrixA.cz ); + float ss = -b3Dot( matrixB.cz, matrixA.cy ); + float den = cs * cs + ss * ss; + den = den > 0.0f ? 1.0f / den : 0.0f; + b3Vec3 steeringAxis = + b3MulSV( den, b3Cross( matrixB.cz, b3Sub( b3MulSV( -cs, matrixA.cy ), b3MulSV( ss, matrixA.cz ) ) ) ); + + float k = b3Dot( steeringAxis, b3MulMV( invInertiaSum, steeringAxis ) ); + joint->steeringMass = k > 0.0f ? 1.0f / k : 0.0f; + } + + if ( context->enableWarmStarting == false ) + { + joint->linearImpulse = (b3Vec2){ 0.0f, 0.0f }; + joint->angularImpulse = (b3Vec2){ 0.0f, 0.0f }; + joint->spinImpulse = 0.0f; + joint->suspensionSpringImpulse = 0.0f; + joint->lowerSuspensionImpulse = 0.0f; + joint->upperSuspensionImpulse = 0.0f; + joint->steeringSpringImpulse = 0.0f; + joint->lowerSteeringImpulse = 0.0f; + joint->upperSteeringImpulse = 0.0f; + } +} + +void b3WarmStartWheelJoint( b3JointSim* base, b3StepContext* context ) +{ + B3_ASSERT( base->type == b3_wheelJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3WheelJoint* joint = &base->wheelJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Vec3 d = b3Add( b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), joint->deltaCenter ), b3Sub( rB, rA ) ); + + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the rotation angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Matrix3 matrixA = b3MakeMatrixFromQuat( quatA ); + b3Matrix3 matrixB = b3MakeMatrixFromQuat( quatB ); + + b3Vec3 sAx = b3Cross( b3Add( d, rA ), matrixA.cx ); + b3Vec3 sBx = b3Cross( rB, matrixA.cx ); + b3Vec3 sAy = b3Cross( b3Add( d, rA ), matrixA.cy ); + b3Vec3 sBy = b3Cross( rB, matrixA.cy ); + b3Vec3 sAz = b3Cross( b3Add( d, rA ), matrixA.cz ); + b3Vec3 sBz = b3Cross( rB, matrixA.cz ); + + float suspensionImpulse = joint->suspensionSpringImpulse + joint->lowerSuspensionImpulse - joint->upperSuspensionImpulse; + + float linearImpulseY = joint->linearImpulse.x; + float linearImpulseZ = joint->linearImpulse.y; + float angularImpulseX = joint->angularImpulse.x; + float angularImpulseY = joint->angularImpulse.y; + + b3Vec3 linearImpulse = b3Blend3( suspensionImpulse, matrixA.cx, linearImpulseY, matrixA.cy, linearImpulseZ, matrixA.cz ); + b3Vec3 angularImpulseA = b3Blend3( suspensionImpulse, sAx, linearImpulseY, sAy, linearImpulseZ, sAz ); + b3Vec3 angularImpulseB = b3Blend3( suspensionImpulse, sBx, linearImpulseY, sBy, linearImpulseZ, sBz ); + b3Vec3 angularImpulse = b3MulSV( joint->spinImpulse, matrixA.cz ); + + b3Vec3 spinAxis = matrixB.cz; + + if ( joint->enableSteering ) + { + // Twist constraint around x-axis + float cs = b3Dot( matrixB.cz, matrixA.cz ); + float ss = -b3Dot( matrixB.cz, matrixA.cy ); + float den = cs * cs + ss * ss; + den = den > 0.0f ? 1.0f / den : 0.0f; + b3Vec3 steeringAxis = + b3MulSV( den, b3Cross( matrixB.cz, b3Sub( b3MulSV( -cs, matrixA.cy ), b3MulSV( ss, matrixA.cz ) ) ) ); + + b3Vec3 perpAxis = b3Cross( spinAxis, matrixA.cx ); + float steeringImpulse = joint->steeringSpringImpulse + joint->lowerSteeringImpulse - joint->upperSteeringImpulse; + angularImpulse = b3Blend3( angularImpulseX, perpAxis, joint->spinImpulse, spinAxis, steeringImpulse, steeringAxis ); + } + else + { + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + b3Vec3 perpAxisX = b3MulSV( + 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + b3Vec3 perpAxisY = b3MulSV( + 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + angularImpulse = b3Add( + angularImpulse, + b3Blend3( angularImpulseX, perpAxisX, angularImpulseY, perpAxisY, joint->spinImpulse, spinAxis ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = b3MulSub( stateA->linearVelocity, mA, linearImpulse ); + stateA->angularVelocity = b3Sub( stateA->angularVelocity, b3MulMV( iA, b3Add( angularImpulseA, angularImpulse ) ) ); + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = b3MulAdd( stateB->linearVelocity, mB, linearImpulse ); + stateB->angularVelocity = b3Add( stateB->angularVelocity, b3MulMV( iB, b3Add( angularImpulseB, angularImpulse ) ) ); + } +} + +void b3SolveWheelJoint( b3JointSim* base, b3StepContext* context, bool useBias ) +{ + B3_ASSERT( base->type == b3_wheelJoint ); + + float mA = base->invMassA; + float mB = base->invMassB; + b3Matrix3 iA = base->invIA; + b3Matrix3 iB = base->invIB; + + // dummy state for static bodies + b3BodyState dummyState = b3_identityBodyState; + + b3WheelJoint* joint = &base->wheelJoint; + + b3BodyState* stateA = joint->indexA == B3_NULL_INDEX ? &dummyState : context->states + joint->indexA; + b3BodyState* stateB = joint->indexB == B3_NULL_INDEX ? &dummyState : context->states + joint->indexB; + + b3Vec3 vA = stateA->linearVelocity; + b3Vec3 wA = stateA->angularVelocity; + b3Vec3 vB = stateB->linearVelocity; + b3Vec3 wB = stateB->angularVelocity; + + bool fixedRotation = base->fixedRotation; + + // current anchors + b3Vec3 rA = b3RotateVector( stateA->deltaRotation, joint->frameA.p ); + b3Vec3 rB = b3RotateVector( stateB->deltaRotation, joint->frameB.p ); + + b3Quat quatA = b3MulQuat( stateA->deltaRotation, joint->frameA.q ); + b3Quat quatB = b3MulQuat( stateB->deltaRotation, joint->frameB.q ); + + if ( b3DotQuat( quatA, quatB ) < 0.0f ) + { + // this keeps the rotation angle in the range [-pi, pi] + quatB = b3NegateQuat( quatB ); + } + + b3Quat relQ = b3InvMulQuat( quatA, quatB ); + b3Matrix3 matrixA = b3MakeMatrixFromQuat( quatA ); + b3Matrix3 matrixB = b3MakeMatrixFromQuat( quatB ); + + // b3Vec3 spinAxis = b3RotateVector( quatB, b3Vec3_axisZ ); + + b3Vec3 d = b3Add( b3Add( b3Sub( stateB->deltaPosition, stateA->deltaPosition ), joint->deltaCenter ), b3Sub( rB, rA ) ); + b3Vec3 sAx = b3Cross( b3Add( d, rA ), matrixA.cx ); + b3Vec3 sBx = b3Cross( rB, matrixA.cx ); + b3Vec3 sAy = b3Cross( b3Add( d, rA ), matrixA.cy ); + b3Vec3 sBy = b3Cross( rB, matrixA.cy ); + b3Vec3 sAz = b3Cross( b3Add( d, rA ), matrixA.cz ); + b3Vec3 sBz = b3Cross( rB, matrixA.cz ); + + float translation = b3Dot( matrixA.cx, d ); + + // Steering param ib = cz_b, ia = cz_a, ja = -cy_a + float cs = b3Dot( matrixB.cz, matrixA.cz ); + float ss = -b3Dot( matrixB.cz, matrixA.cy ); + float den = cs * cs + ss * ss; + den = den > 0.0f ? 1.0f / den : 0.0f; + b3Vec3 steeringAxis = + b3MulSV( den, b3Cross( matrixB.cz, b3Sub( b3MulSV( -cs, matrixA.cy ), b3MulSV( ss, matrixA.cz ) ) ) ); + + // motor constraint + if ( joint->enableSpinMotor && fixedRotation == false ) + { + b3Vec3 spinAxis = matrixB.cz; + float cdot = b3Dot( b3Sub( wB, wA ), spinAxis ) - joint->spinSpeed; + float impulse = -joint->spinMass * cdot; + float oldImpulse = joint->spinImpulse; + float maxImpulse = context->h * joint->maxSpinTorque; + joint->spinImpulse = b3ClampFloat( joint->spinImpulse + impulse, -maxImpulse, maxImpulse ); + impulse = joint->spinImpulse - oldImpulse; + + wA = b3Sub( wA, b3MulMV( iA, b3MulSV( impulse, spinAxis ) ) ); + wB = b3Add( wB, b3MulMV( iB, b3MulSV( impulse, spinAxis ) ) ); + } + + // suspension + if ( joint->enableSuspensionSpring ) + { + // This is a real spring and should be applied even during relax + float c = translation; + float bias = joint->suspensionSoftness.biasRate * c; + float massScale = joint->suspensionSoftness.massScale; + float impulseScale = joint->suspensionSoftness.impulseScale; + + float cdot = b3Dot( matrixA.cx, b3Sub( vB, vA ) ) + b3Dot( sBx, wB ) - b3Dot( sAx, wA ); + float impulse = -massScale * joint->suspensionMass * ( cdot + bias ) - impulseScale * joint->suspensionSpringImpulse; + joint->suspensionSpringImpulse += impulse; + + b3Vec3 linearImpulse = b3MulSV( impulse, matrixA.cx ); + b3Vec3 angularImpulseA = b3MulSV( impulse, sAx ); + b3Vec3 angularImpulseB = b3MulSV( impulse, sBx ); + + vA = b3MulSub( vA, mA, linearImpulse ); + wA = b3Sub( wA, b3MulMV( iA, angularImpulseA ) ); + vB = b3MulAdd( vB, mB, linearImpulse ); + wB = b3Add( wB, b3MulMV( iB, angularImpulseB ) ); + } + + // steering + if ( joint->enableSteering && fixedRotation == false ) + { + float steeringAngle = b3Atan2( ss, cs ); + + { + // This is a real spring and should be applied even during relax + float c = steeringAngle - joint->targetSteeringAngle; + float bias = joint->steeringSoftness.biasRate * c; + float massScale = joint->steeringSoftness.massScale; + float impulseScale = joint->steeringSoftness.impulseScale; + + float cdot = b3Dot( steeringAxis, b3Sub( wB, wA ) ); + float oldImpulse = joint->steeringSpringImpulse; + float impulse = -massScale * joint->steeringMass * ( cdot + bias ) - impulseScale * oldImpulse; + float maxImpulse = context->h * joint->maxSteeringTorque; + joint->steeringSpringImpulse = b3ClampFloat( oldImpulse + impulse, -maxImpulse, maxImpulse ); + impulse = joint->steeringSpringImpulse - oldImpulse; + + wA = b3Sub( wA, b3MulMV( iA, b3MulSV( impulse, steeringAxis ) ) ); + wB = b3Add( wB, b3MulMV( iB, b3MulSV( impulse, steeringAxis ) ) ); + } + + if ( joint->enableSteeringLimit ) + { + // Lower limit + { + float c = steeringAngle - joint->lowerSteeringLimit; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + float cdot = b3Dot( steeringAxis, b3Sub( wB, wA ) ); + float oldImpulse = joint->lowerSteeringImpulse; + float impulse = -massScale * joint->steeringMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->lowerSteeringImpulse = b3MaxFloat( oldImpulse + impulse, 0.0f ); + impulse = joint->lowerSteeringImpulse - oldImpulse; + + wA = b3Sub( wA, b3MulMV( iA, b3MulSV( impulse, steeringAxis ) ) ); + wB = b3Add( wB, b3MulMV( iB, b3MulSV( impulse, steeringAxis ) ) ); + } + + // Upper limit + // Note: signs are flipped to keep c positive when the constraint is satisfied. + // This also keeps the impulse positive when the limit is active. + { + // sign flipped + float c = joint->upperSteeringLimit - steeringAngle; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // sign flipped on cdot + float cdot = b3Dot( steeringAxis, b3Sub( wA, wB ) ); + float oldImpulse = joint->upperSteeringImpulse; + float impulse = -massScale * joint->steeringMass * ( cdot + bias ) - impulseScale * oldImpulse; + joint->upperSteeringImpulse = b3MaxFloat( oldImpulse + impulse, 0.0f ); + impulse = joint->upperSteeringImpulse - oldImpulse; + + // sign flipped on applied impulse + wA = b3Add( wA, b3MulMV( iA, b3MulSV( impulse, steeringAxis ) ) ); + wB = b3Sub( wB, b3MulMV( iB, b3MulSV( impulse, steeringAxis ) ) ); + } + } + } + + if ( joint->enableSuspensionLimit ) + { + // Lower limit + { + float c = translation - joint->lowerSuspensionLimit; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + float cdot = b3Dot( matrixA.cx, b3Sub( vB, vA ) ) + b3Dot( sBx, wB ) - b3Dot( sAx, wA ); + float impulse = -massScale * joint->suspensionMass * ( cdot + bias ) - impulseScale * joint->lowerSuspensionImpulse; + float oldImpulse = joint->lowerSuspensionImpulse; + joint->lowerSuspensionImpulse = b3MaxFloat( oldImpulse + impulse, 0.0f ); + impulse = joint->lowerSuspensionImpulse - oldImpulse; + + b3Vec3 linearImpulse = b3MulSV( impulse, matrixA.cx ); + b3Vec3 angularImpulseA = b3MulSV( impulse, sAx ); + b3Vec3 angularImpulseB = b3MulSV( impulse, sBx ); + + vA = b3MulSub( vA, mA, linearImpulse ); + wA = b3Sub( wA, b3MulMV( iA, angularImpulseA ) ); + vB = b3MulAdd( vB, mB, linearImpulse ); + wB = b3Add( wB, b3MulMV( iB, angularImpulseB ) ); + } + + // Upper limit + // Note: signs are flipped to keep c positive when the constraint is satisfied. + // This also keeps the impulse positive when the limit is active. + { + // sign flipped + float c = joint->upperSuspensionLimit - translation; + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( c > 0.0f ) + { + // speculation + bias = c * context->inv_h; + } + else if ( useBias ) + { + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // sign flipped on cdot + float cdot = b3Dot( matrixA.cx, b3Sub( vA, vB ) ) + b3Dot( sAx, wA ) - b3Dot( sBx, wB ); + float impulse = -massScale * joint->suspensionMass * ( cdot + bias ) - impulseScale * joint->upperSuspensionImpulse; + float oldImpulse = joint->upperSuspensionImpulse; + joint->upperSuspensionImpulse = b3MaxFloat( oldImpulse + impulse, 0.0f ); + impulse = joint->upperSuspensionImpulse - oldImpulse; + + b3Vec3 linearImpulse = b3MulSV( impulse, matrixA.cx ); + b3Vec3 angularImpulseA = b3MulSV( impulse, sAx ); + b3Vec3 angularImpulseB = b3MulSV( impulse, sBx ); + + // sign flipped on applied impulse + vA = b3MulAdd( vA, mA, linearImpulse ); + wA = b3Add( wA, b3MulMV( iA, angularImpulseA ) ); + vB = b3MulSub( vB, mB, linearImpulse ); + wB = b3Sub( wB, b3MulMV( iB, angularImpulseB ) ); + } + } + + // Collinearity constraint + if ( fixedRotation == false ) + { + if ( joint->enableSteering == true ) + { + float bias = 0.0f; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias ) + { + float c = b3Dot( matrixA.cx, matrixB.cz ); + + bias = base->constraintSoftness.biasRate * c; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + b3Vec3 u = b3Cross( matrixB.cz, matrixA.cx ); + float cdot = b3Dot( b3Sub( wB, wA ), u ); + + b3Matrix3 invInertiaSum = b3AddMM( iA, iB ); + float k = b3Dot( u, b3MulMV( invInertiaSum, u ) ); + float perpMass = k > 0.0f ? 1.0f / k : 0.0f; + + float deltaImpulse = -massScale * perpMass * ( cdot + bias ) - impulseScale * joint->angularImpulse.x; + joint->angularImpulse.x += deltaImpulse; + + wA = b3MulSub( wA, deltaImpulse, b3MulMV( iA, u ) ); + wB = b3MulAdd( wB, deltaImpulse, b3MulMV( iB, u ) ); + } + else + { + b3Vec2 bias = { 0.0f, 0.0f }; + float massScale = 1.0f; + float impulseScale = 0.0f; + + if ( useBias ) + { + b3Vec2 c = { relQ.v.x, relQ.v.y }; + bias = (b3Vec2){ base->constraintSoftness.biasRate * c.x, base->constraintSoftness.biasRate * c.y }; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + // Collinearity constraint as 2-by-2 + b3Vec3 perpAxisX = b3MulSV( + 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisX ), b3Cross( relQ.v, b3Vec3_axisX ) ) ) ); + b3Vec3 perpAxisY = b3MulSV( + 0.5f, b3RotateVector( quatA, b3Add( b3MulSV( relQ.s, b3Vec3_axisY ), b3Cross( relQ.v, b3Vec3_axisY ) ) ) ); + + b3Matrix3 invInertiaSum = b3AddMM( iA, iB ); + float kxx = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisX ) ); + float kyy = b3Dot( perpAxisY, b3MulMV( invInertiaSum, perpAxisY ) ); + float kxy = b3Dot( perpAxisX, b3MulMV( invInertiaSum, perpAxisY ) ); + + b3Matrix2 k = { { kxx, kxy }, { kxy, kyy } }; + + b3Vec3 wRel = b3Sub( wB, wA ); + b3Vec2 cdot = { b3Dot( wRel, perpAxisX ), b3Dot( wRel, perpAxisY ) }; + b3Vec2 oldImpulse = joint->angularImpulse; + b3Vec2 cdotPlusBias = { cdot.x + bias.x, cdot.y + bias.y }; + b3Vec2 sol = b3Solve2( k, cdotPlusBias ); + b3Vec2 deltaImpulse = { + -massScale * sol.x - impulseScale * oldImpulse.x, + -massScale * sol.y - impulseScale * oldImpulse.y, + }; + joint->angularImpulse = (b3Vec2){ oldImpulse.x + deltaImpulse.x, oldImpulse.y + deltaImpulse.y }; + + b3Vec3 angularImpulse = b3Blend2( deltaImpulse.x, perpAxisX, deltaImpulse.y, perpAxisY ); + wA = b3Sub( wA, b3MulMV( iA, angularImpulse ) ); + wB = b3Add( wB, b3MulMV( iB, angularImpulse ) ); + } + } + + // Solve point-to-line constraint + { + b3Vec3 perpY = matrixA.cy; + b3Vec3 perpZ = matrixA.cz; + + b3Vec2 bias = { 0.0f, 0.0f }; + float massScale = 1.0f; + float impulseScale = 0.0f; + if ( useBias ) + { + b3Vec2 c = { b3Dot( perpY, d ), b3Dot( perpZ, d ) }; + bias = (b3Vec2){ base->constraintSoftness.biasRate * c.x, base->constraintSoftness.biasRate * c.y }; + massScale = base->constraintSoftness.massScale; + impulseScale = base->constraintSoftness.impulseScale; + } + + b3Vec3 vRel = b3Sub( b3Sub( b3Add( vB, b3Cross( wB, rB ) ), vA ), b3Cross( wA, b3Add( rA, d ) ) ); + b3Vec2 cdot = { b3Dot( perpY, vRel ), b3Dot( perpZ, vRel ) }; + + //// K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + ///// Jx = [-perpX, -cross(d + rA, perpX), perpX, cross(rB, perpX)] + + float kyy = mA + mB + b3Dot( sAy, b3MulMV( iA, sAy ) ) + b3Dot( sBy, b3MulMV( iB, sBy ) ); + float kyz = b3Dot( sAy, b3MulMV( iA, sAz ) ) + b3Dot( sBy, b3MulMV( iB, sBz ) ); + float kzz = mA + mB + b3Dot( sAz, b3MulMV( iA, sAz ) ) + b3Dot( sBz, b3MulMV( iB, sBz ) ); + + b3Matrix2 k = { { kyy, kyz }, { kyz, kzz } }; + + b3Vec2 oldImpulse = joint->linearImpulse; + b3Vec2 cdotPlusBias = { cdot.x + bias.x, cdot.y + bias.y }; + b3Vec2 sol = b3Solve2( k, cdotPlusBias ); + b3Vec2 deltaImpulse = { + -massScale * sol.x - impulseScale * oldImpulse.x, + -massScale * sol.y - impulseScale * oldImpulse.y, + }; + joint->linearImpulse = (b3Vec2){ oldImpulse.x + deltaImpulse.x, oldImpulse.y + deltaImpulse.y }; + + b3Vec3 linearImpulse = b3Blend2( deltaImpulse.x, perpY, deltaImpulse.y, perpZ ); + + vA = b3MulSub( vA, mA, linearImpulse ); + wA = b3Sub( wA, b3MulMV( iA, b3Blend2( deltaImpulse.x, sAy, deltaImpulse.y, sAz ) ) ); + vB = b3MulAdd( vB, mB, linearImpulse ); + wB = b3Add( wB, b3MulMV( iB, b3Blend2( deltaImpulse.x, sBy, deltaImpulse.y, sBz ) ) ); + } + + if ( stateA->flags & b3_dynamicFlag ) + { + stateA->linearVelocity = vA; + stateA->angularVelocity = wA; + } + + if ( stateB->flags & b3_dynamicFlag ) + { + stateB->linearVelocity = vB; + stateB->angularVelocity = wB; + } +} + +#if 0 +void b3WheelJoint_Dump() +{ + int32 indexA = joint->bodyA->joint->islandIndex; + int32 indexB = joint->bodyB->joint->islandIndex; + + b3Dump(" b3WheelJointDef jd;\n"); + b3Dump(" jd.bodyA = sims[%d];\n", indexA); + b3Dump(" jd.bodyB = sims[%d];\n", indexB); + b3Dump(" jd.collideConnected = bool(%d);\n", joint->collideConnected); + b3Dump(" jd.localAnchorA.Set(%.9g, %.9g);\n", joint->localAnchorA.x, joint->localAnchorA.y); + b3Dump(" jd.localAnchorB.Set(%.9g, %.9g);\n", joint->localAnchorB.x, joint->localAnchorB.y); + b3Dump(" jd.referenceAngle = %.9g;\n", joint->referenceAngle); + b3Dump(" jd.enableLimit = bool(%d);\n", joint->enableLimit); + b3Dump(" jd.lowerAngle = %.9g;\n", joint->lowerAngle); + b3Dump(" jd.upperAngle = %.9g;\n", joint->upperAngle); + b3Dump(" jd.enableMotor = bool(%d);\n", joint->enableMotor); + b3Dump(" jd.motorSpeed = %.9g;\n", joint->motorSpeed); + b3Dump(" jd.maxMotorTorque = %.9g;\n", joint->maxMotorTorque); + b3Dump(" joints[%d] = joint->world->CreateJoint(&jd);\n", joint->index); +} +#endif + +void b3DrawWheelJoint( b3DebugDraw* draw, b3JointSim* base, b3WorldTransform transformA, b3WorldTransform transformB, float scale ) +{ + B3_ASSERT( base->type == b3_wheelJoint ); + + b3WheelJoint* joint = &base->wheelJoint; + + b3WorldTransform frameA = b3MulWorldTransforms( transformA, base->localFrameA ); + b3WorldTransform frameB = b3MulWorldTransforms( transformB, base->localFrameB ); + + b3Matrix3 matrixA = b3MakeMatrixFromQuat( frameA.q ); + b3Matrix3 matrixB = b3MakeMatrixFromQuat( frameB.q ); + + draw->DrawSegmentFcn( frameA.p, frameB.p, b3_colorBlue, draw->context ); + + if ( joint->enableSuspensionLimit ) + { + b3Pos lower = b3OffsetPos( frameA.p, b3MulSV( joint->lowerSuspensionLimit, matrixA.cx ) ); + b3Pos upper = b3OffsetPos( frameA.p, b3MulSV( joint->upperSuspensionLimit, matrixA.cx ) ); + b3Vec3 perp = matrixA.cy; + draw->DrawSegmentFcn( lower, upper, b3_colorGray, draw->context ); + draw->DrawSegmentFcn( b3OffsetPos( lower, b3MulSV( -0.1f * scale, perp ) ), b3OffsetPos( lower, b3MulSV( 0.1f * scale, perp ) ), + b3_colorGreen, draw->context ); + draw->DrawSegmentFcn( b3OffsetPos( upper, b3MulSV( -0.1f * scale, perp ) ), b3OffsetPos( upper, b3MulSV( 0.1f * scale, perp ) ), + b3_colorRed, draw->context ); + } + else + { + draw->DrawSegmentFcn( b3OffsetPos( frameA.p, b3MulSV( -1.0f * scale, matrixA.cx ) ), + b3OffsetPos( frameA.p, b3MulSV( 1.0f * scale, matrixA.cx ) ), b3_colorGray, draw->context ); + } + + if ( joint->enableSteering && joint->enableSteeringLimit ) + { + // b3Quat quatA = frameA.q; + // b3Quat quatB = frameB.q; + + // if ( b3DotQuat( quatA, quatB ) < 0.0f ) + //{ + // // this keeps the twist angle in the range [-pi, pi] + // quatB = -quatB; + // } + + // b3Quat relQ = b3InvMulQuat( quatA, quatB ); + + b3WorldTransform frame = { + .p = frameB.p, + .q = frameA.q, + }; + + const float radius = 0.5f * scale; + const int sliceCount = 16; + float lower = joint->lowerSteeringLimit; + float upper = joint->upperSteeringLimit; + + b3CosSin cs = b3ComputeCosSin( lower ); + b3Pos vertex1 = b3TransformWorldPoint( frame, (b3Vec3){ 0.0f, -radius * cs.sine, radius * cs.cosine } ); + + for ( int index = 0; index < sliceCount; ++index ) + { + float t2 = ( index + 1.0f ) / sliceCount; + float phi = b3LerpFloat( lower, upper, t2 ); + + cs = b3ComputeCosSin( phi ); + b3Pos vertex2 = b3TransformWorldPoint( frame, (b3Vec3){ 0.0f, -radius * cs.sine, radius * cs.cosine } ); + + if ( index == 0 ) + { + draw->DrawSegmentFcn( frame.p, vertex1, b3_colorCyan, draw->context ); + } + + if ( index == sliceCount - 1 ) + { + draw->DrawSegmentFcn( vertex2, frame.p, b3_colorCyan, draw->context ); + } + draw->DrawSegmentFcn( vertex1, vertex2, b3_colorCyan, draw->context ); + + vertex1 = vertex2; + } + } + + draw->DrawSegmentFcn( b3OffsetPos( frameB.p, b3MulSV( -0.5f * scale, matrixB.cz ) ), + b3OffsetPos( frameB.p, b3MulSV( 0.5f * scale, matrixB.cz ) ), b3_colorMagenta, draw->context ); + + draw->DrawPointFcn( frameA.p, 5.0f, b3_colorGray, draw->context ); + draw->DrawPointFcn( frameB.p, 5.0f, b3_colorDimGray, draw->context ); +} diff --git a/vendor/box3d/src/src/world_snapshot.c b/vendor/box3d/src/src/world_snapshot.c new file mode 100644 index 000000000..9efaee9fd --- /dev/null +++ b/vendor/box3d/src/src/world_snapshot.c @@ -0,0 +1,1335 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#if defined( _MSC_VER ) && !defined( _CRT_SECURE_NO_WARNINGS ) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "world_snapshot.h" + +#include "bitset.h" +#include "body.h" +#include "broad_phase.h" +#include "compound.h" +#include "constraint_graph.h" +#include "contact.h" +#include "container.h" +#include "core.h" +#include "id_pool.h" +#include "island.h" +#include "joint.h" +#include "physics_world.h" +#include "recording.h" +#include "sensor.h" +#include "shape.h" +#include "solver_set.h" +#include "table.h" + +#include "box3d/box3d.h" +#include "box3d/collision.h" +#include "box3d/types.h" + +#include + +// Snapshot image magic 'BNS3' and version +#define B3_SNAP_MAGIC 0x33534E42u +#define B3_SNAP_VERSION 2u + +#define B3_SNAP_FLAG_VALIDATION 0x1u +#define B3_SNAP_FLAG_DOUBLE_PRECISION 0x2u + +// Layout hash over all POD-copied structs + key constants. +// Changing a struct size updates this, catching ABI drift early. +static uint32_t b3ComputeLayoutHash( void ) +{ + uint32_t h = 2166136261u; +#define MIX( x ) \ + h ^= (uint32_t)( x ); \ + h *= 16777619u; + MIX( sizeof( b3Body ) ) + MIX( sizeof( b3BodySim ) ) + MIX( sizeof( b3BodyState ) ) + MIX( sizeof( b3Shape ) ) + MIX( sizeof( b3Contact ) ) + MIX( sizeof( b3Manifold ) ) + MIX( sizeof( b3Joint ) ) + MIX( sizeof( b3JointSim ) ) + MIX( sizeof( b3Island ) ) + MIX( sizeof( b3IslandSim ) ) + MIX( sizeof( b3ContactLink ) ) + MIX( sizeof( b3JointLink ) ) + MIX( sizeof( b3Sensor ) ) + MIX( sizeof( b3Visitor ) ) + MIX( sizeof( b3SolverSet ) ) + MIX( sizeof( b3GraphColor ) ) + MIX( sizeof( b3DynamicTree ) ) + MIX( sizeof( b3TreeNode ) ) + MIX( sizeof( b3SetItem ) ) + MIX( sizeof( b3IdPool ) ) + MIX( sizeof( b3SurfaceMaterial ) ) + MIX( sizeof( b3ContactSpec ) ) + MIX( sizeof( b3TriangleCache ) ) + MIX( B3_GRAPH_COLOR_COUNT ) + MIX( b3_bodyTypeCount ) + MIX( sizeof( void* ) ) +#undef MIX + return h; +} + +typedef struct b3SnapHeader +{ + uint32_t magic; + uint32_t version; + uint32_t layoutHash; + uint32_t flags; +} b3SnapHeader; + +// Bounds-checked read cursor +typedef struct b3SnapReader +{ + const uint8_t* data; + int cursor; + int size; + bool ok; +} b3SnapReader; + +static void b3SnapRCheck( b3SnapReader* r, int need ) +{ + if ( need < 0 || (int64_t)r->cursor + (int64_t)need > (int64_t)r->size ) + { + r->ok = false; + } +} + +static void b3SnapR_Bytes( b3SnapReader* r, void* dst, int n ) +{ + b3SnapRCheck( r, n ); + if ( !r->ok ) + { + return; + } + memcpy( dst, r->data + r->cursor, n ); + r->cursor += n; +} + +static int b3SnapR_I32( b3SnapReader* r ) +{ + int32_t v = 0; + b3SnapR_Bytes( r, &v, 4 ); + return (int)v; +} + +static uint32_t b3SnapR_U32( b3SnapReader* r ) +{ + uint32_t v = 0; + b3SnapR_Bytes( r, &v, 4 ); + return v; +} + +static void b3SnapW_I32( b3RecBuffer* buf, int v ) +{ + int32_t w = (int32_t)v; + b3RecBufAppend( buf, &w, 4 ); +} + +static void b3SnapW_U32( b3RecBuffer* buf, uint32_t v ) +{ + b3RecBufAppend( buf, &v, 4 ); +} + +static void b3SnapW_Bytes( b3RecBuffer* buf, const void* src, int n ) +{ + b3RecBufAppend( buf, src, n ); +} + +// Bounds check before allocating from image +static bool b3SnapCheckCount( const b3SnapReader* r, int count, int memSize, int minStreamBytes ) +{ + if ( count < 0 || memSize < 0 || minStreamBytes < 0 ) + { + return false; + } + if ( memSize > 0 && count > 0x7FFFFFFF / memSize ) + { + return false; + } + int64_t remaining = (int64_t)r->size - (int64_t)r->cursor; + return (int64_t)count * (int64_t)minStreamBytes <= remaining; +} + +// POD array: count + raw bytes +#define b3SerPodArray( buf, arr ) \ + do \ + { \ + b3SnapW_I32( buf, ( arr ).count ); \ + if ( ( arr ).count > 0 ) \ + { \ + b3SnapW_Bytes( buf, ( arr ).data, ( arr ).count * (int)sizeof( *( arr ).data ) ); \ + } \ + } \ + while ( 0 ) + +#define b3DesPodArray( r, arr ) \ + do \ + { \ + int cnt = b3SnapR_I32( r ); \ + int elemSize = (int)sizeof( *( arr ).data ); \ + if ( ( r )->ok && b3SnapCheckCount( r, cnt, elemSize, elemSize ) == false ) \ + { \ + ( r )->ok = false; \ + } \ + if ( ( r )->ok && cnt > 0 ) \ + { \ + b3Array_Resize( arr, cnt ); \ + b3SnapR_Bytes( r, ( arr ).data, cnt * elemSize ); \ + } \ + else if ( ( r )->ok ) \ + { \ + ( arr ).count = 0; \ + } \ + } \ + while ( 0 ) + +// Id pool: nextIndex + freeArray +static void b3SerIdPool( b3RecBuffer* buf, const b3IdPool* pool ) +{ + b3SnapW_I32( buf, pool->nextIndex ); + b3SerPodArray( buf, pool->freeArray ); +} + +static void b3DesIdPool( b3SnapReader* r, b3IdPool* pool ) +{ + pool->nextIndex = b3SnapR_I32( r ); + b3DesPodArray( r, pool->freeArray ); +} + +// BitSet: blockCount + raw words +static void b3SerBitSet( b3RecBuffer* buf, const b3BitSet* bs ) +{ + b3SnapW_U32( buf, bs->blockCount ); + if ( bs->blockCount > 0 ) + { + b3SnapW_Bytes( buf, bs->bits, (int)( bs->blockCount * sizeof( uint64_t ) ) ); + } +} + +static void b3DesBitSet( b3SnapReader* r, b3BitSet* bs ) +{ + uint32_t blockCount = b3SnapR_U32( r ); + if ( r->ok && b3SnapCheckCount( r, (int)blockCount, (int)sizeof( uint64_t ), (int)sizeof( uint64_t ) ) == false ) + { + r->ok = false; + } + b3DestroyBitSet( bs ); + if ( !r->ok ) + { + return; + } + uint32_t blockCapacity = blockCount > 0 ? blockCount : 1; + bs->bits = (uint64_t*)b3Alloc( blockCapacity * sizeof( uint64_t ) ); + memset( bs->bits, 0, blockCapacity * sizeof( uint64_t ) ); + bs->blockCapacity = blockCapacity; + bs->blockCount = blockCount; + if ( blockCount > 0 ) + { + b3SnapR_Bytes( r, bs->bits, (int)( blockCount * sizeof( uint64_t ) ) ); + } +} + +// HashSet: capacity + count + raw items (probe order depends on layout) +static void b3SerHashSet( b3RecBuffer* buf, const b3HashSet* hs ) +{ + b3SnapW_U32( buf, hs->capacity ); + b3SnapW_U32( buf, hs->count ); + if ( hs->capacity > 0 ) + { + b3SnapW_Bytes( buf, hs->items, (int)( hs->capacity * sizeof( b3SetItem ) ) ); + } +} + +static void b3DesHashSet( b3SnapReader* r, b3HashSet* hs ) +{ + uint32_t cap = b3SnapR_U32( r ); + uint32_t cnt = b3SnapR_U32( r ); + bool valid = b3SnapCheckCount( r, (int)cap, (int)sizeof( b3SetItem ), (int)sizeof( b3SetItem ) ) && + ( cap & ( cap - 1 ) ) == 0 && cnt <= cap; + if ( r->ok && valid == false && ( cap != 0 || cnt != 0 ) ) + { + r->ok = false; + } + b3DestroySet( hs ); + if ( !r->ok ) + { + return; + } + if ( cap > 0 ) + { + hs->items = (b3SetItem*)b3Alloc( cap * sizeof( b3SetItem ) ); + hs->capacity = cap; + hs->count = cnt; + b3SnapR_Bytes( r, hs->items, (int)( cap * sizeof( b3SetItem ) ) ); + } + else + { + hs->items = NULL; + hs->capacity = 0; + hs->count = 0; + } +} + +// DynamicTree: version, scalars, full nodeCapacity nodes (rebuild scratch excluded) +static void b3SerTree( b3RecBuffer* buf, const b3DynamicTree* tree ) +{ + b3SnapW_Bytes( buf, &tree->version, sizeof( uint64_t ) ); + b3SnapW_I32( buf, tree->root ); + b3SnapW_I32( buf, tree->nodeCount ); + b3SnapW_I32( buf, tree->nodeCapacity ); + b3SnapW_I32( buf, tree->freeList ); + b3SnapW_I32( buf, tree->proxyCount ); + if ( tree->nodeCapacity > 0 ) + { + b3SnapW_Bytes( buf, tree->nodes, tree->nodeCapacity * (int)sizeof( b3TreeNode ) ); + } +} + +static void b3DesTree( b3SnapReader* r, b3DynamicTree* tree ) +{ + uint64_t version; + b3SnapR_Bytes( r, &version, sizeof( uint64_t ) ); + int root = b3SnapR_I32( r ); + int nodeCount = b3SnapR_I32( r ); + int nodeCapacity = b3SnapR_I32( r ); + int freeList = b3SnapR_I32( r ); + int proxyCount = b3SnapR_I32( r ); + + if ( r->ok && b3SnapCheckCount( r, nodeCapacity, (int)sizeof( b3TreeNode ), (int)sizeof( b3TreeNode ) ) == false ) + { + r->ok = false; + } + + // Free existing allocation including any rebuild scratch + b3Free( tree->nodes, tree->nodeCapacity * (int)sizeof( b3TreeNode ) ); + b3Free( tree->leafIndices, tree->rebuildCapacity * (int)sizeof( int ) ); + b3Free( tree->leafBoxes, tree->rebuildCapacity * (int)sizeof( b3AABB ) ); + b3Free( tree->leafCenters, tree->rebuildCapacity * (int)sizeof( b3Vec3 ) ); + b3Free( tree->binIndices, tree->rebuildCapacity * (int)sizeof( int ) ); + tree->nodes = NULL; + tree->leafIndices = NULL; + tree->leafBoxes = NULL; + tree->leafCenters = NULL; + tree->binIndices = NULL; + tree->nodeCapacity = 0; + tree->rebuildCapacity = 0; + + if ( !r->ok ) + { + return; + } + + tree->version = version; + tree->root = root; + tree->nodeCount = nodeCount; + tree->nodeCapacity = nodeCapacity; + tree->freeList = freeList; + tree->proxyCount = proxyCount; + + if ( nodeCapacity > 0 ) + { + tree->nodes = (b3TreeNode*)b3Alloc( nodeCapacity * (int)sizeof( b3TreeNode ) ); + b3SnapR_Bytes( r, tree->nodes, nodeCapacity * (int)sizeof( b3TreeNode ) ); + } +} + +// Solver set: setIndex + 4 arrays (note: contactIndices is int array, not contactSims) +static void b3SerSolverSet( b3RecBuffer* buf, const b3SolverSet* set ) +{ + b3SnapW_I32( buf, set->setIndex ); + b3SerPodArray( buf, set->bodySims ); + b3SerPodArray( buf, set->bodyStates ); + b3SerPodArray( buf, set->jointSims ); + b3SerPodArray( buf, set->contactIndices ); + b3SerPodArray( buf, set->islandSims ); +} + +static void b3DesSolverSet( b3SnapReader* r, b3SolverSet* set ) +{ + set->setIndex = b3SnapR_I32( r ); + b3DesPodArray( r, set->bodySims ); + b3DesPodArray( r, set->bodyStates ); + b3DesPodArray( r, set->jointSims ); + b3DesPodArray( r, set->contactIndices ); + b3DesPodArray( r, set->islandSims ); +} + +static void b3SerNames( b3RecBuffer* buf, const b3NameCache* cache ) +{ + b3SnapW_I32( buf, cache->entries.count ); + int count = cache->entries.count; + for ( int i = 0; i < count; ++i ) + { + const b3NameEntry* entry = cache->entries.data + i; + b3SnapW_U32( buf, entry->hash ); + b3SnapW_I32( buf, entry->length ); + b3RecBufAppend( buf, entry->name, entry->length ); + } +} + +static void b3DesNames( b3SnapReader* r, b3NameCache* cache ) +{ + int count = b3SnapR_I32( r ); + + if ( r->ok && b3SnapCheckCount( r, count, (int)sizeof( b3NameEntry ), 8 ) == false ) + { + r->ok = false; + } + + if ( r->ok == false ) + { + return; + } + + b3Array_Reserve( cache->entries, count ); + for ( int i = 0; i < count; ++i ) + { + uint32_t hash = b3SnapR_U32( r ); + int length = b3SnapR_I32( r ); + if ( r->ok == false || length < 0 || length > r->size - r->cursor ) + { + r->ok = false; + return; + } + char* name = b3Alloc( length + 1 ); + b3SnapR_Bytes( r, name, length ); + name[length] = 0; + b3LoadName( cache, hash, name, length ); + } +} + +// Graph color: bodySet (non-overflow only) + jointSims + convexContacts + contacts +static void b3SerGraphColor( b3RecBuffer* buf, const b3GraphColor* color, bool isOverflow ) +{ + if ( !isOverflow ) + { + b3SerBitSet( buf, &color->bodySet ); + } + b3SerPodArray( buf, color->jointSims ); + b3SerPodArray( buf, color->convexContacts ); + b3SerPodArray( buf, color->contacts ); + // wideConstraints / manifoldConstraints / contactConstraints are transient, not serialized +} + +static void b3DesGraphColor( b3SnapReader* r, b3GraphColor* color, bool isOverflow ) +{ + if ( !isOverflow ) + { + b3DesBitSet( r, &color->bodySet ); + } + b3DesPodArray( r, color->jointSims ); + b3DesPodArray( r, color->convexContacts ); + b3DesPodArray( r, color->contacts ); + // Transient pointers left at NULL/0 from shell +} + +// World simulation scalars (never host/callback/worker state) +static void b3SerWorldConfig( b3RecBuffer* buf, const b3World* world ) +{ + b3SnapW_Bytes( buf, &world->gravity, sizeof( b3Vec3 ) ); + b3SnapW_Bytes( buf, &world->hitEventThreshold, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->restitutionThreshold, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->maxLinearSpeed, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->contactSpeed, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->contactHertz, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->contactDampingRatio, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->contactRecycleDistance, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->stepIndex, sizeof( uint64_t ) ); + b3SnapW_I32( buf, world->splitIslandId ); + b3SnapW_Bytes( buf, &world->inv_h, sizeof( float ) ); + b3SnapW_Bytes( buf, &world->inv_dt, sizeof( float ) ); + b3SnapW_I32( buf, world->endEventArrayIndex ); + b3SnapW_Bytes( buf, &world->maxCapacity, sizeof( b3Capacity ) ); + uint8_t flags = 0; + flags |= world->enableSleep ? 0x01u : 0u; + flags |= world->enableWarmStarting ? 0x02u : 0u; + flags |= world->enableContinuous ? 0x04u : 0u; + flags |= world->enableSpeculative ? 0x08u : 0u; + b3RecBufAppend( buf, &flags, 1 ); +} + +static void b3DesWorldConfig( b3SnapReader* r, b3World* world ) +{ + b3SnapR_Bytes( r, &world->gravity, sizeof( b3Vec3 ) ); + b3SnapR_Bytes( r, &world->hitEventThreshold, sizeof( float ) ); + b3SnapR_Bytes( r, &world->restitutionThreshold, sizeof( float ) ); + b3SnapR_Bytes( r, &world->maxLinearSpeed, sizeof( float ) ); + b3SnapR_Bytes( r, &world->contactSpeed, sizeof( float ) ); + b3SnapR_Bytes( r, &world->contactHertz, sizeof( float ) ); + b3SnapR_Bytes( r, &world->contactDampingRatio, sizeof( float ) ); + b3SnapR_Bytes( r, &world->contactRecycleDistance, sizeof( float ) ); + b3SnapR_Bytes( r, &world->stepIndex, sizeof( uint64_t ) ); + world->splitIslandId = b3SnapR_I32( r ); + b3SnapR_Bytes( r, &world->inv_h, sizeof( float ) ); + b3SnapR_Bytes( r, &world->inv_dt, sizeof( float ) ); + world->endEventArrayIndex = b3SnapR_I32( r ); + b3SnapR_Bytes( r, &world->maxCapacity, sizeof( b3Capacity ) ); + uint8_t flags = 0; + b3SnapR_Bytes( r, &flags, 1 ); + world->enableSleep = ( flags & 0x01u ) != 0; + world->enableWarmStarting = ( flags & 0x02u ) != 0; + world->enableContinuous = ( flags & 0x04u ) != 0; + world->enableSpeculative = ( flags & 0x08u ) != 0; +} + +// Shapes carry pointer fields: materials, userData, userShape, and the geometry union. +// Serialize the POD scalars with pointers nulled, then the owned materials array, then geometry. +// A single material lives inline in the struct image. +// Hull/mesh/heightField/compound are interned into the recording registry; sphere/capsule inline. +static void b3SerShapes( b3RecBuffer* buf, b3World* world, b3Recording* rec ) +{ + int count = world->shapes.count; + b3SnapW_I32( buf, count ); + + for ( int i = 0; i < count; ++i ) + { + b3Shape shape = world->shapes.data[i]; + bool isLive = ( shape.id == i ); + + // Null out pointer fields before writing the raw struct + shape.materials = NULL; + shape.userData = NULL; + shape.userShape = NULL; + // Zero the geometry union so free-slot images have deterministic bytes + if ( !isLive ) + { + memset( &shape.capsule, 0, sizeof( shape.capsule ) ); + } + b3SnapW_Bytes( buf, &shape, sizeof( b3Shape ) ); + + if ( !isLive ) + { + // Free slot: no materials or geometry + b3SnapW_I32( buf, 0 ); // materialCount + b3SnapW_I32( buf, -1 ); // geometry kind sentinel + continue; + } + + // Owned material array. Only multi material meshes and compounds have one. A single material + // already rode along inline in the struct image, so write a zero length for it. + const b3Shape* src = world->shapes.data + i; + if ( src->materials != NULL ) + { + b3SnapW_I32( buf, src->materialCount ); + b3SnapW_Bytes( buf, src->materials, src->materialCount * (int)sizeof( b3SurfaceMaterial ) ); + } + else + { + b3SnapW_I32( buf, 0 ); + } + + // Geometry + switch ( src->type ) + { + case b3_sphereShape: + b3SnapW_I32( buf, (int)b3_sphereShape ); + b3SnapW_Bytes( buf, &src->sphere, sizeof( b3Sphere ) ); + break; + case b3_capsuleShape: + b3SnapW_I32( buf, (int)b3_capsuleShape ); + b3SnapW_Bytes( buf, &src->capsule, sizeof( b3Capsule ) ); + break; + case b3_hullShape: + { + b3SnapW_I32( buf, (int)b3_hullShape ); + uint32_t gid = b3RecInternHull( rec, src->hull ); + b3SnapW_U32( buf, gid ); + break; + } + case b3_meshShape: + { + b3SnapW_I32( buf, (int)b3_meshShape ); + uint32_t gid = b3RecInternMesh( rec, src->mesh.data ); + b3SnapW_U32( buf, gid ); + b3SnapW_Bytes( buf, &src->mesh.scale, sizeof( b3Vec3 ) ); + break; + } + case b3_heightShape: + { + b3SnapW_I32( buf, (int)b3_heightShape ); + uint32_t gid = b3RecInternHeightField( rec, src->heightField ); + b3SnapW_U32( buf, gid ); + break; + } + case b3_compoundShape: + { + b3SnapW_I32( buf, (int)b3_compoundShape ); + uint32_t gid = b3RecInternCompound( rec, src->compound ); + b3SnapW_U32( buf, gid ); + break; + } + default: + // A live shape must have a known geometry type. Fail loudly rather than emit a shape + // with no geometry that would silently lose its collision on restore. + B3_ASSERT( false ); + b3SnapW_I32( buf, -1 ); + break; + } + } +} + +static void b3DesShapes( b3SnapReader* r, b3World* world, b3RecReader* rdr ) +{ + int count = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, count, (int)sizeof( b3Shape ), (int)sizeof( b3Shape ) ) == false ) + { + r->ok = false; + } + if ( !r->ok ) + { + return; + } + + // Save renderer handles before the array is wiped. A keyframe restore is a deterministic replay + // state, so a live shape that still occupies the same slot with the same generation is the same + // shape with the same geometry. Carrying its handle over avoids tearing down and rebuilding every + // GPU mesh on each seek, which the host (a 3D renderer) would otherwise pay for. Handles that are + // not reclaimed below belong to shapes that are gone or were replaced, and get released so the host + // pool does not leak across seeks. Box2D has no such handles, so its restore skips all of this. + int oldShapeCount = world->shapes.count; + void** savedUserShape = NULL; + uint16_t* savedGeneration = NULL; + if ( oldShapeCount > 0 ) + { + savedUserShape = (void**)b3Alloc( (size_t)oldShapeCount * sizeof( void* ) ); + savedGeneration = (uint16_t*)b3Alloc( (size_t)oldShapeCount * sizeof( uint16_t ) ); + for ( int i = 0; i < oldShapeCount; ++i ) + { + b3Shape* old = world->shapes.data + i; + bool oldLive = ( old->id == i ); + savedUserShape[i] = oldLive ? old->userShape : NULL; + savedGeneration[i] = old->generation; + } + } + + b3Array_Resize( world->shapes, count ); + memset( world->shapes.data, 0, (size_t)count * sizeof( b3Shape ) ); + + for ( int i = 0; i < count && r->ok; ++i ) + { + b3Shape* dst = world->shapes.data + i; + b3SnapR_Bytes( r, dst, sizeof( b3Shape ) ); + // Pointer fields were written as NULL; set them cleanly + dst->materials = NULL; + dst->userData = NULL; + dst->userShape = NULL; + memset( &dst->capsule, 0, sizeof( dst->capsule ) ); + + bool isLive = ( dst->id == i ); + + // Carry the renderer handle over when the same shape still occupies this slot. Consumed + // handles are nulled so the teardown sweep only releases the ones that vanished. + if ( isLive && i < oldShapeCount && savedUserShape != NULL && savedUserShape[i] != NULL && + savedGeneration[i] == dst->generation ) + { + dst->userShape = savedUserShape[i]; + savedUserShape[i] = NULL; + } + + // Serializer writes: matCount, matData, geoKind, geoData + int matCount = b3SnapR_I32( r ); + + if ( !r->ok ) + { + break; + } + + if ( !isLive ) + { + // Free slot: matCount=0, geoKind=-1 + (void)matCount; + b3SnapR_I32( r ); // consume the geoKind sentinel + continue; + } + + // Owned material array (written before geoKind in serializer). A zero length means the single + // material is already inline in the restored struct image, so leave materialCount as restored. + if ( matCount > 0 ) + { + if ( b3SnapCheckCount( r, matCount, (int)sizeof( b3SurfaceMaterial ), (int)sizeof( b3SurfaceMaterial ) ) == false ) + { + r->ok = false; + break; + } + dst->materialCount = matCount; + dst->materials = (b3SurfaceMaterial*)b3Alloc( (size_t)matCount * sizeof( b3SurfaceMaterial ) ); + b3SnapR_Bytes( r, dst->materials, matCount * (int)sizeof( b3SurfaceMaterial ) ); + } + else + { + dst->materials = NULL; + } + + int geoKind = b3SnapR_I32( r ); + + // Geometry + switch ( (b3ShapeType)geoKind ) + { + case b3_sphereShape: + b3SnapR_Bytes( r, &dst->sphere, sizeof( b3Sphere ) ); + break; + case b3_capsuleShape: + b3SnapR_Bytes( r, &dst->capsule, sizeof( b3Capsule ) ); + break; + case b3_hullShape: + { + uint32_t gid = b3SnapR_U32( r ); + if ( !r->ok ) + { + break; + } + if ( rdr == NULL || gid >= (uint32_t)rdr->slotCount ) + { + r->ok = false; + break; + } + // Hull is cloned into the world DB; pass raw bytes directly + b3RegistrySlot* slot = rdr->slots + gid; + dst->hull = b3AddHullToDatabase( world, (const b3HullData*)slot->bytes ); + break; + } + case b3_meshShape: + { + uint32_t gid = b3SnapR_U32( r ); + b3Vec3 scale; + b3SnapR_Bytes( r, &scale, sizeof( b3Vec3 ) ); + if ( !r->ok ) + { + break; + } + if ( rdr == NULL || gid >= (uint32_t)rdr->slotCount ) + { + r->ok = false; + break; + } + b3RegistrySlot* slot = rdr->slots + gid; + // Mesh is a self-contained blob used by reference; point straight at the pristine bytes. + dst->mesh.data = (const b3MeshData*)slot->bytes; + dst->mesh.scale = scale; + break; + } + case b3_heightShape: + { + uint32_t gid = b3SnapR_U32( r ); + if ( !r->ok ) + { + break; + } + if ( rdr == NULL || gid >= (uint32_t)rdr->slotCount ) + { + r->ok = false; + break; + } + b3RegistrySlot* slot = rdr->slots + gid; + // Self-contained blob used by reference; point straight at the pristine bytes. + dst->heightField = (const b3HeightFieldData*)slot->bytes; + break; + } + case b3_compoundShape: + { + uint32_t gid = b3SnapR_U32( r ); + if ( !r->ok ) + { + break; + } + if ( rdr == NULL || gid >= (uint32_t)rdr->slotCount ) + { + r->ok = false; + break; + } + b3RegistrySlot* slot = rdr->slots + gid; + if ( slot->live == NULL ) + { + slot->live = b3Alloc( (size_t)slot->byteCount ); + memcpy( slot->live, slot->bytes, (size_t)slot->byteCount ); + b3ConvertBytesToCompound( (uint8_t*)slot->live, slot->byteCount ); + } + dst->compound = (const b3CompoundData*)slot->live; + break; + } + default: + // Unknown geometry kind means a corrupt or unsupported snapshot. Fail the load instead + // of leaving a shape with no geometry. + r->ok = false; + break; + } + } + + // Release handles for shapes that are gone or were replaced this restore, so the host pool and any + // GPU resources they pinned do not leak across seeks. + if ( savedUserShape != NULL ) + { + for ( int i = 0; i < oldShapeCount; ++i ) + { + if ( savedUserShape[i] != NULL && world->destroyDebugShape != NULL ) + { + world->destroyDebugShape( savedUserShape[i], world->userDebugShapeContext ); + } + } + b3Free( savedUserShape, (size_t)oldShapeCount * sizeof( void* ) ); + b3Free( savedGeneration, (size_t)oldShapeCount * sizeof( uint16_t ) ); + } +} + +// Contact serialization. b3Contact is not fully POD: +// - manifolds: heap array of b3Manifold, allocated via b3AllocateManifolds +// - meshContact.triangleCache: heap b3Array, active when b3_simMeshContact flag is set +// Serialize: raw struct (with nulled manifolds/triangleCache), then manifolds, then triangleCache. +static void b3SerContacts( b3RecBuffer* buf, b3World* world ) +{ + int count = world->contacts.count; + b3SnapW_I32( buf, count ); + + for ( int i = 0; i < count; ++i ) + { + const b3Contact* c = world->contacts.data + i; + bool isLive = ( c->contactId == i ); + + // Write raw struct with pointer fields zeroed + b3Contact copy = *c; + copy.manifolds = NULL; + copy.bodySimIndexA = B3_NULL_INDEX; + copy.bodySimIndexB = B3_NULL_INDEX; + if ( copy.flags & b3_simMeshContact ) + { + copy.meshContact.triangleCache.data = NULL; + copy.meshContact.triangleCache.count = 0; + copy.meshContact.triangleCache.capacity = 0; + } + b3SnapW_Bytes( buf, ©, sizeof( b3Contact ) ); + + if ( !isLive ) + { + // Free slot: no heap data + b3SnapW_I32( buf, 0 ); // manifoldCount + // No triangleCache + continue; + } + + // Manifolds + b3SnapW_I32( buf, c->manifoldCount ); + if ( c->manifoldCount > 0 && c->manifolds != NULL ) + { + b3SnapW_Bytes( buf, c->manifolds, c->manifoldCount * (int)sizeof( b3Manifold ) ); + } + + // Mesh triangleCache + if ( c->flags & b3_simMeshContact ) + { + b3SnapW_I32( buf, c->meshContact.triangleCache.count ); + if ( c->meshContact.triangleCache.count > 0 ) + { + b3SnapW_Bytes( buf, c->meshContact.triangleCache.data, + c->meshContact.triangleCache.count * (int)sizeof( b3TriangleCache ) ); + } + } + } +} + +static void b3DesContacts( b3SnapReader* r, b3World* world ) +{ + int count = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, count, (int)sizeof( b3Contact ), (int)sizeof( b3Contact ) ) == false ) + { + r->ok = false; + } + if ( !r->ok ) + { + return; + } + + b3Array_Resize( world->contacts, count ); + memset( world->contacts.data, 0, (size_t)count * sizeof( b3Contact ) ); + + for ( int i = 0; i < count && r->ok; ++i ) + { + b3Contact* dst = world->contacts.data + i; + b3SnapR_Bytes( r, dst, sizeof( b3Contact ) ); + dst->manifolds = NULL; + dst->bodySimIndexA = B3_NULL_INDEX; + dst->bodySimIndexB = B3_NULL_INDEX; + if ( dst->flags & b3_simMeshContact ) + { + dst->meshContact.triangleCache.data = NULL; + dst->meshContact.triangleCache.count = 0; + dst->meshContact.triangleCache.capacity = 0; + } + + bool isLive = ( dst->contactId == i ); + + int manifoldCount = b3SnapR_I32( r ); + + if ( !r->ok ) + { + break; + } + + if ( isLive && manifoldCount > 0 ) + { + if ( b3SnapCheckCount( r, manifoldCount, (int)sizeof( b3Manifold ), (int)sizeof( b3Manifold ) ) == false ) + { + r->ok = false; + break; + } + dst->manifolds = b3AllocateManifolds( world, manifoldCount ); + dst->manifoldCount = manifoldCount; + b3SnapR_Bytes( r, dst->manifolds, manifoldCount * (int)sizeof( b3Manifold ) ); + } + else + { + dst->manifolds = NULL; + dst->manifoldCount = 0; + } + + // Mesh triangleCache + if ( isLive && ( dst->flags & b3_simMeshContact ) ) + { + int cacheCount = b3SnapR_I32( r ); + if ( !r->ok ) + { + break; + } + if ( cacheCount > 0 ) + { + if ( b3SnapCheckCount( r, cacheCount, (int)sizeof( b3TriangleCache ), (int)sizeof( b3TriangleCache ) ) == false ) + { + r->ok = false; + break; + } + b3Array_Resize( dst->meshContact.triangleCache, cacheCount ); + b3SnapR_Bytes( r, dst->meshContact.triangleCache.data, cacheCount * (int)sizeof( b3TriangleCache ) ); + } + } + } +} + +// Free per-object heap that b3DeserializeIntoShell will overwrite, +// so restoring over a populated world doesn't leak. +static void b3FreeLiveSimElements( b3World* world ) +{ + // Shape heap: materials and hull DB references + for ( int i = 0; i < world->shapes.count; ++i ) + { + b3Shape* s = world->shapes.data + i; + if ( s->id != i ) + { + continue; + } + // A single material lives inline (materials == NULL). Multi material meshes and compounds own + // the array, so free it exactly as b3DestroyShapeAllocations does. + if ( s->materials != NULL ) + { + b3Free( s->materials, (size_t)s->materialCount * sizeof( b3SurfaceMaterial ) ); + s->materials = NULL; + s->materialCount = 0; + } + // Hull is ref-counted in the world DB; release before overwrite so re-adding is ref-neutral. + if ( s->type == b3_hullShape && s->hull != NULL ) + { + b3RemoveHullFromDatabase( world, s->hull ); + s->hull = NULL; + } + // name / userData / userShape are host-owned; do not free + } + + // Contact heap: manifolds + mesh triangleCache + for ( int i = 0; i < world->contacts.count; ++i ) + { + b3Contact* c = world->contacts.data + i; + if ( c->contactId == i ) + { + if ( c->manifolds != NULL ) + { + b3FreeManifolds( world, c->manifolds, c->manifoldCount ); + c->manifolds = NULL; + c->manifoldCount = 0; + } + if ( c->flags & b3_simMeshContact ) + { + b3Array_Destroy( c->meshContact.triangleCache ); + } + } + } + + // Sensor heap: inner arrays + for ( int i = 0; i < world->sensors.count; ++i ) + { + b3Sensor* sensor = world->sensors.data + i; + b3Array_Destroy( sensor->hits ); + b3Array_Destroy( sensor->overlaps1 ); + b3Array_Destroy( sensor->overlaps2 ); + } + + // Island heap: inner arrays + for ( int i = 0; i < world->islands.count; ++i ) + { + b3Island* island = world->islands.data + i; + b3Array_Destroy( island->bodies ); + b3Array_Destroy( island->contacts ); + b3Array_Destroy( island->joints ); + } +} + +int b3SerializeWorld( b3World* world, b3RecBuffer* buf, b3Recording* rec ) +{ + int startSize = buf->size; + + // Snapshot header + b3SnapHeader hdr; + hdr.magic = B3_SNAP_MAGIC; + hdr.version = B3_SNAP_VERSION; + hdr.layoutHash = b3ComputeLayoutHash(); + hdr.flags = B3_ENABLE_VALIDATION ? B3_SNAP_FLAG_VALIDATION : 0u; +#if defined( BOX3D_DOUBLE_PRECISION ) + hdr.flags |= B3_SNAP_FLAG_DOUBLE_PRECISION; +#endif + b3SnapW_Bytes( buf, &hdr, (int)sizeof( hdr ) ); + + // World scalars + b3SerWorldConfig( buf, world ); + + // 6 id pools (Box3D has no chainIdPool) + b3SerIdPool( buf, &world->bodyIdPool ); + b3SerIdPool( buf, &world->shapeIdPool ); + b3SerIdPool( buf, &world->contactIdPool ); + b3SerIdPool( buf, &world->jointIdPool ); + b3SerIdPool( buf, &world->islandIdPool ); + b3SerIdPool( buf, &world->solverSetIdPool ); + + // Solver sets + int setCount = world->solverSets.count; + b3SnapW_I32( buf, setCount ); + for ( int i = 0; i < setCount; ++i ) + { + b3SerSolverSet( buf, world->solverSets.data + i ); + } + + // Sparse body array (userData is host wiring, zero it on the copy) + { + int bodyCount = world->bodies.count; + b3SnapW_I32( buf, bodyCount ); + for ( int i = 0; i < bodyCount; ++i ) + { + b3Body elem = world->bodies.data[i]; + elem.userData = NULL; + b3SnapW_Bytes( buf, &elem, sizeof( b3Body ) ); + } + } + + // Shape sparse array with geometry interning + b3SerShapes( buf, world, rec ); + + // Contact sparse array with manifold and mesh triangleCache + b3SerContacts( buf, world ); + + // Joint sparse array (userData scrubbed) + { + int jointCount = world->joints.count; + b3SnapW_I32( buf, jointCount ); + for ( int i = 0; i < jointCount; ++i ) + { + b3Joint elem = world->joints.data[i]; + elem.userData = NULL; + b3SnapW_Bytes( buf, &elem, sizeof( b3Joint ) ); + } + } + + // Sensors: shapeId + 3 inner arrays each + { + int sensorCount = world->sensors.count; + b3SnapW_I32( buf, sensorCount ); + for ( int i = 0; i < sensorCount; ++i ) + { + b3Sensor* s = world->sensors.data + i; + b3SnapW_I32( buf, s->shapeId ); + b3SerPodArray( buf, s->hits ); + b3SerPodArray( buf, s->overlaps1 ); + b3SerPodArray( buf, s->overlaps2 ); + } + } + + // Islands: 4 scalars + 3 inner arrays each + { + int islandCount = world->islands.count; + b3SnapW_I32( buf, islandCount ); + for ( int i = 0; i < islandCount; ++i ) + { + b3Island* island = world->islands.data + i; + b3SnapW_I32( buf, island->setIndex ); + b3SnapW_I32( buf, island->localIndex ); + b3SnapW_I32( buf, island->islandId ); + b3SnapW_I32( buf, island->constraintRemoveCount ); + b3SerPodArray( buf, island->bodies ); + b3SerPodArray( buf, island->contacts ); + b3SerPodArray( buf, island->joints ); + } + } + + // Broad phase + b3BroadPhase* bp = &world->broadPhase; + for ( int t = 0; t < b3_bodyTypeCount; ++t ) + { + b3SerTree( buf, &bp->trees[t] ); + } + for ( int t = 0; t < b3_bodyTypeCount; ++t ) + { + b3SerBitSet( buf, &bp->movedProxies[t] ); + } + b3SerPodArray( buf, bp->moveArray ); + b3SerHashSet( buf, &bp->pairSet ); + + // Constraint graph + b3ConstraintGraph* graph = &world->constraintGraph; + for ( int c = 0; c < B3_GRAPH_COLOR_COUNT; ++c ) + { + b3SerGraphColor( buf, &graph->colors[c], c == B3_OVERFLOW_INDEX ); + } + + b3SerNames( buf, &world->names ); + + return buf->size - startSize; +} + +bool b3DeserializeIntoShell( const uint8_t* data, int size, b3World* world, b3RecReader* rdr ) +{ + if ( data == NULL || size < (int)sizeof( b3SnapHeader ) ) + { + return false; + } + + // Validate header + b3SnapHeader hdr; + memcpy( &hdr, data, sizeof( hdr ) ); + if ( hdr.magic != B3_SNAP_MAGIC || hdr.version != B3_SNAP_VERSION ) + { + printf( "b3DeserializeIntoShell: bad magic/version\n" ); + return false; + } + bool imageDouble = ( hdr.flags & B3_SNAP_FLAG_DOUBLE_PRECISION ) != 0; +#if defined( BOX3D_DOUBLE_PRECISION ) + bool buildDouble = true; +#else + bool buildDouble = false; +#endif + if ( imageDouble != buildDouble ) + { + printf( "b3DeserializeIntoShell: precision mismatch\n" ); + return false; + } + if ( hdr.layoutHash != b3ComputeLayoutHash() ) + { + printf( "b3DeserializeIntoShell: layout hash mismatch\n" ); + return false; + } + + b3SnapReader readerStorage; + b3SnapReader* r = &readerStorage; + r->data = data; + r->cursor = (int)sizeof( b3SnapHeader ); + r->size = size; + r->ok = true; + + // Free existing per-object heap before overwriting + b3FreeLiveSimElements( world ); + + // 1. World scalars + b3DesWorldConfig( r, world ); + + // 2. 6 id pools; destroy the pre-created sets' pool state first + b3DesIdPool( r, &world->bodyIdPool ); + b3DesIdPool( r, &world->shapeIdPool ); + b3DesIdPool( r, &world->contactIdPool ); + b3DesIdPool( r, &world->jointIdPool ); + b3DesIdPool( r, &world->islandIdPool ); + b3DesIdPool( r, &world->solverSetIdPool ); + + // 3. Solver sets: destroy inner arrays of existing sets first + for ( int i = 0; i < world->solverSets.count; ++i ) + { + b3SolverSet* set = world->solverSets.data + i; + b3Array_Destroy( set->bodySims ); + b3Array_Destroy( set->bodyStates ); + b3Array_Destroy( set->jointSims ); + b3Array_Destroy( set->contactIndices ); + b3Array_Destroy( set->islandSims ); + } + + int setCount = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, setCount, (int)sizeof( b3SolverSet ), 6 * (int)sizeof( int ) ) == false ) + { + r->ok = false; + } + if ( r->ok ) + { + b3Array_Resize( world->solverSets, setCount ); + memset( world->solverSets.data, 0, (size_t)setCount * sizeof( b3SolverSet ) ); + for ( int i = 0; i < setCount; ++i ) + { + b3DesSolverSet( r, world->solverSets.data + i ); + } + } + + if ( !r->ok ) + { + return false; + } + + // 4. Body sparse array + { + int bodyCount = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, bodyCount, (int)sizeof( b3Body ), (int)sizeof( b3Body ) ) == false ) + { + r->ok = false; + } + if ( r->ok ) + { + b3Array_Resize( world->bodies, bodyCount ); + for ( int i = 0; i < bodyCount; ++i ) + { + b3SnapR_Bytes( r, world->bodies.data + i, sizeof( b3Body ) ); + world->bodies.data[i].userData = NULL; + } + } + } + + if ( !r->ok ) + { + return false; + } + + // 5. Shape sparse array + b3DesShapes( r, world, rdr ); + + if ( !r->ok ) + { + return false; + } + + // 6. Contact sparse array + b3DesContacts( r, world ); + + if ( !r->ok ) + { + return false; + } + + // 7. Joint sparse array + { + int jointCount = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, jointCount, (int)sizeof( b3Joint ), (int)sizeof( b3Joint ) ) == false ) + { + r->ok = false; + } + if ( r->ok ) + { + b3Array_Resize( world->joints, jointCount ); + for ( int i = 0; i < jointCount; ++i ) + { + b3SnapR_Bytes( r, world->joints.data + i, sizeof( b3Joint ) ); + world->joints.data[i].userData = NULL; + } + } + } + + // 8. Sensors + { + b3Array_Destroy( world->sensors ); + b3Array_Create( world->sensors ); + + int sensorCount = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, sensorCount, (int)sizeof( b3Sensor ), 4 * (int)sizeof( int ) ) == false ) + { + r->ok = false; + } + if ( r->ok ) + { + b3Array_Resize( world->sensors, sensorCount ); + memset( world->sensors.data, 0, (size_t)sensorCount * sizeof( b3Sensor ) ); + } + + for ( int i = 0; i < sensorCount && r->ok; ++i ) + { + b3Sensor* s = world->sensors.data + i; + s->shapeId = b3SnapR_I32( r ); + b3Array_Create( s->hits ); + b3Array_Create( s->overlaps1 ); + b3Array_Create( s->overlaps2 ); + b3DesPodArray( r, s->hits ); + b3DesPodArray( r, s->overlaps1 ); + b3DesPodArray( r, s->overlaps2 ); + } + } + + // 9. Islands + { + b3Array_Destroy( world->islands ); + b3Array_Create( world->islands ); + + int islandCount = b3SnapR_I32( r ); + if ( r->ok && b3SnapCheckCount( r, islandCount, (int)sizeof( b3Island ), 7 * (int)sizeof( int ) ) == false ) + { + r->ok = false; + } + if ( r->ok ) + { + b3Array_Resize( world->islands, islandCount ); + memset( world->islands.data, 0, (size_t)islandCount * sizeof( b3Island ) ); + } + + for ( int i = 0; i < islandCount && r->ok; ++i ) + { + b3Island* island = world->islands.data + i; + island->setIndex = b3SnapR_I32( r ); + island->localIndex = b3SnapR_I32( r ); + island->islandId = b3SnapR_I32( r ); + island->constraintRemoveCount = b3SnapR_I32( r ); + b3Array_Create( island->bodies ); + b3Array_Create( island->contacts ); + b3Array_Create( island->joints ); + b3DesPodArray( r, island->bodies ); + b3DesPodArray( r, island->contacts ); + b3DesPodArray( r, island->joints ); + } + } + + // 10. Broad phase + { + b3BroadPhase* bp = &world->broadPhase; + + for ( int t = 0; t < b3_bodyTypeCount; ++t ) + { + b3DesTree( r, &bp->trees[t] ); + } + for ( int t = 0; t < b3_bodyTypeCount; ++t ) + { + b3DesBitSet( r, &bp->movedProxies[t] ); + } + + b3Array_Destroy( bp->moveArray ); + b3Array_Create( bp->moveArray ); + b3DesPodArray( r, bp->moveArray ); + + b3DesHashSet( r, &bp->pairSet ); + // Transient moveResults/movePairs stay at shell's NULL/0 + } + + // 11. Constraint graph + { + b3ConstraintGraph* graph = &world->constraintGraph; + for ( int c = 0; c < B3_GRAPH_COLOR_COUNT; ++c ) + { + b3DesGraphColor( r, &graph->colors[c], c == B3_OVERFLOW_INDEX ); + } + } + + b3DesNames( r, &world->names ); + + return r->ok; +} diff --git a/vendor/box3d/src/src/world_snapshot.h b/vendor/box3d/src/src/world_snapshot.h new file mode 100644 index 000000000..7bf0065a1 --- /dev/null +++ b/vendor/box3d/src/src/world_snapshot.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-License-Identifier: MIT + +#pragma once + +#include "recording.h" +#include "recording_replay.h" + +#include +#include + +typedef struct b3World b3World; + +// Serialize the live world into buf, interning shape geometry into rec->registry. +// On success buf holds a self-contained snapshot image. Returns the byte count. +int b3SerializeWorld( b3World* world, b3RecBuffer* buf, b3Recording* rec ); + +// Overwrite a freshly-created (shell) world with the simulation state held in the +// snapshot image [data, size). Geometry references are resolved via the shared +// registry slots in rdr. Returns false on a corrupt or incompatible image. +bool b3DeserializeIntoShell( const uint8_t* data, int size, b3World* world, b3RecReader* rdr );

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 sn$h4RcrZSE^bAECxcjV-tY=_K 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�WME*J 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⪼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