地形曲线到点阵列

时间:2013-03-09 14:34:36

标签: java algorithm andengine points curve

在我的2D游戏中,我使用图形工具创建由黑色代表的漂亮,平滑的地形: enter image description here

用java编写的简单算法每15个像素查找一次黑色,创建以下一组行(灰色):

enter image description here

正如你所看到的,有些地方的地图非常糟糕,有些地方非常好。在其他情况下,没有必要对每15个像素进行采样,例如。如果地形平坦。

使用尽可能少的点将这条曲线转换为点[线]的最佳方法是什么? 每15个像素采样= 55 FPS,10像素= 40 FPS

以下算法正在执行该任务,从右到左采样,将可粘贴输出到代码数组中:

public void loadMapFile(String path) throws IOException {
    File mapFile = new File(path);
    image = ImageIO.read(mapFile);
    boolean black;
    System.out.print("{ ");

    int[] lastPoint = {0, 0};

    for (int x = image.getWidth()-1; x >= 0; x -= 15) {
        for (int y = 0; y < image.getHeight(); y++) {
            black = image.getRGB(x, y) == -16777216 ? true : false;

            if (black) {
                lastPoint[0] = x;
                lastPoint[1] = y;
                System.out.print("{" + (x) + ", " + (y) + "}, ");
                break;
            }

        }
    }

    System.out.println("}");
}

我在Android上开发,使用Java和AndEngine

3 个答案:

答案 0 :(得分:2)

这个问题与信号(例如声音)的数字化问题几乎完全相同,其中基本规律是输入中频率太高而无法采样率的信号将不会反映在数字化中输出。因此,值得关注的是,如果你检查了30个像素然后测试中间值为bmorris591建议,你可能会错过采样点之间的7个像素孔。这表明,如果有10个像素功能,你不能错过,你需要每5个像素扫描一次:你的采样率应该是信号中最高频率的两倍。

有助于改进算法的一件事是更好的y维搜索。目前,您正在线性搜索天空和地形之间的交叉点,但二进制搜索应该更快

int y = image.getHeight()/2; // Start searching from the middle of the image
int yIncr = y/2;
while (yIncr>0) {
    if (image.getRGB(x, y) == -16777216) {
        // We hit the terrain, to towards the sky
        y-=yIncr;
    } else {
        // We hit the sky, go towards the terrain
        y+=yIncr;
    }
    yIncr = yIncr/2;
}
// Make sure y is on the first terrain point: move y up or down a few pixels
// Only one of the following two loops will execute, and only one or two iterations max
while (image.getRGB(x, y) != -16777216) y++; 
while (image.getRGB(x, y-1) == -16777216) y--;

其他优化也是可能的。如果您知道您的地形没有悬崖,那么您只需要从lastY + maxDropoff到lastY-maxDropoff搜索窗口。此外,如果您的地形永远不会像整个位图一样高,则您也不需要搜索位图的顶部。这应该有助于释放一些CPU周期,可用于更高分辨率的X扫描地形。

答案 1 :(得分:2)

最有效的解决方案(关于所需的点)将允许沿X轴的点之间的可变间距。这样,大的平坦部分将需要非常少的点/样本,并且复杂的地形将使用更多。

在3D网格处理中,有一个很好的网格简化算法,名为“二次边折叠”,你可以适应你的问题。

这个想法,转化为你的问题 - 它实际上比原始的3D算法简单得多:

  1. 用太多的点来表示你的曲线。
  2. 对于每个点,如果删除它,请测量误差(即与平滑地形的差异)。
  3. 删除产生最小错误的点。
  4. 重复,直到您将点数减少到足够远或错误变得太大。
  5. 更准确地说明第2步:给定点P, Q, RQ的误差是地形近似两条直线P->Q和{{1}之间的差异只需一行Q->R即可近似地形。

    请注意,删除某个点时,只有其邻居需要更新其错误值。

答案 2 :(得分:2)

我建议找到存在于白色和暗像素之间边界上的边界点。之后我们可以将这些点数字化。为此,我们应该定义DELTA,指定我们应该跳过哪个点以及应该将哪些点添加到结果列表中。

DELTA = 3, Number of points = 223

enter image description here

DELTA = 5, Number of points = 136

enter image description here

DELTA = 10, Number of points = 70

enter image description here

下面,我提供了源代码,可以打印图像并查找点。我希望,您将能够阅读并找到解决问题的方法。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Program {

    public static void main(String[] args) throws IOException {
        BufferedImage image = ImageIO.read(new File("/home/michal/Desktop/FkXG1.png"));
        PathFinder pathFinder = new PathFinder(10);
        List<Point> borderPoints = pathFinder.findBorderPoints(image);
        System.out.println(Arrays.toString(borderPoints.toArray()));
        System.out.println(borderPoints.size());

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new ImageBorderPanel(image, borderPoints));
        frame.pack();
        frame.setMinimumSize(new Dimension(image.getWidth(), image.getHeight()));
        frame.setVisible(true);
    }
}

