«КАК DOOM ПОЖАР БЫЛ СДЕЛАН» - Реализация анимации огня, которая приводит к анимации шума - PullRequest
0 голосов
/ 02 января 2019

(примечание: пример Minimal, Complete и Verifiable приведен в конце этого вопроса)

Краткое описание

  1. Контекст, цель и проблема

  2. Мое (возможно, неправильное?) Понимание авторских объяснений и источников - примечание: я должен объяснить это вам, поскольку мои ошибки могут быть связаны с плохим пониманием того, что делать, или с плохой реализацией от моего имени ...

  3. Что я сделал

  4. Ожидаемые результаты, фактические результаты и вопрос

  5. Пример минимального, полного и проверяемого значения

Контекст, цель и проблема

Я изучаю анимацию знаменитой игры DOOM в соответствии с http://fabiensanglard.net/doom_fire_psx/,, которая заключается в генерации огня.

Полная версия авторского кода: https://github.com/fabiensanglard/DoomFirePSX/blob/master/flames.html

Я закончил с этой реализацией, но в результате я получил анимацию шума (вот в чем проблема). Действительно, в конце этой анимации я получаю такой результат:

enter image description here


Мое (возможно, неправильное?) Понимание авторских объяснений и источников

Первый подход: реализация основных принципов и упрощение огня (потому что заменены простым градиентом цветов)

  1. Установите точно заполненный набор цветов: этот набор определяет градиент, который выглядит как огонь. Есть 36 цветов от белого до черного, и среди них есть желтый, оранжевый и красный. Этот набор не содержит дубликатов.

  2. Итерация в первый раз на пикселях холста. Здесь цель состоит в том, чтобы покрасить все пиксели в черный цвет (т.е. последний цвет набора).

  3. Повторять второй раз на холсте. На этот раз мы должны закрасить белым цветом пиксели первой строки внизу (т. Е. Первый цвет набора).

  4. Повторять снова на холсте, но только со второй нижней строки (включена), а не с первой нижней строки (которая, таким образом, исключается). Для каждого повторного пикселя мы изменяем его цвет следующим образом: мы берем цвет его прямого нижнего пикселя, находим индекс этого цвета во всех цветах, добавляем 1 к этому индексу: мы получаем другой индекс с именем i2, мы найдите цвет с индексом i2, затем мы применим этот цвет к этому повторному пикселю.

По завершении выполнения будет несколько градиентов, каждый из которых содержит 36 строк (36 цветов).

Чтобы он действительно выглядел как огонь.

Программа, объясненная http://fabiensanglard.net/doom_fire_psx/, конечно же, идет дальше: она использует псевдослучайное дважды, чтобы получить что-то, что не похоже на простой градиент, но действительно похоже на огонь.

Идея заключается в следующем. Для повторяемого пикселя:

Мы получаем индекс цвета пикселя, расположенного чуть ниже. Затем мы получаем цвет в наборе цветов, индекс которого равен этому индексу + случайное число, включающее небольшое смещение (максимум на 2 квадрата, если я правильно помню). Таким образом, мы можем моделировать ускорение изменения температуры частиц.

И, кроме того, мы рассматриваем пиксель, расположенный немного левее итерируемого пикселя. «Немножко» = в соответствии с тем же случайным номером чипа № 1. Именно этому пикселю, расположенному немного слева, будет присвоен цвет, восстановленный в чипе № 1. Таким образом, мы можем смоделировать горизонтальное смещение слева от пламени.

Итак, мы видим, что это треугольник (потому что мы берем итерируемый пиксель, один чуть ниже, а другой чуть левее).

Псевдослучайный элемент, используемый для моделирования ускорения ускорения изменения температуры частиц

Генерируется случайное число, от 0 до 3, включенное и используемое здесь:

firePixels[src - FIRE_WIDTH ] = pixel - (rand & 1);

В связи с этим вводится небольшое изменение в применяемом цвете.

Псевдослучайный элемент, используемый для моделирования горизонтального смещения влево от пламени.

