1 #pragma once
2 
3 /// \file Collision.h
4 /// \brief Collision handling
5 /// \author Pavel Sevecek (sevecek at sirrah.troja.mff.cuni.cz)
6 /// \date 2016-2021
7 
8 #include "objects/containers/FlatSet.h"
9 #include "physics/Functions.h"
10 #include "quantities/Storage.h"
11 #include "system/Settings.h"
12 
13 NAMESPACE_SPH_BEGIN
14 
15 enum class CollisionResult {
16     NONE,          ///< No collision took place
17     BOUNCE,        ///< Bounce/scatter collision, no merging and no fragmentation
18     FRAGMENTATION, ///< Target was disrupted, creating largest remnant and fragments
19     MERGER,        ///< Particles merged together
20     EVAPORATION,   ///< No asteroids survived the collision
21 };
22 
23 /// \brief Abstraction of collision outcome.
24 ///
25 /// Collision can arbitrarily change the number of particles in the storage. It can one or both colliding
26 /// particles ("merger" or "evaporation"), or it can even add more particles into the storage
27 /// ("fragmentation"). It is necessary to update all pointers and array views if collision handler returns
28 /// true, or keep pointers to the arrays (at a cost of double indirection).
29 class ICollisionHandler : public Polymorphic {
30 public:
31     virtual void initialize(Storage& storage) = 0;
32 
33     /// \brief Computes the outcome of collision between i-th and j-th particle
34     ///
35     /// It is guaranteed that this function is called after \ref initialize has been called (at least once)
36     /// and that the storage object passed to \ref initialize is valid, so it is allowed (and recommended) to
37     /// storage a pointer to the storage.
38     /// \param i,j Indices of particles in the storage.
39     /// \param toRemove Indices of particles to be removed from the storage. May already contain some indices,
40     ///                 collision handler should only add new indices and it shall not clear the storage.
41     /// \return True if the collision took place, false to reject the collision.
42     ///
43     /// \todo Needs to be generatelized for fragmentation handlers. Currently the function CANNOT change the
44     /// number of particles as it would invalidate arrayviews and we would lost the track of i-th and j-th
45     /// particle (which we need for decreasing movement time).
46     virtual CollisionResult collide(const Size i, const Size j, FlatSet<Size>& toRemove) = 0;
47 };
48 
49 /// \brief Handles overlaps of particles.
50 ///
51 /// Interface similar to \ref ICollisionHandler, but unlike collision result, overlaps has no result -
52 /// particles either overlap or not. Note that overlaps are processed before collisions and if two particles
53 /// do not overlap (\ref overlaps returns false), the check for collisions is skipped.
54 class IOverlapHandler : public Polymorphic {
55 public:
56     virtual void initialize(Storage& storage) = 0;
57 
58     /// \brief Returns true if two particles overlaps
59     ///
60     /// If so, the overlap is then resolved using \ref handle.
61     /// \param i,j Indices of particles in the storage.
62     virtual bool overlaps(const Size i, const Size j) const = 0;
63 
64     /// \brief Handles the overlap of two particles.
65     ///
66     /// When called, the particles must actually overlap (\ref overlaps must return true). This is checked by
67     /// assert.
68     virtual void handle(const Size i, const Size j, FlatSet<Size>& toRemove) = 0;
69 };
70 
71 /// \brief Helper function sorting two values
72 template <typename T>
minMax(const T & t1,const T & t2)73 Tuple<T, T> minMax(const T& t1, const T& t2) {
74     if (t1 < t2) {
75         return { t1, t2 };
76     } else {
77         return { t2, t1 };
78     }
79 }
80 
81 template <typename T>
weightedAverage(const T & v1,const Float w1,const T & v2,const Float w2)82 INLINE T weightedAverage(const T& v1, const Float w1, const T& v2, const Float w2) {
83     SPH_ASSERT(w1 + w2 > 0._f, w1, w2);
84     return (v1 * w1 + v2 * w2) / (w1 + w2);
85 }
86 
areParticlesBound(const Float m_sum,const Float h_sum,const Vector & dv,const Float limit)87 INLINE bool areParticlesBound(const Float m_sum, const Float h_sum, const Vector& dv, const Float limit) {
88     const Float vEscSqr = 2._f * Constants::gravity * m_sum / h_sum;
89     const Float vRelSqr = getSqrLength(dv);
90     return vRelSqr * limit < vEscSqr;
91 }
92 
93 // ----------------------------------------------------------------------------------------------------------
94 // Collision Handlers
95 // ----------------------------------------------------------------------------------------------------------
96 
97 /// \brief Helper handler always returning CollisionResult::NONE.
98 ///
99 /// Cannot be used directly as collision always have to return a valid result (not NONE), but it can be used
100 /// for testing purposes or in composite handlers (such as \ref FallbackHandler).
101 class NullCollisionHandler : public ICollisionHandler {
102 public:
initialize(Storage & UNUSED (storage))103     virtual void initialize(Storage& UNUSED(storage)) override {}
104 
collide(const Size UNUSED (i),const Size UNUSED (j),FlatSet<Size> & UNUSED (toRemove))105     virtual CollisionResult collide(const Size UNUSED(i),
106         const Size UNUSED(j),
107         FlatSet<Size>& UNUSED(toRemove)) override {
108         return CollisionResult::NONE;
109     }
110 };
111 
112 /// \brief Handler merging particles into a single, larger particles.
113 ///
114 /// The volume of the merger is the sum of volumes of colliders. Particles are only merged if the relative
115 /// velocity of collision is lower than the escape velocity and if the angular frequency of the would-be
116 /// merger is lower than the break-up frequency; if not, CollisionResult::NONE is returned.
117 class MergingCollisionHandler : public ICollisionHandler {
118 private:
119     ArrayView<Vector> r, v;
120     ArrayView<Float> m;
121     ArrayView<Vector> L;
122     ArrayView<Vector> omega;
123     ArrayView<SymmetricTensor> I;
124     ArrayView<Tensor> E;
125 
126     /// \todo filling factor, collapse of particles in contact must result in sphere of same volume!
127 
128     Float bounceLimit;
129     Float rotationLimit;
130 
131 public:
MergingCollisionHandler(const RunSettings & settings)132     explicit MergingCollisionHandler(const RunSettings& settings) {
133         bounceLimit = settings.get<Float>(RunSettingsId::COLLISION_BOUNCE_MERGE_LIMIT);
134         rotationLimit = settings.get<Float>(RunSettingsId::COLLISION_ROTATION_MERGE_LIMIT);
135     }
136 
MergingCollisionHandler(const Float bounceLimit,const Float rotationLimit)137     explicit MergingCollisionHandler(const Float bounceLimit, const Float rotationLimit)
138         : bounceLimit(bounceLimit)
139         , rotationLimit(rotationLimit) {}
140 
initialize(Storage & storage)141     virtual void initialize(Storage& storage) override {
142         ArrayView<Vector> dv;
143         tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
144         m = storage.getValue<Float>(QuantityId::MASS);
145         omega = storage.getValue<Vector>(QuantityId::ANGULAR_FREQUENCY);
146 
147         if (storage.has(QuantityId::MOMENT_OF_INERTIA)) {
148             I = storage.getValue<SymmetricTensor>(QuantityId::MOMENT_OF_INERTIA);
149             E = storage.getValue<Tensor>(QuantityId::LOCAL_FRAME);
150             L = storage.getValue<Vector>(QuantityId::ANGULAR_MOMENTUM);
151         }
152     }
153 
collide(const Size i,const Size j,FlatSet<Size> & toRemove)154     virtual CollisionResult collide(const Size i, const Size j, FlatSet<Size>& toRemove) override {
155         // set radius of the merger so that the volume is conserved
156         const Float h_merger = root<3>(pow<3>(r[i][H]) + pow<3>(r[j][H]));
157 
158         // conserve total mass
159         const Float m_merger = m[i] + m[j];
160 
161         // merge so that the center of mass remains unchanged
162         const Vector r_merger = weightedAverage(r[i], m[i], r[j], m[j]);
163 
164         // converve momentum
165         const Vector v_merger = weightedAverage(v[i], m[i], v[j], m[j]);
166 
167         Vector omega_merger;
168         SymmetricTensor I_merger;
169         Vector L_merger;
170         Tensor E_merger = Tensor::identity();
171 
172         // Never modify particle values below UNTIL we know the collision is accepted; save the preliminary
173         // values to _merger variables!
174 
175         if (I) {
176             // So far this is just an experimental branch, not intended to be used for "serious" simulations.
177             // This assert is just a check that it does not get enabled by accident.
178             SPH_ASSERT(Assert::isTest);
179 
180             // compute inertia tensors in inertial frame
181             const SymmetricTensor I1 = transform(I[i], convert<AffineMatrix>(E[i]));
182             const SymmetricTensor I2 = transform(I[j], convert<AffineMatrix>(E[j]));
183 
184             // sum up the inertia tensors, but first move them to new origin
185             I_merger = Rigid::parallelAxisTheorem(I1, m[i], r_merger - r[i]) +
186                        Rigid::parallelAxisTheorem(I2, m[j], r_merger - r[j]);
187 
188             // compute the total angular momentum - has to be conserved
189             L_merger = m[i] * cross(r[i] - r_merger, v[i] - v_merger) + //
190                        m[j] * cross(r[j] - r_merger, v[j] - v_merger) + //
191                        L[i] + L[j];
192             // L = I*omega  =>  omega = I^-1 * L
193             omega_merger = I_merger.inverse() * L_merger;
194 
195             // compute the new local frame of the merger and inertia tensor in this frame
196             Eigen eigen = eigenDecomposition(I_merger);
197             I_merger = SymmetricTensor(eigen.values, Vector(0._f));
198             SPH_ASSERT(isReal(I_merger));
199 
200             E_merger = convert<Tensor>(eigen.vectors);
201             SPH_ASSERT(isReal(E_merger));
202 
203             SPH_ASSERT(isReal(L_merger) && isReal(omega_merger), L_merger, omega_merger);
204             /// \todo remove, we have unit tests for this
205             SPH_ASSERT(almostEqual(getSqrLength(E_merger.row(0)), 1._f, 1.e-6_f));
206             SPH_ASSERT(almostEqual(getSqrLength(E_merger.row(1)), 1._f, 1.e-6_f));
207             SPH_ASSERT(almostEqual(getSqrLength(E_merger.row(2)), 1._f, 1.e-6_f));
208 
209         } else {
210             L_merger = m[i] * cross(r[i] - r_merger, v[i] - v_merger) + //
211                        m[j] * cross(r[j] - r_merger, v[j] - v_merger) + //
212                        Rigid::sphereInertia(m[i], r[i][H]) * omega[i] + //
213                        Rigid::sphereInertia(m[j], r[j][H]) * omega[j];
214             omega_merger = Rigid::sphereInertia(m_merger, h_merger).inverse() * L_merger;
215         }
216 
217         if (!this->acceptMerge(i, j, h_merger, omega_merger)) {
218             return CollisionResult::NONE;
219         }
220 
221         // NOW we can save the values
222 
223         m[i] = m_merger;
224         r[i] = r_merger;
225         r[i][H] = h_merger;
226         v[i] = v_merger;
227         v[i][H] = 0._f;
228         omega[i] = omega_merger;
229 
230         if (I) {
231             I[i] = I_merger;
232             L[i] = L_merger;
233             E[i] = E_merger;
234         }
235 
236         SPH_ASSERT(isReal(v[i]) && isReal(r[i]));
237         toRemove.insert(j);
238         return CollisionResult::MERGER;
239     }
240 
241 private:
242     /// \brief Checks if the particles should be merged
243     ///
244     /// We merge particles if their relative velocity is lower than the escape velocity AND if the angular
245     /// velocity of the merger is lower than the breakup limit.
246     /// \param i, j Particle indices
247     /// \param h Radius of the merger
248     /// \param omega_merge Angular velocity of the merger
acceptMerge(const Size i,const Size j,const Float h,const Vector & omega_merge)249     INLINE bool acceptMerge(const Size i, const Size j, const Float h, const Vector& omega_merge) const {
250         if (!areParticlesBound(m[i] + m[j], r[i][H] + r[j][H], v[i] - v[j], bounceLimit)) {
251             // moving too fast, reject the merge
252             /// \todo shouldn't we check velocities AFTER the bounce?
253             return false;
254         }
255         const Float omegaCritSqr = Constants::gravity * (m[i] + m[j]) / pow<3>(h);
256         const Float omegaSqr = getSqrLength(omega_merge);
257         if (omegaSqr * rotationLimit > omegaCritSqr) {
258             // rotates too fast, reject the merge
259             return false;
260         }
261         return true;
262     }
263 };
264 
265 /// \brief Handler for bounce on collision.
266 ///
267 /// No merging takes place. Particles lose fraction of momentum, given by coefficients of restitution.
268 /// \todo if restitution.t < 1, we should spin up the particles to conserve the angular momentum!!
269 class ElasticBounceHandler : public ICollisionHandler {
270 protected:
271     ArrayView<Vector> r, v;
272     ArrayView<Float> m;
273 
274     /// Coefficients of restitution
275     struct {
276         /// Normal;
277         Float n;
278 
279         /// Tangential
280         Float t;
281 
282     } restitution;
283 
284 public:
ElasticBounceHandler(const RunSettings & settings)285     explicit ElasticBounceHandler(const RunSettings& settings) {
286         restitution.n = settings.get<Float>(RunSettingsId::COLLISION_RESTITUTION_NORMAL);
287         restitution.t = settings.get<Float>(RunSettingsId::COLLISION_RESTITUTION_TANGENT);
288     }
289 
ElasticBounceHandler(const Float n,const Float t)290     ElasticBounceHandler(const Float n, const Float t) {
291         restitution.n = n;
292         restitution.t = t;
293     }
294 
initialize(Storage & storage)295     virtual void initialize(Storage& storage) override {
296         ArrayView<Vector> dv;
297         tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
298         m = storage.getValue<Float>(QuantityId::MASS);
299     }
300 
collide(const Size i,const Size j,FlatSet<Size> & UNUSED (toRemove))301     virtual CollisionResult collide(const Size i, const Size j, FlatSet<Size>& UNUSED(toRemove)) override {
302         const Vector dr = getNormalized(r[i] - r[j]);
303         const Vector v_com = weightedAverage(v[i], m[i], v[j], m[j]);
304         v[i] = this->reflect(v[i], v_com, -dr);
305         v[j] = this->reflect(v[j], v_com, dr);
306 
307         // no change of radius
308         v[i][H] = 0._f;
309         v[j][H] = 0._f;
310 
311         SPH_ASSERT(isReal(v[i]) && isReal(v[j]));
312         return CollisionResult::BOUNCE;
313     }
314 
315 private:
reflect(const Vector & v_particle,const Vector & v_com,const Vector & dir)316     INLINE Vector reflect(const Vector& v_particle, const Vector& v_com, const Vector& dir) {
317         SPH_ASSERT(almostEqual(getSqrLength(dir), 1._f), dir);
318         const Vector v_rel = v_particle - v_com;
319         const Float proj = dot(v_rel, dir);
320         const Vector v_t = v_rel - proj * dir;
321         const Vector v_n = proj * dir;
322 
323         // flip the orientation of normal component (bounce) and apply coefficients of restitution
324         return restitution.t * v_t - restitution.n * v_n + v_com;
325     }
326 };
327 
328 /// \brief Composite handler, choosing another collision handler if the primary handler rejects the
329 /// collision by returning \ref CollisionResult::NONE.
330 template <typename TPrimary, typename TFallback>
331 class FallbackHandler : public ICollisionHandler {
332 private:
333     TPrimary primary;
334     TFallback fallback;
335 
336 public:
FallbackHandler(const RunSettings & settings)337     FallbackHandler(const RunSettings& settings)
338         : primary(settings)
339         , fallback(settings) {}
340 
initialize(Storage & storage)341     virtual void initialize(Storage& storage) override {
342         primary.initialize(storage);
343         fallback.initialize(storage);
344     }
345 
collide(const Size i,const Size j,FlatSet<Size> & toRemove)346     virtual CollisionResult collide(const Size i, const Size j, FlatSet<Size>& toRemove) override {
347         CollisionResult result = primary.collide(i, j, toRemove);
348         if (result == CollisionResult::NONE) {
349             return fallback.collide(i, j, toRemove);
350         } else {
351             return result;
352         }
353     }
354 };
355 
356 class FragmentationHandler : public ICollisionHandler {
357 public:
358     // ParametricRelations, directionality of fragments
359 
collide(const Size i,const Size j,FlatSet<Size> & UNUSED (toRemove))360     virtual CollisionResult collide(const Size i, const Size j, FlatSet<Size>& UNUSED(toRemove)) override {
361         (void)i;
362         (void)j;
363         /// \todo
364         NOT_IMPLEMENTED;
365         return CollisionResult::FRAGMENTATION;
366     }
367 };
368 
369 
370 // ----------------------------------------------------------------------------------------------------------
371 // Overlap Handlers
372 // ----------------------------------------------------------------------------------------------------------
373 
374 /// \brief Handler simply ignoring overlaps.
375 class NullOverlapHandler : public IOverlapHandler {
376 public:
initialize(Storage & UNUSED (storage))377     virtual void initialize(Storage& UNUSED(storage)) override {}
378 
overlaps(const Size UNUSED (i),const Size UNUSED (j))379     virtual bool overlaps(const Size UNUSED(i), const Size UNUSED(j)) const override {
380         return false;
381     }
382 
handle(const Size UNUSED (i),const Size UNUSED (j),FlatSet<Size> & UNUSED (toRemove))383     virtual void handle(const Size UNUSED(i),
384         const Size UNUSED(j),
385         FlatSet<Size>& UNUSED(toRemove)) override {}
386 };
387 
388 /// \brief Handler unconditionally merging the overlapping particles.
389 ///
390 /// Behaves similarly to collision handler \ref MergingCollisionHandler, but there is no check for relative
391 /// velocities of particles nor angular frequency of the merger - particles are always merged.
392 class MergeOverlapHandler : public IOverlapHandler {
393 private:
394     MergingCollisionHandler handler;
395 
396 public:
MergeOverlapHandler()397     MergeOverlapHandler()
398         : handler(0._f, 0._f) {}
399 
initialize(Storage & storage)400     virtual void initialize(Storage& storage) override {
401         handler.initialize(storage);
402     }
403 
overlaps(const Size UNUSED (i),const Size UNUSED (j))404     virtual bool overlaps(const Size UNUSED(i), const Size UNUSED(j)) const override {
405         return true;
406     }
407 
handle(const Size i,const Size j,FlatSet<Size> & toRemove)408     virtual void handle(const Size i, const Size j, FlatSet<Size>& toRemove) override {
409         handler.collide(i, j, toRemove);
410     }
411 };
412 
413 /// \brief Handler displacing the overlapping particles so that they just touch.
414 ///
415 /// Particles are moved alongside their relative position vector. They must not overlap perfectly, as in such
416 /// a case the displacement direction is undefined. The handler is templated, allowing to specify a followup
417 /// handler that is applied after the particles have been moved. This can be another \ref IOverlapHandler, but
418 /// also a \ref ICollisionHandler, as the two interfaces are similar.
419 template <typename TFollowupHandler>
420 class RepelHandler : public IOverlapHandler {
421 private:
422     TFollowupHandler handler;
423 
424     ArrayView<Vector> r, v;
425     ArrayView<Float> m;
426 
427 public:
RepelHandler(const RunSettings & settings)428     explicit RepelHandler(const RunSettings& settings)
429         : handler(settings) {}
430 
initialize(Storage & storage)431     virtual void initialize(Storage& storage) override {
432         ArrayView<Vector> dv;
433         tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
434         m = storage.getValue<Float>(QuantityId::MASS);
435 
436         handler.initialize(storage);
437     }
438 
overlaps(const Size UNUSED (i),const Size UNUSED (j))439     virtual bool overlaps(const Size UNUSED(i), const Size UNUSED(j)) const override {
440         // this function is called only if the spheres intersect, which is the only condition for this handler
441         return true;
442     }
443 
handle(const Size i,const Size j,FlatSet<Size> & toRemove)444     virtual void handle(const Size i, const Size j, FlatSet<Size>& toRemove) override {
445         Vector dir;
446         Float dist;
447         tieToTuple(dir, dist) = getNormalizedWithLength(r[i] - r[j]);
448         dir[H] = 0._f; // don't mess up radii
449         // can be only used for overlapping particles
450         SPH_ASSERT(dist < r[i][H] + r[j][H], dist, r[i][H] + r[i][H]);
451         const Float x1 = (r[i][H] + r[j][H] - dist) / (1._f + m[i] / m[j]);
452         const Float x2 = m[i] / m[j] * x1;
453         r[i] += dir * x1;
454         r[j] -= dir * x2;
455         SPH_ASSERT(almostEqual(getSqrLength(r[i] - r[j]), sqr(r[i][H] + r[j][H])),
456             getSqrLength(r[i] - r[j]),
457             sqr(r[i][H] + r[j][H]));
458 
459         SPH_ASSERT(isReal(v[i]) && isReal(v[j]));
460         SPH_ASSERT(isReal(r[i]) && isReal(r[j]));
461 
462         // Now when the two particles are touching, handle the collision using the followup handler.
463         handler.collide(i, j, toRemove);
464     }
465 };
466 
467 /// \brief Overlap handler performing a bounce of particles.
468 ///
469 /// Particles only bounce if their centers are moving towards each other. This way we prevent particles
470 /// bouncing infinitely - after the first bounce, the particle move away from each other and they are not
471 /// classified as overlaps by the handler.
472 class InternalBounceHandler : public IOverlapHandler {
473 private:
474     ElasticBounceHandler handler;
475 
476     ArrayView<Vector> r, v;
477 
478 public:
InternalBounceHandler(const RunSettings & settings)479     explicit InternalBounceHandler(const RunSettings& settings)
480         : handler(settings) {
481         // this handler allows overlaps of particles, so it should never be used with point particles, as we
482         // could potentially get infinite accelerations
483         SPH_ASSERT(settings.get<GravityKernelEnum>(RunSettingsId::GRAVITY_KERNEL) !=
484                    GravityKernelEnum::POINT_PARTICLES);
485     }
486 
initialize(Storage & storage)487     virtual void initialize(Storage& storage) override {
488         ArrayView<Vector> dv;
489         tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
490 
491         handler.initialize(storage);
492     }
493 
overlaps(const Size i,const Size j)494     virtual bool overlaps(const Size i, const Size j) const override {
495         // overlap needs to be handled only if the particles are moving towards each other
496         const Vector dr = r[i] - r[j];
497         const Vector dv = v[i] - v[j];
498         return dot(dr, dv) < 0._f;
499     }
500 
handle(const Size i,const Size j,FlatSet<Size> & toRemove)501     virtual void handle(const Size i, const Size j, FlatSet<Size>& toRemove) override {
502         handler.collide(i, j, toRemove);
503     }
504 };
505 
506 /// \brief Handler merging overlapping particles if their relative velocity is lower than the escape velocity.
507 ///
508 /// If the relative veloity is too high, the particles are allowed to simply pass each other - this situation
509 /// is not classified as overlap.
510 class MergeBoundHandler : public IOverlapHandler {
511 private:
512     MergingCollisionHandler handler;
513 
514 public:
515     ArrayView<Vector> r, v, omega;
516     ArrayView<Float> m;
517 
518     Float bounceLimit;
519     Float rotationLimit;
520 
521 public:
MergeBoundHandler(const RunSettings & settings)522     explicit MergeBoundHandler(const RunSettings& settings)
523         : handler(settings) {
524         bounceLimit = settings.get<Float>(RunSettingsId::COLLISION_BOUNCE_MERGE_LIMIT);
525         rotationLimit = settings.get<Float>(RunSettingsId::COLLISION_ROTATION_MERGE_LIMIT);
526     }
527 
initialize(Storage & storage)528     virtual void initialize(Storage& storage) override {
529         ArrayView<Vector> dv;
530         tie(r, v, dv) = storage.getAll<Vector>(QuantityId::POSITION);
531         omega = storage.getValue<Vector>(QuantityId::ANGULAR_FREQUENCY);
532         m = storage.getValue<Float>(QuantityId::MASS);
533 
534         handler.initialize(storage);
535     }
536 
overlaps(const Size i,const Size j)537     virtual bool overlaps(const Size i, const Size j) const override {
538         const Float m_merger = m[i] + m[j];
539         if (!areParticlesBound(m_merger, r[i][H] + r[j][H], v[i] - v[j], bounceLimit)) {
540             // moving too fast, reject the merge
541             /// \todo shouldn't we check velocities AFTER the bounce?
542             return false;
543         }
544 
545         const Float h_merger = root<3>(pow<3>(r[i][H]) + pow<3>(r[j][H]));
546         const Float omegaCritSqr = Constants::gravity * (m[i] + m[j]) / pow<3>(h_merger);
547 
548         const Vector r_merger = weightedAverage(r[i], m[i], r[j], m[j]);
549         const Vector v_merger = weightedAverage(v[i], m[i], v[j], m[j]);
550         const Vector L_merger = m[i] * cross(r[i] - r_merger, v[i] - v_merger) + //
551                                 m[j] * cross(r[j] - r_merger, v[j] - v_merger) + //
552                                 Rigid::sphereInertia(m[i], r[i][H]) * omega[i] + //
553                                 Rigid::sphereInertia(m[j], r[j][H]) * omega[j];
554         const Vector omega_merger = Rigid::sphereInertia(m_merger, h_merger).inverse() * L_merger;
555         const Float omegaSqr = getSqrLength(omega_merger);
556         if (omegaSqr * rotationLimit > omegaCritSqr) {
557             // rotates too fast, reject the merge
558             return false;
559         }
560 
561         return true;
562     }
563 
handle(const Size i,const Size j,FlatSet<Size> & toRemove)564     virtual void handle(const Size i, const Size j, FlatSet<Size>& toRemove) override {
565         handler.collide(i, j, toRemove);
566     }
567 };
568 
569 NAMESPACE_SPH_END
570