Космический корабль-симулятор ориентации с таргетингом с концентрическими квадратами индикаторов

Я работаю над играми с 3D-пространством с некоторыми людьми, и одна из вещей, которые мне поручили сделать, - это создать "туннель" компьютера для инструкторов, с которым проходит корабль, с туннелем из квадратов что пользователь пролетает до места назначения, увеличиваясь в количестве, когда пользователь приближается к месту назначения.

Нужно только отображать квадраты для очков перед кораблем, так как это все, что видимо для пользователя. На своем пути к месту назначения корабль-компьютер должен помещать квадраты на HUD, которые представляют собой фиксированные точки в пространстве между вами и пунктом назначения, которые являются небольшими на расстоянии и становятся больше, когда точки приближаются к кораблю.

guidance squares example

У меня было время реализовать это и, похоже, не понимаю, в основном, используя логарифмы (Math.log10(x) и т.д.). Я попытался получить позицию корабля в "логарифмическом пространстве", чтобы выяснить, с какого индекса начинать рисовать квадраты, но тогда тот факт, что у меня только расстояние до места назначения, чтобы работать с смущением, особенно когда вы что число квадратов должно динамически меняться, чтобы убедиться, что они остаются фиксированными в правильных местах в пространстве (т.е. квадраты расположены с интервалами 200 или около того, прежде чем они будут преобразованы логарифмически).

В связи с этим у меня была рабочая реализация с кораблем между началом 0.0d и концом 1.0d, хотя реализация была не такой приятной. Во всяком случае, проблема по существу сводится к 1-й природе. Любые рекомендации будут оценены с этой проблемой, включая возможные обходные пути для достижения того же эффекта или решений.

Frontier: Elite 2

(Также есть видео Youtube, показывающее этот эффект: http://www.youtube.com/watch?v=79F9Nj7GgfM&t=3m5s)

Приветствия,
Крис

Изменить: перефразировал весь вопрос.

Изменить: новый тестовый код:

package st;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferStrategy;
import java.text.DecimalFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class StUI2 extends JFrame {
    public static final double DEG_TO_RAD = Math.PI / 180.0d;
    public static final DecimalFormat decimalFormat = new DecimalFormat("0.0000");

    public static final Font MONO = new Font("Monospaced", Font.PLAIN, 10);

    public class StPanel extends Canvas {
        protected final Object imgLock  = new Object();
        protected int lastWidth = 1, lastHeight = 1;
        protected boolean first = true;
        protected Color bgColour = Color.DARK_GRAY, gridColour = Color.GRAY;

        double shipWrap = 700;
        double shipFrame = 100;
        double shipPos = 0;
        long lastUpdateTimeMS = -1;
        long currUpdateTimeMS = -1;

        public StPanel() {      
            setFocusable(true);
            setMinimumSize(new Dimension(1, 1));
            setAlwaysOnTop(true);
        }

        public void internalPaint(Graphics2D g) {
            synchronized (imgLock) {
                if (lastUpdateTimeMS < 0) {
                    lastUpdateTimeMS = System.currentTimeMillis();
                }
                currUpdateTimeMS = System.currentTimeMillis();
                long diffMS = currUpdateTimeMS - lastUpdateTimeMS;

                g.setFont(MONO);

                shipPos += (60d * ((double)diffMS / 1000));
                if (shipPos > shipWrap) {
                    shipPos = 0d;
                }

                double shipPosPerc = shipPos / shipWrap;
                double distToDest = shipWrap - shipPos;
                double compression = 1000d / distToDest;

                g.setColor(bgColour);
                Dimension d = getSize();
                g.fillRect(0, 0, (int)d.getWidth(), (int)d.getHeight());

                //int amnt2 = (int)unlog10((1000d / distToDest));

                g.setColor(Color.WHITE);
                g.drawString("shipPos:    " + decimalFormat.format(shipPos),     10, 10);
                g.drawString("distToDest: " + decimalFormat.format(distToDest),  10, 20);

                g.drawString("shipWrap:   " + decimalFormat.format(shipWrap),    150, 10);

                int offset = 40;

                g.setFont(MONO);

                double scalingFactor = 10d;

                double dist = 0;
                int curri = 0;
                int i = 0;
                do {
                    curri = i;
                    g.setColor(Color.GREEN);

                    dist = distToDest - getSquareDistance(distToDest, scalingFactor, i);
                    double sqh = getSquareHeight(dist, 100d * DEG_TO_RAD);
                    g.drawLine(30 + (int)dist, (offset + 50) - (int)(sqh / 2d), 30 + (int)dist, (offset + 50) + (int)(sqh / 2d));
                    g.setColor(Color.LIGHT_GRAY);
                    g.drawString("i: " +  i + ", dist: " + decimalFormat.format(dist), 10, 120 + (i * 10));
                    i++;
                } while (dist < distToDest);

                g.drawLine(10, 122, 200, 122);
                g.drawString("last / i: " +  curri + ", dist: " + decimalFormat.format(dist), 10, 122 + (i * 10));

                g.setColor(Color.MAGENTA);
                g.fillOval(30 + (int)shipPos, offset + 50, 4, 4);

                lastUpdateTimeMS = currUpdateTimeMS;
            }
        }

        public double getSquareDistance(double initialDist, double scalingFactor, int num) {
            return Math.pow(scalingFactor, num) * num * initialDist;
        }

        public double getSquareHeight(double distance, double angle) {
            return distance / Math.tan(angle);
        }

        /* (non-Javadoc)
         * @see java.awt.Canvas#paint(java.awt.Graphics)
         */
        @Override
        public void paint(Graphics g) {
            internalPaint((Graphics2D)g);
        }

        public void redraw() {
            synchronized (imgLock) {
                Dimension d = getSize();
                if (d.width == 0)  d.width = 1;
                if (d.height == 0) d.height = 1;

                if (first || d.getWidth() != lastWidth || d.getHeight() != lastHeight) {
                    first = false;

                    // remake buf
                    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                    //create an object that represents the device that outputs to screen (video card).
                    GraphicsDevice gd = ge.getDefaultScreenDevice();
                    gd.getDefaultConfiguration();

                    createBufferStrategy(2);

                    lastWidth  = (int)d.getWidth();
                    lastHeight = (int)d.getHeight();
                }

                BufferStrategy strategy = getBufferStrategy();
                Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
                internalPaint(g);
                g.dispose();
                if (!strategy.contentsLost()) strategy.show();
            }
        }
    }

    protected final StPanel canvas;

    protected Timer viewTimer = new Timer(1000 / 60, new ActionListener() {     
        @Override
        public void actionPerformed(ActionEvent e) {
            canvas.redraw();
        }
    });
    {
        viewTimer.setRepeats(true);
        viewTimer.setCoalesce(true);
    }

    /**
     * Create the applet.
     */
    public StUI2() {
        JPanel panel = new JPanel(new BorderLayout());
        setContentPane(panel);
        panel.add(canvas = new StPanel(), BorderLayout.CENTER);
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(800, 300);
        setTitle("Targetting indicator test #2");
        viewTimer.start();
    }

    public static double unlog10(double x) {  
        return Math.pow(10d, x);
    }   

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                StUI2 ui = new StUI2();
            }
        });
    }
}

