Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Space ship simulator guidance computer targeting with concentric indicator squares

I'm working on a 3D space trading game with some people, and one of the things I've been assigned to do is to make a guidance computer 'tunnel' that the ship travels through, with the tunnel made of squares that the user flies through to their destination, increasing in number as the user gets closer to the destination.

It's only necessary to render the squares for the points ahead of the ship, since that's all that's visible to the user. On their way to a destination, the ship's computer is supposed to put up squares on the HUD that represent fixed points in space between you and the destination, which are small in the distance and get larger as the points approach the craft.

guidance squares example

I've had a go at implementing this and can't seem to figure it out, mainly using logarithms (Math.log10(x) and such). I tried to get to get the ship position in 'logarithmic space' to help find out what index to start from when drawing the squares, but then the fact that I only have distance to the destination to work with confuses the matter, especially when you consider that the number of squares has to vary dynamically to make sure they stay fixed at the right locations in space (i.e., the squares are positioned at intervals of 200 or so before being transformed logarithmically).

With regard to this, I had a working implementation with the ship between a start of 0.0d and end of 1.0d, although the implementation wasn't so nice. Anyway, the problem essentially boils down to a 1d nature. Any advice would be appreciated with this issue, including possible workarounds to achieve the same effect or solutions.

Frontier: Elite 2

(Also, there's a Youtube video showing this effect: http://www.youtube.com/watch?v=79F9Nj7GgfM&t=3m5s)

Cheers,
Chris

Edit: rephrased the entire question.

Edit: new testbed code:

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();
            }
        });
    }
}
like image 954
Chris Dennett Avatar asked Mar 21 '12 00:03

Chris Dennett


1 Answers

Assuming you want the squares to be equal height (when you reach them), you can calculate a scaling factor based on the distance to the destination (d) and the required height of the squares upon reaching them (h).

From these two pieces of information you can calculate the inverse tangent (atan) of the angle (alpha) between the line connecting the ship to the destination (horizontal line in your image) and the line connecting the top of the squares with the destination (angled line in your image).

EDIT: corrected
formula Using the angle, you can calculate the height of the square (h') at any given distance from the destination: you know the distance to the destination (d') and the angle (alpha); The height of the square at distance d' is h'=r'*sin(alpha) -- sin(alpha)=cos(alpha)*tan(alpha) and r'=d'/cos(alpha) (the distance between the destination and the top of the square -- the "radius"). Or more easily: h'=d'*tan(alpha).

Note: adopting the algorithm to varying height (when you reach them) squares is relatively simple: when calculating the angle, just assume a (phantom) square of fixed height and scale the squares relatively to that.

If the height of the square at distance d' is calculated for you by your graphic library, all the better, you only need to figure out the distances to place the squares.

What distances to place the squares from the destination?

1) If you want a varying number of squares shown (in front of the ship), but potentially infinite number of squares to consider (based on d), you can chose the distance of the closest square to the destination (d1) and calculate the distances of other squares by the formula s^k*k*d1, where s (scaling factor) is a number > 1 for the k'th square (counting from the destination). You can stop the algorithm when the result is larger than d.

Note that if d is sufficiently large, the squares closest to the distance will block the destination (there are many of them and their heights are small due to the low angle). In this case you can introduce a minimal distance (possibly based on d), below which you do not display the squares -- you will have to experiment with the exact values to see what looks right/acceptable.

2) If you want a fixed amount of squares (sn) showing always, regardless of d, you can calculate the distances of the squares from the destination by the formula d*s^k, where s is a number < 1, k is the index of the square (counting from the ship). The consideration about small squares probably don't apply here unless sn is high.

To fix the updated code, change the relavant part to:

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

You should also reduce scalingFactor to the magnitude of ~1.5.

EDIT: If you replace the formula s^k*k*d1 with s^(k-1)*k*d1, then the first square will be exactly at distance d1.

EDIT: fixed square height calculating formula

EDIT: updated code

like image 152
Attila Avatar answered Sep 19 '22 04:09

Attila