I see the final result here has a left bias, just press and hold to see it stacking towards left before right. So the next fix could be a random choice.
It does indeed! It's a great note. As you say, randomly choosing between left and right if they both exist would fix the bias.
Another issue is that it forms a perfect diagonal, which isn't the most pleasing to look at. It can be addressed by randomly allowing for stacks of two granules and allowing spreading further than just one position to the left or right.
I figured where I left it was appropriate for this post, but I'm happy to write follow-ups for other particle types and the kind of improvement you mentioned!
Choosing between left and right if they're both open fixes the bias when there is only a single pixel falling, but not when a circle is used. This is because the update function runs from right to left across the screen, so the falling pillar of sand behaves like a solid wall as the update has not had the chance to move the other side of the circle yet. The best example of this is dropping a pillar on the right slope of an existing cone, a solid line will form on the right side of the falling segment, but a slope will form against the left side of the pillar. When you try this with the left side of a cone, the behavior is correct.
One fix I found is relatively straightforward:
class FallingGrid2 extends FallingGrid {
// Draw from the end of the list to the beginning
update() {
+ const direction = Math.random() > 0.5 ? 'ltr' : 'rtl';
for (let i = this.grid.length - this.width - 1; i > 0; i--) {
- this.updatePixel(i);
+ if (direction === 'rtl') {
+ this.updatePixel(i);
+ } else {
+ const offset = i % this.width;
+ const flipped = this.width - offset;
+ this.updatePixel(i - offset + flipped);
+ }
}
}
}
Cheers for making the source so easily hackable :)
Interesting, I solved it differently, by just randomly selecting the order in which belowLeft and belowRight are checked, inside the updatePixel function:
updatePixel(i) {
const below = i + this.width;
const belowLeft = below - 1;
const belowRight = below + 1;
// flag to indicate if we checked the left side already
let checkedLeft = false;
if (this.isEmpty(below)) {
swap(i, below);
// check the left side first with 50 % prob (ltr)
} else if (Math.random() <= 0.5 && this.isEmpty(belowLeft)) {
swap(i, belowLeft);
// indicate we checked the left side already
checkedLeft = true;
// check the right side first with 50 % prob
} else if (this.isEmpty(belowRight)) {
swap(i, belowRight);
// if we haven't checked the left side earlier, check it now (rtl)
} else if (!checkedLeft && window.sandgrid[belowLeft] == 1)
swap(i, belowLeft);
}
I may be testing wrong, but I believe that approach causes dropping a steady stream of sand on the right side of a cone to produce very strange falling behaviour as compared to the left, because the stream is more than one pixel wide. Looks like you changed some other bits around so you may have addressed this some other way.
Ah! I see now! Yes, we should alternate the direction of the rowwise pass, or pick randomly, as you demonstrate. This would be a great step to start a next post with.
It's true! This has to do with how I wrote the "belowLeft" and "belowRight" variables. I don't ensure they are in the same row. You can do this by taking a pair of indices, dividing by width and flooring and checking for equality.
It's because the left pixel is checked before the right pixel (the settling behaviour section). This is common in games where you handle movement input (left, right arrow keys) - if you press both of them you'll always go one way, because that way is checked first for movement.
I see the final result here has a left bias, just press and hold to see it stacking towards left before right. So the next fix could be a random choice.