Ответ 1

Предполагая, что квадраты должны быть равны по высоте (когда вы их достигнете), вы можете рассчитать коэффициент масштабирования, основанный на расстоянии до места назначения (d), и необходимую высоту квадратов при их достижении (h).

Из этих двух частей информации вы можете рассчитать обратный тангенс (atan) угла (alpha) между линией, соединяющей корабль с пунктом назначения (горизонтальная линия на вашем изображении) и линией, соединяющей верхнюю квадратов с местом назначения (угловая линия на вашем изображении).

EDIT: исправленная формула Используя угол, вы можете рассчитать высоту квадрата (h') на любом заданном расстоянии от места назначения: вы знаете расстояние до пункта назначения (d') и угол (alpha); Высота квадрата на расстоянии d' составляет h'=r'*sin(alpha) - sin(alpha)=cos(alpha)*tan(alpha) и r'=d'/cos(alpha) (расстояние между местом назначения и вершиной квадрата - "радиус" ). Или проще: h'=d'*tan(alpha).

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

Если высота квадрата на расстоянии d' вычисляется для вас графической библиотекой, тем лучше, вам нужно всего лишь выяснить расстояния, чтобы поместить квадраты.

Какие расстояния для размещения квадратов от места назначения?

1) Если вы хотите, чтобы на экране отображалось различное количество квадратов (перед кораблем), но потенциально бесконечное число квадратов, которые следует учитывать (на основе d), вы можете выбрать расстояние от ближайшего квадрата до места назначения (d1) и вычислить расстояния других квадратов по формуле s^k*k*d1, где s (коэффициент масштабирования) является числом > 1 для k 'th квадрата (считая от адресата). Вы можете остановить алгоритм, если результат больше, чем d.

Обратите внимание, что если d достаточно велико, квадраты, наиболее близкие к расстоянию, будут блокировать пункт назначения (их много, а их высоты малы из-за малого угла). В этом случае вы можете ввести минимальное расстояние (возможно, основанное на d), ниже которого вы не показываете квадраты - вам придется поэкспериментировать с точными значениями, чтобы увидеть, что выглядит правильным/приемлемым.

2) Если вы хотите, чтобы всегда отображалось фиксированное количество квадратов (sn), независимо от d, вы можете рассчитать расстояния квадратов от адресата по формуле d*s^k, где s число < 1, k - индекс квадрата (считая с корабля). Рассмотрение небольших квадратов, вероятно, не применимо здесь, если sn не является высоким.

Чтобы исправить обновленный код, измените ссылочную часть на:

double dist = 0;
double d1 = 10;
int curri = 0; 
int i = 1; 
int maxSquareHeight = 40;
double angle = Math.atan(maxSquareHeight/distToDest);
while (true)
{ 
  curri = i; 
  g.setColor(Color.GREEN); 

  dist = getSquareDistance(d1, scalingFactor, i); 
  if (dist > distToDest) {
    break;
  }
  double sqh = getSquareHeight(dist, angle); 
  g.drawLine(30 + (int)(shipWrap - dist), offset+50-(int)(sqh / 2d), 30 + (int)(shipWrap - dist), offset+50+(int)(sqh / 2d)); 
  g.setColor(Color.LIGHT_GRAY); 
  i++; 
}

public double getSquareHeight(double distance, double angle) { 
  return distance * Math.tan(angle); 
} 

Вы также должны уменьшить масштабированиеFactor до величины ~ 1.5.

EDIT: Если вы замените формулу s^k*k*d1 на s^(k-1)*k*d1, то первый квадрат будет точно на расстоянии d1.

EDIT: формула расчета фиксированной квадратной высоты

EDIT: обновленный код