Granular Pool Sleep

raisim::GranularPoolSleepController is an opt-in approximation for a settled granular pool that another object drops into or moves through. It temporarily fixes quiet grains that are outside a wake sphere around each incoming object, so only the grains near the disturbance are simulated. The default simulation does not use it.

Include <raisim/object/granular/GranularPoolSleepController.hpp>. The controller is header-only and works on one Granular Media system at a time.

Usage

Settle the pool first, then call arm(). Call update() before every world step with one wake sphere per moving object, and call release() before changing the particle count or when sleeping is no longer wanted.

#include <array>
#include <raisim/object/granular/GranularPoolSleepController.hpp>

raisim::GranularPoolSleepController sleep;
sleep.arm(*grains);
for (int step = 0; step < steps; ++step) {
  raisim::Vec<3> center;
  intruder->getPosition(0, center);
  const std::array sources{
      raisim::GranularPoolSleepController::WakeSphere{center, intruderBoundingRadius}};
  sleep.update(*grains, sources);
  world.integrate();
}
sleep.release(*grains);

arm() puts every grain that is currently quiet to sleep. update() wakes every sleeping grain whose center is within WakeSphere::radius + halo of a wake sphere, then puts other grains to sleep according to the policy. release() wakes all sleeping grains and detaches the controller. sleepingCount() returns the number of grains asleep, and wakeTransitions() counts sleep-to-awake transitions since the last arm().

A sleeping grain is a fixed grain. It loses its velocity when it is frozen. Waking it restores dynamic status, not its previous velocity or contact history. Grains that were already fixed before arm() are never changed.

Options

Pass GranularPoolSleepController::Options to the constructor. Every distance and threshold must be finite and nonnegative; otherwise the constructor throws std::invalid_argument.

Field

Default

Meaning

policy

QUIET

When awake grains go back to sleep (see below).

halo

0.35 m

Distance added to each wake sphere’s radius.

speedThreshold

0.02 m/s

A grain is quiet only when its linear speed is below this value.

angularSpeedThreshold

2.0 rad/s

A grain is quiet only when its angular speed is below this value.

quietSteps

20

Consecutive quiet updates before a grain sleeps under QUIET.

extraWakeDistance

0.08 m

Extra wake reach under WAKE_ONCE.

keepAboveZ

unset

When set, grains whose center is at or above this height stay awake.

The policies are:

  • RADIUS: every grain outside the wake reach sleeps immediately.

  • QUIET: a grain outside the wake reach sleeps after quietSteps consecutive quiet updates.

  • WAKE_ONCE: a woken grain never goes back to sleep. The wake reach is extended by extraWakeDistance.

raisim::GranularPoolSleepController::Options options;
options.policy = raisim::GranularPoolSleepController::Policy::QUIET;
options.halo = 0.35;
options.keepAboveZ = poolTopZ - 4.0 * grainRadius;  // keep the top two layers awake
raisim::GranularPoolSleepController sleep(options);

Rules

  • The wake sphere radius must contain the entire object at every orientation.

  • When a fast object can reach a grain before the next update(), add the largest expected per-step motion to the halo.

  • Include every externally driven object and every other disturbance as a wake source. Grains are woken only by wake spheres and keepAboveZ.

  • Do not change the pool’s particle count while the controller is armed. update() and release() throw std::logic_error when the count changed. Call release() first.

  • Do not save a checkpoint while the controller is armed; the checkpoint would record sleeping grains as fixed grains.

  • arm() throws std::logic_error when the controller is already armed.

Accuracy

No policy reproduces a fully dynamic pool exactly. Frozen grains do not move internally and do not transmit forces, so the trajectories of the grains and of the incoming object drift from a fully dynamic simulation. A smaller halo puts more grains to sleep, which is faster but less accurate. WAKE_ONCE can reduce the grain error compared with QUIET but saves less time, because grains stay awake after the object passes. Keeping the top layers awake with keepAboveZ can improve the incoming object’s path for some shapes, at the cost of more awake grains.

Choose the halo and policy from the error your scene can accept, and leave sleeping disabled when exact replay matters.

API Reference

class GranularPoolSleepController

Opt-in approximation for a settled pool. Sleeping grains act as fixed grains. Call update before each world step, then release before changing the pool’s size.

Public Types

enum class Policy

When awake grains go to sleep. In every policy, grains within a wake sphere’s reach (or above Options::keepAboveZ) are woken. RADIUS: every other grain sleeps immediately. QUIET: every other grain sleeps after Options::quietSteps consecutive quiet updates (below both speed thresholds). WAKE_ONCE: grains never go back to sleep after arm(); the wake reach is extended by Options::extraWakeDistance.

Values:

enumerator RADIUS
enumerator QUIET
enumerator WAKE_ONCE

Public Functions

inline GranularPoolSleepController()

Controller with default options.

inline explicit GranularPoolSleepController(Options options)
Parameters:

options – [in] controller options. Throws std::invalid_argument for negative or non-finite values.

inline void arm(GranularSystem &grains)

Attach to a pool and put its currently quiet grains to sleep (grains that were already fixed are never touched). Throws std::logic_error if already armed.

Parameters:

grains – [inout] the granular system

inline void update(GranularSystem &grains, std::span<const WakeSphere> wakeSources)

Wake grains near the wake sources and put grains to sleep according to the policy. Call before each world step. Throws std::logic_error if not armed with grains or if the particle count changed.

Parameters:
  • grains – [inout] the armed granular system

  • wakeSources – [in] regions that wake grains

inline void release(GranularSystem &grains)

Wake all sleeping grains and detach. Call before adding or removing particles. Throws std::logic_error if not armed with grains or if the particle count changed.

Parameters:

grains – [inout] the armed granular system

inline size_t sleepingCount() const
Returns:

the number of grains currently asleep.

inline size_t wakeTransitions() const
Returns:

the number of sleep-to-awake transitions since the last arm().

struct WakeSphere

A region that wakes grains, typically bounding an object entering the pool.

Public Members

Vec<3> center

Center in the world frame (m).

double radius

Bounding radius of the incoming object (m), finite and nonnegative.

struct Options

Controller parameters. All distances and thresholds must be finite and nonnegative.

Public Members

Policy policy

Sleep policy.

double halo

Distance added to WakeSphere::radius when testing whether a grain center is within reach (m).

double speedThreshold

Linear speed below which a grain counts as quiet (m/s); it must also be below angularSpeedThreshold.

double angularSpeedThreshold

Angular speed below which a grain counts as quiet (rad/s).

unsigned quietSteps

Consecutive quiet updates before a grain sleeps (QUIET policy).

double extraWakeDistance

Extra wake reach for the WAKE_ONCE policy (m).

std::optional<double> keepAboveZ

If set, grains whose center z is at or above this value (m) are kept awake.