8

私の 2D ゲームでは、グラフィック ツールを使用して、黒色で表される美しく滑らかな地形を作成しています。 ここに画像の説明を入力

Java で記述された単純なアルゴリズムは、15 ピクセルごとに黒色を探し、次の一連の線 (灰色) を作成します。

ここに画像の説明を入力

ご覧のとおり、マッピングが非常に悪い場所もあれば、かなり良い場所もあります。それ以外の場合は、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("}");
}

JavaとAndEngineを使用して、Androidで開発しています

4

3 に答える 3

2

この問題は、信号 (音声など) のデジタル化の問題とほぼ同じです。基本的な法則は、サンプリング レートに対して周波数が高すぎる入力の信号は、デジタル化された出力に反映されないというものです。そのため、bmorris591 が示唆するように 30 ピクセルをチェックしてから中央をテストすると、サンプリング ポイント間の 7 ピクセルの穴を見逃す可能性があることが懸念されます。これは、見逃してはならない 10 ピクセルの特徴がある場合、5 ピクセルごとにスキャンする必要があることを示唆しています。サンプル レートは、信号に存在する最高周波数の 2 倍にする必要があります。

アルゴリズムの改善に役立つことの 1 つは、より優れた 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 までのウィンドウを検索するだけで済みます。また、地形がビットマップ全体と同じ高さにならない場合は、ビットマップの上部を検索する必要もありません。これにより、テレインの高解像度 X スキャンに使用できる CPU サイクルが解放されるはずです。

于 2013-03-09T16:07:57.327 に答える
2

最も効率的な解決策 (必要なポイントに関して) は、X 軸に沿ってポイント間の可変間隔を許可することです。このように、大きな平坦な部分では必要なポイント/サンプルが非常に少なくなり、複雑な地形ではより多くのポイント/サンプルが必要になります。

3D メッシュ処理には、「quadric edge collapse」という名前の優れたメッシュ単純化アルゴリズムがあり、これを問題に適応させることができます。

これがあなたの問題に翻訳されたアイデアです-実際には元の3Dアルゴリズムよりもはるかに単純になります:

  1. あまりにも多くの点で曲線を表現してください。
  2. 各点について、それを取り除いた場合の誤差 (つまり、滑らかな地形との差) を測定します。
  3. 最小の誤差を与えるポイントを削除します。
  4. ポイントの数を十分に減らすか、エラーが大きくなりすぎるまで繰り返します。

ステップ 2 に関してより正確に言うと: ポイントが与えられた場合P, Q, R、 の誤差は、2 本の直線による地形の近似と、およびQ1 本の直線による地形の近似との差です。P->QQ->RP->R

ポイントが削除されると、その近傍のみがエラー値の更新を必要とすることに注意してください。

于 2013-03-09T16:19:59.180 に答える
2

白と暗いピクセルの境界に存在する境界点を見つけることを提案します。その後、それらのポイントをデジタル化できます。DELTAそのためには、スキップするポイントと結果リストに追加するポイントを定義する必要があります。

DELTA = 3, Number of points = 223

ここに画像の説明を入力

DELTA = 5, Number of points = 136

ここに画像の説明を入力

DELTA = 10, Number of points = 70

ここに画像の説明を入力

以下に、画像を印刷してポイントを探すソースコードを示します。これを読んで、問題を解決する方法を見つけていただければ幸いです。

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);
        }
    }
}

私のソースコードでは、この質問の例を使用しました:

于 2013-03-09T16:51:02.497 に答える