В дополнение к тому, что мы только что видели, также используется псевдослучайный элемент, используемый в пикселях для каждой строки.

Здесь используется то же случайное число:

var dst = src - rand + 1; firePixels[dst - FIRE_WIDTH ] = pixel - (rand & 1);

Здесь небольшое смещение выполняется по горизонтали.


Что я сделал

Все объяснения, приведенные выше, были выполнены, но моя программа выдает плохие результаты.Итак:

  1. Либо я плохо понял мышление,

  2. Или я плохо его реализовал.

Ниже вы найдете источники реализации.

Ожидаемые результаты, фактические результаты и вопрос

Я ожидаю иметь несколько вертикальных градиентов (каждый снизу вверх).«Несколько», потому что высота моего холста больше, чем количество цветов моих градиентов, и потому что я использую по модулю, чтобы выбрать цвет для применения.Эти градиенты должны быть похожи на градиенты проклятия (http://fabiensanglard.net/doom_fire_psx/).

Фактические результаты: вместо этого я получаю несколько шумов.

Мой вопрос: почему это не работает? Я думаю, что яХорошо понимаю, что делать. Возможно, я забыл что-то в реализации, но что?

Мой вопрос: почему это не работает? Я думаю, что я хорошо понял, что делать. Возможно, я забыл что-то в реализации, но что?

Пример минимального, полного и проверяемого

Launcher.java

import java.util.ArrayList;

public class Launcher {

    public static void main(String args[]) {
        int width = 800, height = 800;
        Gui gui = new Gui(width, height);
        gui.setUp("DOOM-like fire");
        gui.setVisible(true);

        Colors colors = new FireColors(new ArrayList<>());
        gui.colorize(colors.getLastColor(), -1, -1);  // Setting black anywhere
        gui.colorize(colors.getColorAtIndex(0), -1, height - 1);  // Setting white, in a lower line

        new ThreadPainter(0, colors, gui, width, height).schedulePainting();
    }

}

Gui.java

import java.awt.*;
import javax.swing.*;
import java.awt.image.BufferedImage;

class Gui extends JFrame {

    private JPanel panel;
    private BufferedImage buffered_image;

    Gui(int width, int height) {
        buffered_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        panel = new JPanel() {
            public void paintComponent(Graphics graphics) {
                super.paintComponent(graphics);
                graphics.drawImage(buffered_image, 0, 0, null);
            }
        };
    }

    void setUp(String title) {
        setTitle(title);
        setLayout(null);
        setSize(buffered_image.getWidth(), buffered_image.getHeight());
        setContentPane(panel);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    void colorize(Color color, int x_parameter, int y_parameter) {
        for(int y = (y_parameter == -1 ? 0 : y_parameter); y <= (y_parameter == -1 ? this.getHeight() - 1 : y_parameter); y++) {
            for(int x = (x_parameter == -1 ? 0 : x_parameter); x <= (x_parameter== -1 ? this.getWidth() - 1 : x_parameter); x++) {
                buffered_image.setRGB(x, y, color.getRGB());
            }
        }
        panel.repaint();
    }

    int getRGBAtCoordinates(int x, int y) {
        return buffered_image.getRGB(x, y);
    }

}

ThreadPainter.java

import java.util.Timer;
import java.util.TimerTask;

class ThreadPainter extends Timer {
    private Gui gui;
    private Colors colors;
    private int delay;
    private int height;
    private int width;

    ThreadPainter(int delay, Colors colors, Gui gui, int width, int height) {
        this.colors = colors;
        this.gui = gui;
        this.delay = delay;
        this.width = width;
        this.height = height;
    }

    void schedulePainting() {
        this.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    int number_of_colored_portions = height / colors.getSize();
                    // int locking_changement_of_color = 0;
                    for(int y = height - 2; y >= 0; y--) {

                        //if(locking_changement_of_color == number_of_colored_portions) {
                            //locking_changement_of_color = 0;
                        /*} else {
                            index_of_color_to_apply = index_of_found_color;
                        }*/

                        for(int x = 0; x < width; x++) {
                            int rand = (int) Math.round(Math.random() * 3.0) & 3;
                            int below_pixel_rgb = gui.getRGBAtCoordinates(x, y + 1);
                            int index_of_found_color = colors.getIndexOfColor(below_pixel_rgb);
                            int index_of_color_to_apply = (index_of_found_color + (rand & 1)) % colors.getSize();

                            int x_copy = x - rand + 1;
                            if(x_copy <= 0) {
                                x_copy = 0;
                            } else if(x_copy >= width) {
                                x_copy = width - 1;
                            }
                            gui.colorize(colors.getColorAtIndex(index_of_color_to_apply), x_copy, y);
                        }

                        //locking_changement_of_color++;
                        //Thread.sleep(10);
                    }
                } catch(Exception e) {
                    System.err.println("Exception: " + e.getMessage());
                }
            }
        }, this.delay);
    }

}

