Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I produce a pseudorandom pattern from X/Y coordinates deterministically?

I'm writing a shader which occasionally makes a point sparkle on a 2D map. (The "sparkle" is simply a brighter-colored pixel.) I'd like the sparkled blocks to appear randomly and uniformly distributed on the (infinite) plane, but I want the sparkling to be deterministic based on X and Y coordinates. I tried creating seed from the coordinates and creating a Java Random from that seed, but my attempts have so far resulted in recognizable patterns. This function will be called frequently (many millions of times) so performance is critical.

I first tried to mimic my hashCode() implementation, which uses a prime number multiplier to avoid collisions. This resulted in a visible gash across the map where a series of points shared the same seed.

I then tried to create a seed by concatenating the coordinates like so:

long seed = ((long) x << 32) | (long) y;
Random rand = new Random(seed);

This seems to result in patterned data as well, though the pattern isn't as obvious. The selected coordinates appear in lines, not evenly distributed at all.

I've avoided using MD5 or other cryptographic hashing algorithms because I'm afraid of the performance impact.

like image 579
Henry Merriam Avatar asked Jan 13 '12 02:01

Henry Merriam


2 Answers

The following is a very efficient function for mixing bits in a pseudo-random but deterministic fashion:

public static final long xorShift64(long a) {
    a ^= (a << 21);
    a ^= (a >>> 35);
    a ^= (a << 4);
    return a;
}

So if you want a pseudo-random long result from x and y co-ordinates you could do something like:

    long mix = xorShift64(x) + Long.rotateLeft(xorShift64(y),32) + 0xCAFEBABE;
    long result = xorShift64(mix);

I've used this approach successfully in graphics before, gives pretty good results! The quality of random numbers is about as good as java.util.Random but it is much faster....

like image 142
mikera Avatar answered Oct 19 '22 18:10

mikera


The linear congruential generator implemented in java.util.Random has the advantage of being repeatable for any chosen SEED. Given these declarations,

private static final int SEED = 42;
private static final int N = 128;
private static final int MAX_X = 1024;
private static final int MAX_Y = 1024;
private final Random rnd = new Random(SEED);
private final List<SparklePoint> list = new ArrayList<SparklePoint>(N);

You can initialize a (repeatable) list of N randomly chosen points in the rectangle (0, 0, MAX_X, MAX_Y) as follows:

public void init(int seed) {
    for (int i = 0; i < N; i++) {
        int x = rnd.nextInt(MAX_X);
        int y = rnd.nextInt(MAX_Y);
        list.add(new SparklePoint(x, y));
    }
}

It may be convenient to give each point a Timer whose period is chosen from the same sequence:

private class SparklePoint implements ActionListener {

    private static final int MAX_DELAY = 1000;
    private final Point p;
    private final Timer t;
    private boolean bright;

    public SparklePoint(int x, int y) {
        p = new Point(x, y);
        t = new Timer(rnd.nextInt(MAX_DELAY), this);
        t.setRepeats(false);
        t.start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        t.stop();
        if (bright) {
            // darken p
        } else {
            // brighten p
        }
        bright = !bright;
        t.setDelay(rnd.nextInt(MAX_DELAY));
        t.start();
    }
}
like image 2
trashgod Avatar answered Oct 19 '22 17:10

trashgod