class PathFinder {

    private int maxDelta = 3;

    public PathFinder(int delta) {
        this.maxDelta = delta;
    }

    public List<Point> findBorderPoints(BufferedImage image) {
        int width = image.getWidth();
        int[][] imageInBytes = convertTo2DWithoutUsingGetRGB(image);
        int[] borderPoints = findBorderPoints(width, imageInBytes);

        List<Integer> indexes = dwindlePoints(width, borderPoints);
        List<Point> points = new ArrayList<Point>(indexes.size());
        for (Integer index : indexes) {
            points.add(new Point(index, borderPoints[index]));
        }
        return points;
    }

    private List<Integer> dwindlePoints(int width, int[] borderPoints) {
        List<Integer> indexes = new ArrayList<Integer>(width);
        indexes.add(borderPoints[0]);
        int delta = 0;
        for (int index = 1; index < width; index++) {
            delta += Math.abs(borderPoints[index - 1] - borderPoints[index]);
            if (delta >= maxDelta) {
                indexes.add(index);
                delta = 0;
            }
        }
        return indexes;
    }

    private int[] findBorderPoints(int width, int[][] imageInBytes) {
        int[] borderPoints = new int[width];
        int black = Color.BLACK.getRGB();
        for (int y = 0; y < imageInBytes.length; y++) {
            int maxX = imageInBytes[y].length;
            for (int x = 0; x < maxX; x++) {
                int color = imageInBytes[y][x];
                if (color == black && borderPoints[x] == 0) {
                    borderPoints[x] = y;
                }
            }
        }
        return borderPoints;
    }

    private int[][] convertTo2DWithoutUsingGetRGB(BufferedImage image) {
        final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        final int width = image.getWidth();
        final int height = image.getHeight();
        final boolean hasAlphaChannel = image.getAlphaRaster() != null;

        int[][] result = new int[height][width];
        if (hasAlphaChannel) {
            final int pixelLength = 4;
            for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
                int argb = 0;
                argb += (((int) pixels[pixel] & 0xff) << 24); // alpha
                argb += ((int) pixels[pixel + 1] & 0xff); // blue
                argb += (((int) pixels[pixel + 2] & 0xff) << 8); // green
                argb += (((int) pixels[pixel + 3] & 0xff) << 16); // red
                result[row][col] = argb;
                col++;
                if (col == width) {
                    col = 0;
                    row++;
                }
            }
        } else {
            final int pixelLength = 3;
            for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
                int argb = 0;
                argb += -16777216; // 255 alpha
                argb += ((int) pixels[pixel] & 0xff); // blue
                argb += (((int) pixels[pixel + 1] & 0xff) << 8); // green
                argb += (((int) pixels[pixel + 2] & 0xff) << 16); // red
                result[row][col] = argb;
                col++;
                if (col == width) {
                    col = 0;
                    row++;
                }
            }
        }

        return result;
    }
}

class ImageBorderPanel extends JPanel {

    private static final long serialVersionUID = 1L;

    private BufferedImage image;
    private List<Point> borderPoints;

    public ImageBorderPanel(BufferedImage image, List<Point> borderPoints) {
        this.image = image;
        this.borderPoints = borderPoints;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image, 0, 0, null);

        Graphics2D graphics2d = (Graphics2D) g;

        g.setColor(Color.YELLOW);
        for (Point point : borderPoints) {
            graphics2d.fillRect(point.x, point.y, 3, 3);
        }
    }
}

在我的源代码中,我使用了这个问题的例子: