Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wave generation with the "Hugo Elias" algorithm please! Java

I appear to have hit a wall in my most recent project involving wave/ripple generation over an image. I made one that works with basic colors on a grid that works perfectly; heck, I even added shades to the colors depending on the height of the wave.

However, my overall goal was to make this effect work over an image like you would see here. I was following an algorithm that people are calling the Hugo Elias method (though idk if he truly came up with the design). His tutorial can be found here!

When following that tutorial I found his pseudo code challenging to follow. I mean the concept for the most part makes sense until I hit the height map portion over an image. The problem being the x and y offsets throw an ArrayIndexOutOfBoundsException due to him adding the offset to the corresponding x or y. If the wave is too big (i.e. in my case 512) it throws an error; yet, if it is too small you can't see it.

Any ideas or fixes to my attempted implementation of his algorithm?

So I can't really make a compile-able version that is small and shows the issue, but I will give the three methods I'm using in the algorithm. Also keep in mind that the buffer1 and buffer2 are the height maps for the wave (current and previous) and imgArray is a bufferedImage represented by a int[img.getWidth() * img.getHeight()] full of ARGB values.

Anyways here you go:

public class WaveRippleAlgorithmOnImage extends JPanel implements Runnable, MouseListener, MouseMotionListener
{
    private int[] buffer1;
    private int[] buffer2;

    private int[] imgArray;
    private int[] movedImgArray;

    private static double dampening = 0.96;
    private BufferedImage img;

    public WaveRippleAlgorithmOnImage(BufferedImage img)
    {
        this.img = img;

        imgArray = new int[img.getHeight()*img.getWidth()];
        movedImgArray = new int[img.getHeight()*img.getWidth()];

        imgArray = img.getRGB(0, 0, 
                img.getWidth(), img.getHeight(), 
                null, 0, img.getWidth()); 

        //OLD CODE
        /*for(int y = 0; y < img.getHeight(); y++)
        {
            for(int x = 0; x < img.getWidth(); x++)
            {
                imgArray[y][x] = temp[0 + (y-0)*img.getWidth() + (x-0)];
            }
        }*/

        buffer1 = new int[img.getHeight()*img.getWidth()];
        buffer2 = new int[img.getHeight()*img.getWidth()];

        buffer1[buffer1.length/2] = (img.getWidth() <= img.getHeight() ? img.getWidth() / 3 : img.getHeight() / 3);
        //buffer1[25][25] = 10;

        back = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);

