Creating A Falling Sand Simulator Part 1

Falling Sand Simulator - Chunking

Embarking on my journey to create a falling sand simulator was no small feat. It was a venture born out of a deep fascination with the mechanics of virtual worlds and a desire to challenge myself. Armed with a passion for C# game development and a curiosity about Monogame’s capabilities, I set out to bring my vision to life. This wasn’t just about coding or game design; it was a personal quest to translate a complex, dynamic system into a digital experience, learning and adapting with each obstacle encountered.

What is a Falling Sand Simulator?

A falling sand simulator is a digital experience that mimics the movement of particles as they gracefully descend, influenced by the pull of gravity. These tiny particles interact dynamically, following the rules of physics and sometimes even engage in chemical reactions, creating a symphony of effects. I recommend checking out an example. I found a programmer with the alias “R74n” has a great example: https://sandboxels.r74n.com/

The Spark of Inspiration

The motivation to develop a falling sand simulator often stems from a childhood fascination with these simulations. Many of us were captivated by the way particles interacted and created emergent gameplay, and that fascination only grew stronger with time. As an adult, I honed our skills in processing vast amounts of data efficiently, making the challenge even more intriguing.

In the context of this project, data is the lifeblood. Each particle in the simulation possesses unique properties and the potential for fascinating interactions with others. Our journey isn’t just about recreating these captivating phenomena; it’s also about doing so with efficiency and effectiveness.

Choosing My Tools: C# and Monogame

So, how do we bring our falling sand simulator to life? For this journey, I chosen to employ the versatile C# framework known as Monogame. Monogame offers us the freedom to dive right into coding without the constraints of a predefined game engine. However, it also means we’re building everything from the ground up, from animation sprites to state machines and input systems.

Join me on this thrilling adventure as I explore the inner workings of creating a falling sand simulator. Discover the beauty of simulating nature’s phenomena in a digital world and learn the secrets of making it all happen efficiently and effectively.

Getting Started

To set up the simulation world, I envision it as a grid of cells, where each cell can either be empty or occupied by a particle, which I’ll refer to as an “element” in this project. This world can be easily represented using a two-dimensional array of integers, where each integer indicates the type of element in a given cell. For performance optimization, I will treat this two-dimensional array as a one-dimensional array.

To convert between two-dimensional and one-dimensional coordinates, I can use the following code:

C#
int[] elements = new int[width * height];

/* To determine the X and Y coordinates from an element index 'i' */
int y = i / width;
int x = i % width;

/* To convert from X and Y coordinates to one dimension 'i' */
int i = y * width + x;

By default, all elements within the array are initialized with a value of -1, signifying empty space.

Our First Element – Sand

The first element I’m going to start with is Sand. Whenever I encounter a zero in the array, I will interpret it as representing sand. These simulations primarily follow cellular automata principles, where each element follows specific rules.

Sand Rules

Here are the rules Sand will follow.

Sand will fall one cell down if the cell below it is empty.

If the cell below is occupied, the sand will attempt to move right or left.

If there is no where to move, The Sand will stay put.

Running the Rules

To achieve this behavior, I loop through the element array and, whenever I encounter a value of 0, I apply the sand’s properties to that cell.

C#
for (int i = 0; i < elements.Length; i++)
{
    if (elements[i] == -1) continue;

    if (elements[i] == 0)
    {
        int y_current = i / width;
	int x_current = i % width;
	
	//check below
	if (IsEmpty(x_current,y_current + 1))
	{
	    int to = (y_current + 1) * width + x_current;
 	    Move(i, to);
	    return to;
	}

	//check right
	if (IsEmpty(x_current + 1, y_current + 1))
	{
	    int to = (y_current + 1) * system.width + (x_current + 1);
	    Move(i, to);
	}

	//check left
	if (IsEmpty(x_current - 1, y_current + 1, Position.LocalSpace))
	{
	    int to = (y_current + 1) * system.width + (x_current - 1);
	    Move(i, to);
	}
    }
}

We need to Avoid Reprocessing “Moved” Elements on the Same Update Tick

To ensure that I don’t process a piece of sand that has already moved within the current update tick, I’ve implemented a Boolean array of the same length as the element array. I will call the Boolean array “movedWithFrame”.

Everytime I move a element, I will set same area where the element is in “movedWithFrame” to true. Then I loop over all the elements, I’ll skip the ones where “movedWithFrame” is true.

This prevents reprocessing elements that have already moved during earlier in the update tick.

C#
for (int i = 0; i < movedWithFrame.Length; i++)
{
    movedWithFrame[i] = false;
}

for (int i = 0; i < elements.Length; i++)
{
    if (elements[i] == -1) continue;
    if (movedWithFrame[i]) continue;

    if (elements[i] == 0)
    {
        int y_current = i / width;
	int x_current = i % width;
	
	//check below
	if (IsEmpty(x_current,y_current + 1,Position.LocalSpace))
	{
	    int to = (y_current + 1) * width + x_current;
 	    Move(i, to);
	    movedWithFrame[to] = true;		
	}

	//check right
	if (IsEmpty(x_current + 1, y_current + 1, Position.LocalSpace))
	{
	    int to = (y_current + 1) * width + (x_current + 1);
 	    Move(i, to);
	    movedWithFrame[to] = true;	
	}

	//check left
	if (IsEmpty(x_current - 1, y_current + 1, Position.LocalSpace))
	{
	    int to = (y_current + 1) * width + (x_current - 1);
 	    Move(i, to);
	    movedWithFrame[to] = true;	
	}
    }
}

Look at it go!

Running this logic approximately 10 times per second results in a basic functioning simulation.

Performance Optimization

While the simulation works well at lower resolutions with fewer elements, I noticed that as I increased the resolution and added more elements, each update took up to 10 milliseconds to complete, which raised performance concerns.

To address this, I divided the world into smaller “chunks,” with each chunk responsible for a specific sector of the world. These chunks can be activated or deactivated as needed to conserve system resources. By utilizing the “movedWithFrame” boolean array, I already have a heatmap of which elements have moved during an update cycle, allowing me to selectively enable or disable chunks.

Although this approach didn’t initially boost performance during the simulation’s startup, it significantly improved efficiency once the simulation was running, reducing each update cycle to approximately 1 millisecond.

Red Squares are active chunks

Thank you for joining me on this journey into creating a Falling Sand Simulator. I’m excited to share that there will be a Part 2 to this project, where I’ll delve even deeper into the development process and explore additional features and optimizations. Stay tuned for more updates and insights as I continue to build and enhance our Falling Sand Simulator.