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