        this.addMouseListener(this);
        this.addMouseMotionListener(this);

    }

    //<editor-fold defaultstate="collapsed" desc="Used Methods">
    @Override
    public void run()
    {
        while(true)
        {
            this.update();
            this.repaint();
            this.swap();
        }
    }

    //Called from Thread to update movedImgArray prior to being drawn.
    private void update()
    {
        //This is my attempt of trying to convert his code to java.
        for (int i=img.getWidth(); i < imgArray.length - 1; i++)
        {
            if(i % img.getWidth() == 0 || i >= imgArray.length - img.getWidth())
                continue;

            buffer2[i] = (
                        ((buffer1[i-1]+
                          buffer1[i+1]+
                          buffer1[i-img.getWidth()]+
                          buffer1[i+img.getWidth()]) >> 1)) - buffer2[i];

            buffer2[i] -= (buffer2[i] >> 5);
        }

        //Still my version of his code, because of the int[] instead of int[][].
        for (int y = 1; y < img.getHeight() - 2; y++)
        {
            for(int x = 1; x < img.getWidth() - 2; x++)
            {
                int xOffset = buffer1[((y)*img.getWidth()) + (x-1)] - buffer1[((y)*img.getWidth()) + (x+1)];
                int yOffset = buffer1[((y-1)*img.getWidth()) + (x)] - buffer1[((y+1)*img.getWidth()) + (x)];

                int shading = xOffset;

                //Here is where the error occurs (after a click or wave started), because yOffset becomes -512; which in turn gets
                //multiplied by y... Not good... -_-
                movedImgArray[(y*img.getWidth()) + x] = imgArray[((y+yOffset)*img.getWidth()) + (x+xOffset)] + shading;
            }
        }

        //This is my OLD code that kidna worked...
        //I threw in here to show you how I was doing it before I switched to images.
        /*
        for(int y = 1; y < img.getHeight() - 1; y++)
        {
            for(int x = 1; x < img.getWidth() - 1; x++)
            {
                //buffer2[y][x] = ((buffer1[y][x-1] +
                //buffer1[y][x+1] +
                //buffer1[y+1][x] +
                //buffer1[y-1][x]) / 4) - buffer2[y][x];
                buffer2[y][x] = ((buffer1[y][x-1] +
                        buffer1[y][x+1] +
                        buffer1[y+1][x] +
                        buffer1[y-1][x] +
                        buffer1[y + 1][x-1] +
                        buffer1[y + 1][x+1] +
                        buffer1[y - 1][x - 1] +
                        buffer1[y - 1][x + 1]) / 4) - buffer2[y][x];

                buffer2[y][x] = (int)(buffer2[y][x] * dampening);
            }
        }*/
    }

    //Swaps buffers
    private void swap()
    {
        int[] temp;

        temp = buffer2;
        buffer2 = buffer1;
        buffer1 = temp;
    }

    //This creates a wave upon clicking.  It also is where that 512 is coming from.
    //512 was about right in my OLD code shown above, but helps to cause the Exeception now.
    @Override
    public void mouseClicked(MouseEvent e)
    {
        if(e.getX() > 0 && e.getY() > 0 && e.getX() < img.getWidth() && e.getY() < img.getHeight())
            buffer2[((e.getY())*img.getWidth()) + (e.getX())] = 512;
    }

    private BufferedImage back;
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        back.setRGB(0, 0, img.getWidth(), img.getHeight(), movedImgArray, 0, img.getWidth());
        g.drawImage(back, 0, 0, null);
    }
}

P.S. Here are two images of the old code working.

enter image description hereenter image description here

like image 899
Lost_Soul Avatar asked Jul 15 '14 05:07

Lost_Soul


1 Answers

Looking at my original pseudocode, I assume the Array Out Of Bounds error is happening when you try to look up the texture based on the offset. The problem happens because the refraction in the water is allowing us to see outside of the texture.

for every pixel (x,y) in the buffer

  Xoffset = buffer(x-1, y) - buffer(x+1, y)
  Yoffset = buffer(x, y-1) - buffer(x, y+1)

  Shading = Xoffset

  t = texture(x+Xoffset, y+Yoffset)  // Array out of bounds?

  p = t + Shading

  plot pixel at (x,y) with colour p

end loop

The way to fix this is simply to either clamp the texture coordinates, or let them wrap. Also, if you find that the amount of refraction is too much, you can reduce it by bit-shifting the Xoffset and Yoffset values a little bit.

int clamp(int x, int min, int max)
{
    if (x < min) return min;
    if (x > max) return max;
    return x;
}

int wrap(int x, int min, int max)
{
    while (x<min)
       x += (1+max-min);

    while (x>max)
       x -= (1+max-min);

    return x;
}


for every pixel (x,y) in the buffer

    Xoffset = buffer(x-1, y) - buffer(x+1, y)
    Yoffset = buffer(x, y-1) - buffer(x, y+1)

    Shading = Xoffset

    Xoffset >>= 1                              // Halve the amount of refraction
    Yoffset >>= 1                              // if you want.

    Xcoordinate = clamp(x+Xoffset, 0, Xmax)    // Use clamp() or wrap() here
    Ycoordinate = clamp(y+Yoffset, 0, Ymax)    // 
    t = texture(Xcoordinate, Ycoordinate)  

    p = t + Shading

    plot pixel at (x,y) with colour p

end loop
like image 131
Rocketmagnet Avatar answered Sep 28 '22 14:09

Rocketmagnet