Colors.java

import java.awt.Color;
import java.util.List;

abstract class Colors {
    List<Color> colors;

    Color getColorAtIndex(int index) {
        return colors.get(index);
    }

    int getIndexOfColor(int rgb) throws Exception {
        for (int x = 0; x < colors.size(); x++) {
            if(colors.get(x).getRGB() == rgb) {
                return x;
            }
        }
        throw new Exception("Color not found in the list!");
    }

    int getSize() {
        return colors.size();
    }

    Color getLastColor() {
        return colors.get(colors.size() - 1);
    }
}

FireColors.java

import java.awt.Color;
import java.util.List;

class FireColors extends Colors {

    FireColors(List<Color> colors) {

        this.colors = colors;

        this.colors.add(new Color(255, 255, 255));
        this.colors.add(new Color(239, 239, 199));
        this.colors.add(new Color(223, 223, 159));
        this.colors.add(new Color(207, 207, 111));
        this.colors.add(new Color(183, 183, 55));
        this.colors.add(new Color(183, 183, 47));
        this.colors.add(new Color(183, 175, 47));
        this.colors.add(new Color(191, 175, 47));
        this.colors.add(new Color(191, 167, 39));
        this.colors.add(new Color(191, 167, 39));
        this.colors.add(new Color(191, 159, 31));
        this.colors.add(new Color(191, 159, 31));
        this.colors.add(new Color(199, 151, 31));
        this.colors.add(new Color(199, 143, 23));
        this.colors.add(new Color(199, 135, 23));
        this.colors.add(new Color(207, 135, 23));
        this.colors.add(new Color(207, 127, 15));
        this.colors.add(new Color(207, 119, 15));
        this.colors.add(new Color(207, 111, 15));
        this.colors.add(new Color(215, 103, 15));
        this.colors.add(new Color(215, 95, 7));
        this.colors.add(new Color(223, 87, 7));
        this.colors.add(new Color(223, 87, 7));
        this.colors.add(new Color(223, 79, 7));
        this.colors.add(new Color(199, 71, 7));
        this.colors.add(new Color(191, 71, 7));
        this.colors.add(new Color(175, 63, 7));
        this.colors.add(new Color(159, 47, 7));
        this.colors.add(new Color(143, 39, 7));
        this.colors.add(new Color(119, 31, 7));
        this.colors.add(new Color(103, 31, 7));
        this.colors.add(new Color(87, 23, 7));
        this.colors.add(new Color(71, 15, 7));
        this.colors.add(new Color(47, 15, 7));
        this.colors.add(new Color(7, 7, 7));

    }

}

1 Ответ

0 голосов
/ 02 января 2019

