diff --git a/crates/bevy_xpbd_3d/snapshots/bevy_xpbd_3d__tests__cubes_simulation_is_deterministic_across_machines.snap b/crates/bevy_xpbd_3d/snapshots/bevy_xpbd_3d__tests__cubes_simulation_is_deterministic_across_machines.snap index f31c49c3..c465799e 100644 --- a/crates/bevy_xpbd_3d/snapshots/bevy_xpbd_3d__tests__cubes_simulation_is_deterministic_across_machines.snap +++ b/crates/bevy_xpbd_3d/snapshots/bevy_xpbd_3d__tests__cubes_simulation_is_deterministic_across_machines.snap @@ -9,15 +9,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.8840356, - 0.49998537, - -6.313025, + -4.8840775, + 0.49998614, + -6.3129463, ), rotation: Quat( - 1.0629993e-5, - 0.093042724, - 6.6843427e-6, - 0.9956621, + 1.021307e-5, + 0.093049645, + 6.8060144e-6, + 0.9956615, ), scale: Vec3( 1.0, @@ -32,15 +32,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.596223, - 0.4999242, - -2.682186, + -4.5964494, + 0.49992126, + -2.6822681, ), rotation: Quat( - -1.5169596e-5, - -0.24600334, - -2.5550892e-6, - 0.969269, + -1.6636792e-5, + -0.24592756, + -2.7888416e-6, + 0.96928823, ), scale: Vec3( 1.0, @@ -55,15 +55,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.3235993, - 0.49993095, - -0.17135292, + -4.3248773, + 0.4999228, + -0.17086446, ), rotation: Quat( - -1.2296375e-6, - -0.1585128, - 8.7848475e-6, - 0.9873569, + 2.0519221e-6, + -0.15819, + 9.042746e-6, + 0.9874087, ), scale: Vec3( 1.0, @@ -78,15 +78,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.4427857, - 0.499941, - 2.6315987, + -4.4412217, + 0.49999866, + 2.6387563, ), rotation: Quat( - -1.2343673e-5, - -0.11525637, - -3.7697223e-6, - 0.9933358, + 3.540812e-6, + -0.116961814, + -2.04861e-6, + 0.9931364, ), scale: Vec3( 1.0, @@ -101,15 +101,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.1673584, - 0.4999489, - -5.5901766, + -2.1452253, + 0.4999498, + -5.5944157, ), rotation: Quat( - 5.015875e-5, - -0.1698646, - -1.2454645e-5, - 0.98546743, + 5.319262e-5, + -0.17677967, + -9.545255e-6, + 0.9842504, ), scale: Vec3( 1.0, @@ -124,15 +124,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.0178218, - 0.49993953, - -2.415313, + -2.0166335, + 0.49993542, + -2.4149897, ), rotation: Quat( - 1.3793331e-5, - -0.16026713, - 5.4145507e-6, - 0.98707366, + 2.5634718e-5, + -0.15976909, + 6.7164265e-6, + 0.9871544, ), scale: Vec3( 1.0, @@ -147,15 +147,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.2366908, - 0.4999408, - 0.3017618, + -2.2364225, + 0.4999671, + 0.30047134, ), rotation: Quat( - 6.457056e-6, - -0.15338342, - -5.119276e-6, - 0.98816675, + 6.6363104e-6, + -0.15254955, + -7.864287e-6, + 0.98829585, ), scale: Vec3( 1.0, @@ -170,15 +170,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.2011483, - 0.49997532, - 2.7484078, + -2.1912346, + 0.49995777, + 2.7510848, ), rotation: Quat( - -6.8380115e-7, - -0.0559736, - 1.1814222e-5, - 0.9984323, + 2.27204e-6, + -0.058584716, + -6.692791e-6, + 0.99828243, ), scale: Vec3( 1.0, @@ -193,15 +193,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.07663575, - 0.49999654, - -5.585707, + 0.080052644, + 0.4999958, + -5.6150384, ), rotation: Quat( - 3.3687318e-6, - -0.03938613, - -1.448745e-6, - 0.99922407, + 3.4785962e-6, + -0.058722805, + -3.4695254e-6, + 0.9982743, ), scale: Vec3( 1.0, @@ -216,15 +216,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.13636228, - 0.49991438, - -2.4614265, + 0.13608316, + 0.499906, + -2.457428, ), rotation: Quat( - -2.608979e-6, - -0.15122448, - 1.6181239e-5, - 0.98849946, + -9.838817e-7, + -0.14868803, + 1.8792523e-5, + 0.98888415, ), scale: Vec3( 1.0, @@ -239,15 +239,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.07284922, - 0.49994543, - 0.08777963, + 0.079418726, + 0.49993843, + 0.08562035, ), rotation: Quat( - 8.519603e-6, - -0.05437913, - 3.779078e-6, - 0.9985204, + 9.89041e-6, + -0.05644419, + -7.1605564e-6, + 0.99840575, ), scale: Vec3( 1.0, @@ -262,15 +262,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.36756644, - 0.49995184, - 2.5993185, + 0.38031706, + 0.49994525, + 2.603351, ), rotation: Quat( - -3.3736926e-6, - -0.043156873, - 8.6914915e-6, - 0.9990683, + 3.4154615e-7, + -0.04453798, + -1.6303159e-6, + 0.9990077, ), scale: Vec3( 1.0, @@ -285,15 +285,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.989145, - 0.499999, - -5.493995, + 2.9306567, + 0.5000009, + -5.4706345, ), rotation: Quat( - 1.9726706e-6, - -0.32046726, - -5.7511545e-7, - 0.9472596, + 2.3209077e-7, + -0.30563417, + -4.6851065e-7, + 0.95214903, ), scale: Vec3( 1.0, @@ -308,15 +308,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.635782, - 0.49993324, - -2.5089047, + 2.6305487, + 0.49999875, + -2.497731, ), rotation: Quat( - -5.3853164e-6, - -0.14143284, - 1.26430305e-5, - 0.98994786, + 1.8066021e-6, + -0.13722269, + -7.0844806e-7, + 0.9905402, ), scale: Vec3( 1.0, @@ -331,15 +331,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.3914063, - 0.4999211, - -0.14340033, + 2.393628, + 0.49995422, + -0.13766682, ), rotation: Quat( - 7.4643895e-6, - -0.1930573, - 7.1156483e-6, - 0.98118746, + 1.7823162e-5, + -0.19327234, + 1.97134e-6, + 0.98114514, ), scale: Vec3( 1.0, @@ -354,15 +354,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.479609, - 0.49994862, - 2.5013056, + 2.491556, + 0.499996, + 2.5504298, ), rotation: Quat( - -3.1640832e-6, - -0.07078866, - 5.936103e-7, - 0.99749136, + 5.196372e-6, + -0.058466423, + -2.7348212e-6, + 0.99828935, ), scale: Vec3( 1.0, @@ -377,15 +377,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.662555, + -4.662588, 2.499905, - -4.322822, + -4.3226237, ), rotation: Quat( - 1.3214758e-5, - -0.073117256, - -2.2066044e-6, - 0.99732333, + 1.483363e-5, + -0.073136546, + -6.0377903e-7, + 0.99732196, ), scale: Vec3( 1.0, @@ -400,15 +400,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.177807, - 2.499919, - -2.1251888, + -4.177984, + 2.4999137, + -2.125405, ), rotation: Quat( - -6.5700397e-6, - -0.009336207, - -2.4370202e-6, - 0.9999564, + -8.976946e-6, + -0.0091114715, + -5.6848403e-6, + 0.9999585, ), scale: Vec3( 1.0, @@ -423,15 +423,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.31659, - 2.4999256, - -0.048750307, + -4.3157706, + 2.4999058, + -0.051404595, ), rotation: Quat( - 3.8942517e-6, - 0.018639013, - 2.2025747e-6, - 0.99982625, + 1.3230207e-5, + 0.018637767, + -1.2472643e-6, + 0.9998263, ), scale: Vec3( 1.0, @@ -446,15 +446,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.076432, - 2.4999282, - 2.3561103, + -4.067958, + 2.4998908, + 2.3522725, ), rotation: Quat( - -2.4535753e-5, - 0.034401257, - -7.87359e-6, - 0.9994081, + 4.46566e-6, + 0.03355747, + 7.713113e-6, + 0.9994368, ), scale: Vec3( 1.0, @@ -469,15 +469,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.1665528, - 2.4998896, - -5.044417, + -2.159903, + 2.500702, + -5.0451503, ), rotation: Quat( - 7.115814e-5, - 0.11973387, - 1.754926e-5, - 0.992806, + 0.0008281269, + 0.11962082, + 0.00040496586, + 0.99281925, ), scale: Vec3( 1.0, @@ -492,15 +492,15 @@ expression: bodies ), Transform { translation: Vec3( - -1.966424, - 2.4999232, - -2.2515213, + -1.9693993, + 2.4999104, + -2.249833, ), rotation: Quat( - 1.4611958e-5, - -0.0063671465, - 6.067066e-6, - 0.99997973, + 2.9868295e-5, + -0.00567612, + 1.45236e-5, + 0.9999839, ), scale: Vec3( 1.0, @@ -515,15 +515,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.1194282, - 2.4999688, - 0.13462763, + -2.1200032, + 2.4990773, + 0.131278, ), rotation: Quat( - 6.79259e-6, - 0.065936126, - -1.5556876e-5, - 0.99782383, + -0.00017730892, + 0.06616363, + 0.00027539983, + 0.99780875, ), scale: Vec3( 1.0, @@ -538,15 +538,15 @@ expression: bodies ), Transform { translation: Vec3( - -1.7707083, - 2.4999244, - 2.5357254, + -1.7671894, + 2.499954, + 2.538702, ), rotation: Quat( - -3.5030455e-6, - 0.16199076, - 1.7886188e-6, - 0.98679227, + 3.2592714e-6, + 0.1611022, + -7.982803e-6, + 0.9869377, ), scale: Vec3( 1.0, @@ -561,15 +561,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.15299724, - 2.4999037, - -4.64109, + 0.12926933, + 2.499903, + -4.650124, ), rotation: Quat( - 3.745003e-5, - 0.1246561, - -9.563974e-6, - 0.9922, + 4.40926e-5, + 0.11931538, + 1.5501978e-6, + 0.9928564, ), scale: Vec3( 1.0, @@ -584,15 +584,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.14621699, - 2.4999173, - -2.1559896, + 0.1417445, + 2.4999175, + -2.158794, ), rotation: Quat( - 1.9200648e-5, - 0.0021915007, - -1.2948521e-5, - 0.9999976, + 2.3013969e-5, + 0.002523815, + -1.9412608e-5, + 0.99999684, ), scale: Vec3( 1.0, @@ -607,15 +607,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.092416026, - 2.4999309, - 0.13713561, + 0.09210145, + 2.4999523, + 0.14431445, ), rotation: Quat( - 1.0146473e-5, - 0.089868575, - -4.5559923e-6, - 0.9959536, + 9.503883e-6, + 0.088851586, + -2.6966816e-6, + 0.9960449, ), scale: Vec3( 1.0, @@ -630,15 +630,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.4520084, - 2.499944, - 2.3941014, + 0.46577328, + 2.4999602, + 2.3998425, ), rotation: Quat( - -3.920592e-6, - 0.11988139, - 1.4924854e-6, - 0.9927882, + 5.8005376e-6, + 0.118931964, + -6.0612965e-6, + 0.9929024, ), scale: Vec3( 1.0, @@ -653,15 +653,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.5828648, - 2.4999328, - -4.671327, + 2.549638, + 2.5053687, + -4.621387, ), rotation: Quat( - 1.441999e-5, - 0.15175816, - 1.0526054e-5, - 0.9884176, + -0.0021512546, + 0.18118577, + 0.0010396797, + 0.983446, ), scale: Vec3( 1.0, @@ -676,15 +676,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.641403, - 2.4999352, - -2.3333552, + 2.6509469, + 2.4999816, + -2.3103673, ), rotation: Quat( - -9.597759e-6, - 0.09345308, - 9.5141246e-7, - 0.9956237, + 8.812365e-7, + 0.10688069, + -1.4779067e-5, + 0.9942719, ), scale: Vec3( 1.0, @@ -699,15 +699,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.171319, - 2.499942, - -0.115333684, + 2.1744192, + 2.4999044, + -0.11582472, ), rotation: Quat( - 4.096211e-7, - 0.06503047, - -8.5366755e-6, - 0.99788326, + 6.1420033e-6, + 0.06586435, + -3.1262243e-6, + 0.9978286, ), scale: Vec3( 1.0, @@ -722,15 +722,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.5630872, - 2.4999466, - 1.9173907, + 2.5712745, + 2.4999385, + 1.9223919, ), rotation: Quat( - -1.354222e-5, - 0.090742335, - 9.5541e-7, - 0.9958744, + -6.1123114e-6, + 0.095650524, + -2.5949867e-6, + 0.995415, ), scale: Vec3( 1.0, @@ -745,15 +745,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.7218695, - 4.499918, - -4.0454965, + -4.7222004, + 4.499917, + -4.0457153, ), rotation: Quat( - 5.5629556e-7, - -0.023947848, - -4.821814e-6, - 0.9997132, + 2.3874393e-6, + -0.023879122, + -2.877863e-6, + 0.99971485, ), scale: Vec3( 1.0, @@ -768,15 +768,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.4282675, - 4.4999194, - -1.8132097, + -4.4281816, + 4.4999204, + -1.8141768, ), rotation: Quat( - -8.50616e-6, - -0.050489318, - 3.3534768e-6, - 0.9987246, + -9.476739e-6, + -0.049846936, + -8.67436e-7, + 0.9987569, ), scale: Vec3( 1.0, @@ -791,15 +791,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.4162836, - 4.4999075, - 0.5227382, + -4.4111433, + 4.499904, + 0.5215338, ), rotation: Quat( - 6.968412e-6, - -0.021176213, - -7.754899e-6, - 0.99977577, + 5.2289547e-6, + -0.018272685, + 4.2383494e-6, + 0.99983305, ), scale: Vec3( 1.0, @@ -814,15 +814,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.1297684, - 4.499882, - 2.9494112, + -4.1206765, + 4.499867, + 2.9368165, ), rotation: Quat( - -2.9195377e-5, - 0.052963983, - -4.4077788e-6, - 0.99859643, + -1.8833022e-6, + 0.050360065, + 8.785629e-6, + 0.99873114, ), scale: Vec3( 1.0, @@ -837,15 +837,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.1040337, - 4.499812, - -4.583855, + -2.0982618, + 4.4999485, + -4.5954814, ), rotation: Quat( - 8.291803e-5, - 0.07558479, - 1.48603785e-5, - 0.9971394, + 0.0008569843, + 0.07476338, + 0.0003668467, + 0.99720085, ), scale: Vec3( 1.0, @@ -860,15 +860,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.0399349, - 4.4998903, - -1.9808781, + -2.0418162, + 4.499855, + -1.9817297, ), rotation: Quat( - 8.772537e-6, - 0.022853626, - -2.8496009e-8, - 0.9997388, + 3.1433286e-5, + 0.023295468, + 4.817175e-6, + 0.9997286, ), scale: Vec3( 1.0, @@ -883,15 +883,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.2919278, - 4.4999065, - 0.1247377, + -2.2922916, + 4.4996166, + 0.123583816, ), rotation: Quat( - 2.7425397e-6, - 0.00027707758, - -9.8485025e-6, - 0.99999994, + -0.00014370061, + 0.0006513288, + -5.3522206e-5, + 0.99999976, ), scale: Vec3( 1.0, @@ -906,15 +906,15 @@ expression: bodies ), Transform { translation: Vec3( - -1.9134618, - 4.499895, - 2.9461567, + -1.9065185, + 4.499897, + 2.948519, ), rotation: Quat( - -7.552541e-8, - 0.16723502, - -1.7282146e-5, - 0.98591703, + 7.2758303e-6, + 0.16466804, + -1.0518872e-5, + 0.98634905, ), scale: Vec3( 1.0, @@ -929,15 +929,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.10139636, - 4.499856, - -4.2290635, + 0.084558636, + 4.499845, + -4.2212543, ), rotation: Quat( - 4.1247542e-5, - 0.012372264, - -1.264204e-5, - 0.99992347, + 5.0826264e-5, + 0.0032887203, + -1.1353104e-6, + 0.9999946, ), scale: Vec3( 1.0, @@ -952,15 +952,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.046615515, - 4.499892, - -1.9993606, + 0.046418056, + 4.4998946, + -1.9996858, ), rotation: Quat( - 1.31747875e-5, - 0.032713663, - -1.9446587e-5, - 0.99946475, + 1.8233854e-5, + 0.033099268, + -2.5970241e-5, + 0.99945205, ), scale: Vec3( 1.0, @@ -975,15 +975,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.10879148, - 4.4999266, - 0.14372009, + 0.115307376, + 4.499905, + 0.15320742, ), rotation: Quat( - 1.1733811e-5, - 0.06736381, - -9.170773e-6, - 0.99772847, + 1.3577231e-5, + 0.06730782, + -3.2129797e-6, + 0.9977323, ), scale: Vec3( 1.0, @@ -998,15 +998,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.15608701, - 4.4999433, - 2.5169132, + 0.1633159, + 4.4999037, + 2.5240226, ), rotation: Quat( - -6.910158e-6, - 0.1279863, - -2.1175604e-6, - 0.99177593, + 5.585338e-6, + 0.12953368, + -8.621638e-6, + 0.991575, ), scale: Vec3( 1.0, @@ -1021,15 +1021,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.4965358, - 4.4999065, - -4.248469, + 2.4627173, + 4.523821, + -4.223433, ), rotation: Quat( - -7.980201e-6, - 0.10847425, - -2.2054703e-6, - 0.99409926, + 0.002565093, + 0.12797171, + -0.0035405958, + 0.9917682, ), scale: Vec3( 1.0, @@ -1044,15 +1044,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.3044858, - 4.499934, - -2.039, + 2.2974544, + 4.4999475, + -2.0317554, ), rotation: Quat( - -3.4714603e-6, - 0.034224026, - -5.390694e-6, - 0.9994142, + 7.264934e-6, + 0.04885285, + -1.3812624e-5, + 0.998806, ), scale: Vec3( 1.0, @@ -1067,15 +1067,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.2902527, - 4.499874, - 0.18958372, + 2.2948377, + 4.499882, + 0.19410415, ), rotation: Quat( - -1.8717392e-6, - 0.069902435, - -5.448829e-6, - 0.9975538, + -1.9167567e-6, + 0.07155434, + -2.2111035e-6, + 0.9974367, ), scale: Vec3( 1.0, @@ -1090,15 +1090,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.3585987, - 4.499913, - 2.344395, + 2.3714726, + 4.4999046, + 2.3423145, ), rotation: Quat( - -1.38780115e-5, - 0.104511954, - -1.4291491e-5, - 0.99452364, + 3.7926222e-6, + 0.105108425, + -1.8758468e-5, + 0.99446076, ), scale: Vec3( 1.0, @@ -1113,15 +1113,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.4633474, - 6.4998736, - -4.0632877, + -4.4638953, + 6.499873, + -4.0630817, ), rotation: Quat( - -3.2037321e-6, - -0.039527066, - -1.0842633e-5, - 0.9992185, + -1.0684977e-6, + -0.03946447, + -8.834009e-6, + 0.99922097, ), scale: Vec3( 1.0, @@ -1136,15 +1136,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.5414343, - 6.499908, - -1.8388224, + -4.541287, + 6.499911, + -1.8390753, ), rotation: Quat( - -1.7965837e-6, - -0.075223595, - 4.8294905e-6, - 0.9971667, + -2.7836097e-6, + -0.07500307, + 3.1182046e-6, + 0.9971833, ), scale: Vec3( 1.0, @@ -1159,15 +1159,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.4133406, - 6.4999156, - 0.5323253, + -4.4054413, + 6.499905, + 0.5342264, ), rotation: Quat( - 7.1565705e-6, - -0.029635495, - -8.7967455e-6, - 0.9995608, + 1.0029094e-5, + -0.028178202, + -8.311371e-7, + 0.9996029, ), scale: Vec3( 1.0, @@ -1182,15 +1182,15 @@ expression: bodies ), Transform { translation: Vec3( - -4.4521794, - 6.4998703, - 2.7196393, + -4.4478917, + 6.4998593, + 2.6989908, ), rotation: Quat( - -2.5847663e-5, - 0.018090103, - -5.8681953e-6, - 0.9998364, + 1.7115982e-6, + 0.015391452, + 6.6103903e-6, + 0.99988157, ), scale: Vec3( 1.0, @@ -1205,15 +1205,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.015195, - 6.4997816, - -4.391034, + -2.0118814, + 6.4996743, + -4.408518, ), rotation: Quat( - 8.634205e-5, - 0.0054050996, - 1.061444e-5, - 0.9999854, + 0.0008833011, + 0.0048433905, + 0.00030778215, + 0.99998784, ), scale: Vec3( 1.0, @@ -1228,15 +1228,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.066568, - 6.4998965, - -2.0410511, + -2.0685985, + 6.4998636, + -2.0412266, ), rotation: Quat( - 9.750119e-6, - -0.01672879, - -3.2366404e-6, - 0.99986005, + 3.2721695e-5, + -0.016413745, + 6.26121e-7, + 0.9998653, ), scale: Vec3( 1.0, @@ -1251,15 +1251,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.1620016, - 6.4999013, - -0.009376085, + -2.1621423, + 6.4998426, + -0.009622783, ), rotation: Quat( - 6.3031325e-7, - -0.03387773, - -1.3532226e-5, - 0.999426, + -6.4502156e-6, + -0.03323228, + -4.2704098e-5, + 0.99944764, ), scale: Vec3( 1.0, @@ -1274,15 +1274,15 @@ expression: bodies ), Transform { translation: Vec3( - -2.4230695, - 6.4998817, - 2.3860047, + -2.4178045, + 6.4998765, + 2.3701563, ), rotation: Quat( - -7.3331294e-6, - 0.020392325, - -6.328518e-6, - 0.99979204, + 3.315791e-6, + 0.01670394, + -1.0139086e-5, + 0.99986047, ), scale: Vec3( 1.0, @@ -1297,15 +1297,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.1737153, - 6.4998555, - -4.2430644, + 0.15457933, + 6.4998455, + -4.2195, ), rotation: Quat( - 4.434232e-5, - -0.010705035, - -1.7549892e-5, - 0.9999427, + 5.3893982e-5, + -0.009229558, + -5.5537885e-6, + 0.9999574, ), scale: Vec3( 1.0, @@ -1320,15 +1320,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.017470408, - 6.499897, - -2.112041, + 0.020253193, + 6.4998994, + -2.1103811, ), rotation: Quat( - 7.738846e-6, - -0.0091372, - -1.9105904e-5, - 0.9999583, + 1.13126125e-5, + -0.008870928, + -2.4882505e-5, + 0.99996066, ), scale: Vec3( 1.0, @@ -1343,15 +1343,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.33719364, - 6.4999237, - 0.14164668, + 0.3434779, + 6.4999046, + 0.15167767, ), rotation: Quat( - 1.2984191e-5, - 0.003949311, - -1.2300544e-5, - 0.9999922, + 1.3513705e-5, + 0.004419541, + -5.4249876e-6, + 0.9999902, ), scale: Vec3( 1.0, @@ -1366,15 +1366,15 @@ expression: bodies ), Transform { translation: Vec3( - 0.040235426, - 6.499908, - 2.3660412, + 0.045836154, + 6.499876, + 2.372979, ), rotation: Quat( - -9.923653e-6, - 0.080303445, - -1.04829084e-7, - 0.99677044, + 3.31346e-6, + 0.080713205, + -7.773753e-6, + 0.99673736, ), scale: Vec3( 1.0, @@ -1389,15 +1389,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.700329, - 6.499869, - -4.5122986, + 2.69063, + 6.5255804, + -4.4832582, ), rotation: Quat( - -7.703569e-6, - 0.329856, - -1.0006887e-5, - 0.94403124, + 0.0036721418, + 0.35055444, + -0.0024414826, + 0.93653196, ), scale: Vec3( 1.0, @@ -1412,15 +1412,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.4898252, - 6.4999285, - -1.9827287, + 2.4816194, + 6.5043006, + -1.9823791, ), rotation: Quat( - -7.970375e-8, - 0.030127004, - -7.457347e-6, - 0.99954605, + -0.00030939886, + 0.041006003, + -0.0017860039, + 0.99915725, ), scale: Vec3( 1.0, @@ -1435,15 +1435,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.3679268, - 6.4998736, - 0.054523207, + 2.3744953, + 6.4998674, + 0.0644396, ), rotation: Quat( - -4.5444162e-6, - 0.020520307, - -8.355963e-6, - 0.9997894, + 7.2211146e-6, + 0.020571178, + -3.6073593e-6, + 0.9997884, ), scale: Vec3( 1.0, @@ -1458,15 +1458,15 @@ expression: bodies ), Transform { translation: Vec3( - 2.463651, - 6.499916, - 2.4235425, + 2.472841, + 6.4999046, + 2.422926, ), rotation: Quat( - -1.1169432e-5, - 0.17963791, - -1.8230827e-5, - 0.9837328, + 5.2217392e-6, + 0.17963935, + -2.0568676e-5, + 0.9837325, ), scale: Vec3( 1.0, diff --git a/src/components/mod.rs b/src/components/mod.rs index a7178f71..101a9041 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -135,8 +135,6 @@ impl RigidBody { /// collisions or other constraints, or when gravity changes, or when the body's /// position, rotation, velocity, or external forces are modified. /// -/// Note that sleeping can cause unrealistic behaviour in some cases. -/// For example, removing the floor under sleeping bodies can leave them floating in the air. /// Sleeping can be disabled for specific entities with the [`SleepingDisabled`] component, /// or for all entities by setting the [`SleepingThreshold`] to a negative value. #[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq, Eq, From)] diff --git a/src/lib.rs b/src/lib.rs index 9da7449f..a6de0c03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -518,8 +518,9 @@ pub enum PhysicsSet { /// 3. Solve positional and angular constraints /// 4. Update velocities /// 5. Solve velocity constraints (dynamic friction and restitution) -/// 3. Sleeping -/// 4. Spatial queries +/// 3. Report contacts (send collision events) +/// 4. Sleeping +/// 5. Spatial queries #[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum PhysicsStepSet { /// Responsible for collecting pairs of potentially colliding entities into [`BroadCollisionPairs`] using @@ -531,6 +532,10 @@ pub enum PhysicsStepSet { /// /// See [`SubstepSet`] and [`SubstepSchedule`]. Substeps, + /// Responsible for sending collision events and updating [`CollidingEntities`]. + /// + /// See [`ContactReportingPlugin`]. + ReportContacts, /// Responsible for controlling when bodies should be deactivated and marked as [`Sleeping`]. /// /// See [`SleepingPlugin`]. diff --git a/src/plugins/contact_reporting.rs b/src/plugins/contact_reporting.rs new file mode 100644 index 00000000..b5745848 --- /dev/null +++ b/src/plugins/contact_reporting.rs @@ -0,0 +1,79 @@ +//! Sends collision events and updates [`CollidingEntities`]. +//! +//! See [`ContactReportingPlugin`]. + +use crate::prelude::*; + +/// Sends collision events and updates [`CollidingEntities`]. +/// +/// The following collision events are sent each frame in [`PhysicsStepSet::ReportContacts`]: +/// +/// - [`Collision`] +/// - [`CollisionStarted`] +/// - [`CollisionEnded`] +pub struct ContactReportingPlugin; + +impl Plugin for ContactReportingPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .add_event::() + .add_event::(); + + let physics_schedule = app + .get_schedule_mut(PhysicsSchedule) + .expect("add PhysicsSchedule first"); + + physics_schedule.add_systems(report_contacts.in_set(PhysicsStepSet::ReportContacts)); + } +} + +/// A [collision event](Collider#collision-events) that is sent for each contact pair during the narrow phase. +#[derive(Event, Clone, Debug, PartialEq)] +pub struct Collision(pub Contacts); + +/// A [collision event](Collider#collision-events) that is sent when two entities start colliding. +#[derive(Event, Clone, Debug, PartialEq)] +pub struct CollisionStarted(pub Entity, pub Entity); + +/// A [collision event](Collider#collision-events) that is sent when two entities stop colliding. +#[derive(Event, Clone, Debug, PartialEq)] +pub struct CollisionEnded(pub Entity, pub Entity); + +/// Sends collision events and updates [`CollidingEntities`]. +pub fn report_contacts( + mut colliders: Query<&mut CollidingEntities>, + collisions: Res, + mut collision_ev_writer: EventWriter, + mut collision_started_ev_writer: EventWriter, + mut collision_ended_ev_writer: EventWriter, +) { + for ((entity1, entity2), contacts) in collisions.get_internal().iter() { + if contacts.during_current_frame { + collision_ev_writer.send(Collision(contacts.clone())); + + // Collision started + if contacts.during_current_frame && !contacts.during_previous_frame { + collision_started_ev_writer.send(CollisionStarted(*entity1, *entity2)); + + if let Ok(mut colliding_entities1) = colliders.get_mut(*entity1) { + colliding_entities1.insert(*entity2); + } + if let Ok(mut colliding_entities2) = colliders.get_mut(*entity2) { + colliding_entities2.insert(*entity1); + } + } + } + + // Collision ended + if !contacts.during_current_frame { + collision_ended_ev_writer.send(CollisionEnded(*entity1, *entity2)); + + if let Ok(mut colliding_entities1) = colliders.get_mut(*entity1) { + colliding_entities1.remove(entity2); + } + if let Ok(mut colliding_entities2) = colliders.get_mut(*entity2) { + colliding_entities2.remove(entity1); + } + } + } +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 707f8858..b59ad6ac 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -21,6 +21,7 @@ //! - [`SubstepSchedule`] and [`SubstepSet`] pub mod broad_phase; +pub mod contact_reporting; #[cfg(feature = "debug-plugin")] pub mod debug; pub mod integrator; @@ -33,10 +34,11 @@ pub mod spatial_query; pub mod sync; pub use broad_phase::BroadPhasePlugin; +pub use contact_reporting::*; #[cfg(feature = "debug-plugin")] pub use debug::*; pub use integrator::IntegratorPlugin; -pub use narrow_phase::*; +pub use narrow_phase::{contact_data::*, contact_query::*, NarrowPhaseConfig, NarrowPhasePlugin}; pub use prepare::PreparePlugin; pub use setup::*; pub use sleeping::SleepingPlugin; @@ -59,6 +61,7 @@ use bevy::prelude::*; /// [AABB](ColliderAabb) intersection checks. /// - [`IntegratorPlugin`]: Integrates Newton's 2nd law of motion, applying forces and moving entities according to their velocities. /// - [`NarrowPhasePlugin`]: Computes contacts between entities and sends collision events. +/// - [`ContactReportingPlugin`]: Sends collision events and updates [`CollidingEntities`]. /// - [`SolverPlugin`]: Solves positional and angular [constraints], updates velocities and solves velocity constraints /// (dynamic [friction](Friction) and [restitution](Restitution)). /// - [`SleepingPlugin`]: Controls when bodies should be deactivated and marked as [`Sleeping`] to improve performance. @@ -197,6 +200,7 @@ impl PluginGroup for PhysicsPlugins { .add(BroadPhasePlugin) .add(IntegratorPlugin) .add(NarrowPhasePlugin) + .add(ContactReportingPlugin) .add(SolverPlugin) .add(SleepingPlugin) .add(SpatialQueryPlugin::new(self.schedule.dyn_clone())) diff --git a/src/plugins/narrow_phase/mod.rs b/src/plugins/narrow_phase/mod.rs index 5d3f7791..e96d78f1 100644 --- a/src/plugins/narrow_phase/mod.rs +++ b/src/plugins/narrow_phase/mod.rs @@ -1,11 +1,11 @@ -//! Computes contacts between entities and sends collision events. +//! Computes contacts between entities. //! //! See [`NarrowPhasePlugin`]. -mod contact_data; +pub(super) mod contact_data; pub mod contact_query; -use bevy::utils::HashSet; +use bevy::ecs::query::Has; pub use contact_data::*; pub use contact_query::*; @@ -13,70 +13,49 @@ use crate::prelude::*; #[cfg(feature = "parallel")] use bevy::tasks::{ComputeTaskPool, ParallelSlice}; -/// Computes contacts between entities and sends collision events. +/// Computes contacts between entities. /// /// Collisions are only checked between entities contained in [`BroadCollisionPairs`], /// which is handled by the [`BroadPhasePlugin`]. /// -/// The following collision events are sent each frame: -/// -/// - [`Collision`] -/// - [`CollisionStarted`] -/// - [`CollisionEnded`] +/// The results of the narrow phase are added into [`Collisions`]. pub struct NarrowPhasePlugin; impl Plugin for NarrowPhasePlugin { fn build(&self, app: &mut App) { - app.add_event::() - .add_event::() - .add_event::() - .init_resource::() + app.init_resource::() .init_resource::() .register_type::(); - let physics_schedule = app - .get_schedule_mut(PhysicsSchedule) - .expect("add PhysicsSchedule first"); - - physics_schedule.add_systems( - ( - // Reset collision states before the narrow phase - (|mut collisions: ResMut| { - collisions.iter_mut().for_each(|contacts| { - contacts.during_previous_frame = contacts.during_current_frame; - contacts.during_current_frame = false; - contacts.during_current_substep = false; - }) - }) - .after(PhysicsStepSet::BroadPhase) - .before(PhysicsStepSet::Substeps), - // Send collision events - send_collision_events - .after(PhysicsStepSet::Sleeping) - .before(PhysicsStepSet::SpatialQuery), - ) - .chain(), - ); - - let substep_schedule = app - .get_schedule_mut(SubstepSchedule) - .expect("add SubstepSchedule first"); - - substep_schedule.add_systems( - (reset_substep_collision_states, collect_collisions) - .chain() - .in_set(SubstepSet::NarrowPhase), - ); - - // Remove collisions against removed colliders from `Collisions` - app.add_systems( - Last, - |mut removals: RemovedComponents, mut collisions: ResMut| { - for removed in removals.iter() { - collisions.remove_collisions_with_entity(removed); - } - }, - ); + // Manage collision states like `during_current_frame` and remove old contacts + // Todo: It would be nice not to have collision state logic in the narrow phase + app.get_schedule_mut(PhysicsSchedule) + .expect("add PhysicsSchedule first") + .add_systems( + ( + // Reset collision states before the substepping loop + reset_collision_states + .after(PhysicsStepSet::BroadPhase) + .before(PhysicsStepSet::Substeps), + // Remove ended collisions after contact reporting + ((|mut collisions: ResMut| { + collisions.retain(|contacts| contacts.during_current_frame) + }),) + .chain() + .after(PhysicsStepSet::ReportContacts) + .before(PhysicsStepSet::Sleeping), + ) + .chain(), + ); + + // Reset substep collision states and collect contacts into `Collisions` + app.get_schedule_mut(SubstepSchedule) + .expect("add SubstepSchedule first") + .add_systems( + (reset_substep_collision_states, collect_collisions) + .chain() + .in_set(SubstepSet::NarrowPhase), + ); } } @@ -103,18 +82,6 @@ impl Default for NarrowPhaseConfig { } } -/// A [collision event](Collider#collision-events) that is sent for each contact pair during the narrow phase. -#[derive(Event, Clone, Debug, PartialEq)] -pub struct Collision(pub Contacts); - -/// A [collision event](Collider#collision-events) that is sent when two entities start colliding. -#[derive(Event, Clone, Debug, PartialEq)] -pub struct CollisionStarted(pub Entity, pub Entity); - -/// A [collision event](Collider#collision-events) that is sent when two entities stop colliding. -#[derive(Event, Clone, Debug, PartialEq)] -pub struct CollisionEnded(pub Entity, pub Entity); - #[allow(clippy::too_many_arguments)] #[allow(clippy::type_complexity)] fn collect_collisions( @@ -146,17 +113,15 @@ fn collect_collisions( let position2 = position2.0 + accumulated_translation2.copied().unwrap_or_default().0; - let during_previous_frame = collisions - .get_internal() - .get(&(*entity1, *entity2)) - .map_or(false, |c| c.during_previous_frame); + let previous_contact = collisions.get_internal().get(&(*entity1, *entity2)); let contacts = Contacts { entity1: *entity1, entity2: *entity2, during_current_frame: true, during_current_substep: true, - during_previous_frame, + during_previous_frame: previous_contact + .map_or(false, |c| c.during_previous_frame), manifolds: contact_query::contact_manifolds( collider1, position1, @@ -191,17 +156,15 @@ fn collect_collisions( let position2 = position2.0 + accumulated_translation2.copied().unwrap_or_default().0; - let during_previous_frame = collisions - .get_internal() - .get(&(*entity1, *entity2)) - .map_or(false, |c| c.during_previous_frame); + let previous_contact = collisions.get_internal().get(&(*entity1, *entity2)); let contacts = Contacts { entity1: *entity1, entity2: *entity2, during_current_frame: true, during_current_substep: true, - during_previous_frame, + during_previous_frame: previous_contact + .map_or(false, |c| c.during_previous_frame), manifolds: contact_query::contact_manifolds( collider1, position1, @@ -221,78 +184,39 @@ fn collect_collisions( } } -fn reset_substep_collision_states(mut collisions: ResMut) { - for contacts in collisions.get_internal_mut().values_mut() { - contacts.during_current_substep = false; - } -} - -/// Sends collision events and updates [`CollidingEntities`]. -fn send_collision_events( - sleeping: Query<(Ref, Ref)>, - mut colliders: Query<&mut CollidingEntities>, +// TODO: The collision state handling feels a bit confusing and error-prone. +// Ideally, the narrow phase wouldn't need to handle it at all, or it would at least be simpler. +/// Resets collision states like `during_current_frame` and `during_previous_frame`. +pub fn reset_collision_states( mut collisions: ResMut, - mut collision_ev_writer: EventWriter, - mut collision_started_ev_writer: EventWriter, - mut collision_ended_ev_writer: EventWriter, + query: Query<(Option<&RigidBody>, Has)>, ) { - let mut ended_collisions = HashSet::<(Entity, Entity)>::new(); - - for ((entity1, entity2), contacts) in collisions.get_internal_mut().iter_mut() { - // Collision ended - if !contacts.during_current_frame { - // Keep the collision active if the bodies were colliding during the previous frame - // but neither body has moved, for example when they are sleeping. - if let Ok([(pos1, rot1), (pos2, rot2)]) = sleeping.get_many([*entity1, *entity2]) { - if !pos1.is_changed() - && !rot1.is_changed() - && !pos2.is_changed() - && !rot2.is_changed() - { - contacts.during_current_frame = true; - continue; - } - } - - ended_collisions.insert((*entity1, *entity2)); - - if let Ok(mut colliding_entities1) = colliders.get_mut(*entity1) { - colliding_entities1.remove(entity2); - } - if let Ok(mut colliding_entities2) = colliders.get_mut(*entity2) { - colliding_entities2.remove(entity1); - } - - continue; - } - - collision_ev_writer.send(Collision(contacts.clone())); - - // Collision started - if contacts.during_current_frame && !contacts.during_previous_frame { - collision_started_ev_writer.send(CollisionStarted(*entity1, *entity2)); - contacts.during_previous_frame = true; - - if let Ok(mut colliding_entities1) = colliders.get_mut(*entity1) { - colliding_entities1.insert(*entity2); - } else { - ended_collisions.insert((*entity1, *entity2)); - } - if let Ok(mut colliding_entities2) = colliders.get_mut(*entity2) { - colliding_entities2.insert(*entity1); + for contacts in collisions.get_internal_mut().values_mut() { + if let Ok([(rb1, sleeping1), (rb2, sleeping2)]) = + query.get_many([contacts.entity1, contacts.entity2]) + { + let active1 = !rb1.map_or(true, |rb| rb.is_static()) && !sleeping1; + let active2 = !rb2.map_or(true, |rb| rb.is_static()) && !sleeping2; + + // Reset collision states if either of the bodies is active (not static or sleeping) + // Otherwise, the bodies are still in contact. + if active1 || active2 { + contacts.during_previous_frame = true; + contacts.during_current_frame = false; + contacts.during_current_substep = false; } else { - ended_collisions.insert((*entity1, *entity2)); + contacts.during_previous_frame = true; + contacts.during_current_frame = true; } + } else { + contacts.during_current_frame = false; } } +} - // Send CollisionEnded events - collision_ended_ev_writer.send_batch( - ended_collisions - .iter() - .map(|(entity1, entity2)| CollisionEnded(*entity1, *entity2)), - ); - - // Clear collisions at the end of each frame to avoid unnecessary iteration and memory usage - collisions.retain(|contacts| !ended_collisions.contains(&(contacts.entity1, contacts.entity2))); +/// Reset `during_current_substep` for each collision in [`Collisions`]. +pub fn reset_substep_collision_states(mut collisions: ResMut) { + for contacts in collisions.get_internal_mut().values_mut() { + contacts.during_current_substep = false; + } } diff --git a/src/plugins/setup.rs b/src/plugins/setup.rs index b096f5b0..34a29c5d 100644 --- a/src/plugins/setup.rs +++ b/src/plugins/setup.rs @@ -145,6 +145,7 @@ impl Plugin for PhysicsSetupPlugin { ( PhysicsStepSet::BroadPhase, PhysicsStepSet::Substeps, + PhysicsStepSet::ReportContacts, PhysicsStepSet::Sleeping, PhysicsStepSet::SpatialQuery, ) diff --git a/src/plugins/sleeping.rs b/src/plugins/sleeping.rs index 9536127a..d6a8f279 100644 --- a/src/plugins/sleeping.rs +++ b/src/plugins/sleeping.rs @@ -22,12 +22,7 @@ impl Plugin for SleepingPlugin { fn build(&self, app: &mut App) { app.get_schedule_mut(PhysicsSchedule) .expect("add PhysicsSchedule first") - .add_systems( - (apply_deferred, wake_up_on_collision_ended) - .chain() - .after(PhysicsStepSet::Substeps) - .before(PhysicsStepSet::Sleeping), - ) + .add_systems(wake_up_on_collision_ended.in_set(PhysicsStepSet::ReportContacts)) .add_systems( ( mark_sleeping_bodies, @@ -130,27 +125,37 @@ fn wake_all_sleeping_bodies( /// Wakes up bodies when they stop colliding. fn wake_up_on_collision_ended( mut commands: Commands, - mut colliding: Query<&CollidingEntities, (Changed, Without)>, - mut collision_ended_ev_reader: EventReader, - mut sleeping: Query<(Entity, &CollidingEntities, &mut TimeSleeping), With>, + moved_bodies: Query<(), (Changed, Without)>, + collisions: Res, + mut sleeping: Query<(Entity, &mut TimeSleeping), With>, ) { - // Wake up bodies when a body they're colliding with moves - for colliding_entities1 in colliding.iter_mut() { - let mut query = sleeping.iter_many_mut(colliding_entities1.iter()); - if let Some((entity2, _, mut time_sleeping)) = query.fetch_next() { - commands.entity(entity2).remove::(); + // Wake up bodies when a body they're colliding with moves. + for (entity, mut time_sleeping) in &mut sleeping { + // Here we could use CollidingEntities, but it'd be empty if the ContactReportingPlugin was disabled. + let mut colliding_entities = collisions.collisions_with_entity(entity).map(|c| { + if entity == c.entity1 { + c.entity2 + } else { + c.entity1 + } + }); + if colliding_entities.any(|entity| moved_bodies.contains(entity)) { + commands.entity(entity).remove::(); time_sleeping.0 = 0.0; } } // Wake up bodies when a collision ends, for example when one of the bodies is despawned. - for CollisionEnded(entity1, entity2) in collision_ended_ev_reader.iter() { - if let Ok((_, _, mut time_sleeping)) = sleeping.get_mut(*entity1) { - commands.entity(*entity1).remove::(); + for contacts in collisions.get_internal().values() { + if contacts.during_current_frame || !contacts.during_previous_frame { + continue; + } + if let Ok((_, mut time_sleeping)) = sleeping.get_mut(contacts.entity1) { + commands.entity(contacts.entity1).remove::(); time_sleeping.0 = 0.0; } - if let Ok((_, _, mut time_sleeping)) = sleeping.get_mut(*entity2) { - commands.entity(*entity2).remove::(); + if let Ok((_, mut time_sleeping)) = sleeping.get_mut(contacts.entity2) { + commands.entity(contacts.entity2).remove::(); time_sleeping.0 = 0.0; } }