unity – Grid: Spreading temperature

First, I’d make this HeatData type into a struct so you don’t generate a tonne of garbage collection churn every time you do an iteration and spawn a fresh batch of them. This also means we no longer need a clean copy method – we get a copy just via assignment. x and y are implicit in their position in the grid, so we don’t really need to store them.

public struct HeatData
    public int radius;
    public double transferRate;
    public double dissipateRate;
    public double temperature;

Similarly, if you want to use double buffering to separate a read-only and write-only copy of your data, keep both around: that way you don’t incur allocation overhead or generate new garbage on every iteration by creating a new buffer.

public class grid {
    HeatData[,,] _grid;

    int _readIndex = 0;
    int _writeIndex { get { return 1 ^ _readIndex; } }

    bool IsInGridBounds(int x, int y) {
        return x >= 0 && y >= 0 && x < _grid.GetLength(1) && y < _grid.GetLength(2);

    public HeatData GetData(int x, int y) {
        if (IsInGridBounds(x, y))
            return _grid[_readIndex, x, y];
            return default;        

    public void SetData(int x, int y, HeatData data) {
        _grid[_writeIndex, x, y] = data;

    public void FlipBuffers() {
        _readIndex = _writeIndex;

Now we can clean up your loop a bit:

private void TransferHeat()
    for (int x = 0; x < _grid.gridData.GetLength(0); x++)
        for (int y = 0; y < _grid.gridData.GetLength(1); y++)
            // Implicitly copy since we made our type a struct.
            var data = _grid.GetData(x, y);

            var neighbours = _grid.GetNeighbours(data.position);

            foreach (var neighbour in neighbours)
                // Also implicitly copy since we made our type a struct.
                var neighbourData = _grid.GetData(neighbour.x, neighbour.y);
                var difference = HeatValueForNeighbour(neighbourData, data);

                data.temperature = data.temperature - difference;
                // We don't update the neighbour's data here - we'll visit them
                // individually at some point in our loop.

            // Write our new data to the write-only buffer.
            _grid.SetData(x, y, data);

    // Done writing. Make our write-only buffer the new read-only version.
    // We can do this by flipping one bit, without copying the whole array over.

Because we’re only updating “this” node in each iteration, not the neighbouring nodes, you’d also want to update your heat transfer function to include information about both nodes’ transfer rates – to ensure the transfer is symmetrical:

private double HeatValueForNeighbour(HeatData neighbourData, HeatData data)
    var temperatureDifference = data.temperature - neighbourData.temperature;
    // Depending on your conventions here, an average might be appropriate,
    // rather than a sum.
    return temperatureDifference * (data.transferRate + neighbourData.transferRate);

We do end up calling this function twice for each neighbouring pair A, B: once when A is “this” node and B is the neighbour, and a second time when B is “this” node and A is the neighbour. You can reduce this overhead by updating your GetNeighbours function to only enumerate “neighbours we haven’t visited yet” and go back to updating both “this” node and its neighbours in the same pass. But then you need to read “this” node from the writable buffer, to ensure you carry forward data we wrote earlier in the iteration, and be sure to seed your starting node with the readable data.

Another technique you can use to mitigate the directional bias, if you don’t want to use full double buffering, is to alternate your direction of iteration between subsequent frames. So in frame n you have a rightward bias, but in frame n+1 you have a leftward bias, and the two effects “mostly” cancel out, at half the memory cost.

Source link

More To Explore

Share on facebook
Share on twitter
Share on linkedin
Share on email