Есть много небольших проблем, поэтому здесь есть исправленная версия с комментариями, чтобы вы могли понять (я упростил классы в один файл):

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class Launcher {
    private static class Gui extends JFrame {
        final int width;
        final int height;
        final JPanel panel;
        final BufferedImage buffered_image;

        Gui(final String title, final int width, final int height) {
            this.width = width;
            this.height = height;
            this.buffered_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            this.panel = new JPanel() {
                @Override
                public void paintComponent(final Graphics graphics) {
                    super.paintComponent(graphics);
                    graphics.drawImage(Gui.this.buffered_image, 0, 0, null);
                }
            };
            this.setTitle(title);
            this.setContentPane(this.panel);
            this.setSize(width, height);
            this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            this.setVisible(true);
        }

        void colorize(final Color color, final int x, final int y) {
            if ((x < 0) || (x >= this.width) || (y < 0) || (y >= this.height))
                return;
            this.buffered_image.setRGB(x, y, color.getRGB());
        }

        int getRGBAtCoordinates(final int x, final int y) {
            return this.buffered_image.getRGB(x, y);
        }
    }

    public static void main(final String args[]) {
        final List<Color> colors = new ArrayList<>();
        colors.add(new Color(0, 0, 0)); // black
        colors.add(new Color(7, 7, 7));
        colors.add(new Color(47, 15, 7));
        colors.add(new Color(71, 15, 7));
        colors.add(new Color(87, 23, 7));
        colors.add(new Color(103, 31, 7));
        colors.add(new Color(119, 31, 7));
        colors.add(new Color(143, 39, 7));
        colors.add(new Color(159, 47, 7));
        colors.add(new Color(175, 63, 7));
        colors.add(new Color(191, 71, 7));
        colors.add(new Color(199, 71, 7));
        colors.add(new Color(223, 79, 7));
        colors.add(new Color(223, 87, 7));
        colors.add(new Color(223, 87, 7));
        colors.add(new Color(215, 95, 7));
        colors.add(new Color(215, 103, 15));
        colors.add(new Color(207, 111, 15));
        colors.add(new Color(207, 119, 15));
        colors.add(new Color(207, 127, 15));
        colors.add(new Color(207, 135, 23));
        colors.add(new Color(199, 135, 23));
        colors.add(new Color(199, 143, 23));
        colors.add(new Color(199, 151, 31));
        colors.add(new Color(191, 159, 31));
        colors.add(new Color(191, 159, 31));
        colors.add(new Color(191, 167, 39));
        colors.add(new Color(191, 167, 39));
        colors.add(new Color(191, 175, 47));
        colors.add(new Color(183, 175, 47));
        colors.add(new Color(183, 183, 47));
        colors.add(new Color(183, 183, 55));
        colors.add(new Color(207, 207, 111));
        colors.add(new Color(223, 223, 159));
        colors.add(new Color(239, 239, 199));
        colors.add(new Color(255, 255, 255)); // white

        final Gui gui = new Gui("DOOM-like fire", 800, 800);

        final Color black = colors.get(0);
        final Color white = colors.get(colors.size() - 1);
        final Dimension dim = gui.getContentPane().getSize(); // get actual size, without title/borders
        for (int y = 0; y < dim.height; y++) {
            final Color clr = y < (dim.height - 1) ? black : white;
            for (int x = 0; x < dim.width; x++)
                gui.colorize(clr, x, y);
        }

        new Timer().schedule(new TimerTask() {
            final Random rnd = new Random();

            Color getColorAtIndex(final int index) {
                if (index < 0)
                    return colors.get(0); // minimal color is black
                return colors.get(index);
            }

            int getIndexOfColor(final int rgb) {
                for (int x = 0; x < colors.size(); x++)
                    if (colors.get(x).getRGB() == rgb)
                        return x;
                throw new RuntimeException("Color not found in the list!");
            }

            @Override
            public void run() {
                for (int x = 0; x < dim.width; x++) {
                    for (int y = 1; y < dim.height; y++) {
                        final int new_index = this.getIndexOfColor(gui.getRGBAtCoordinates(x, y)) - this.rnd.nextInt(2);
                        final int new_x = (x - this.rnd.nextInt(3)) + 1;
                        gui.colorize(this.getColorAtIndex(new_index), new_x, y - 1);
                    }
                }
                gui.repaint();
            }
        }, 0, 40); // start immediately and repeat every 40ms
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...