Ryu Kajiya
Ryu Kajiya

Reputation: 265

Java mouse picking on diamond tiled map

Ok, im at my wits end. I am trying to create a small isometric tile map thats bigger then the screen witha viewpoint i can modify with mouse dragging. I got the drawing right (i think), i got the dragging working, just do't seem to be able to get the mouse picking right. I have made it so far that i get the tile nearly right, but its off by about half a tiles size and i can't find a way to make up that offset.

Here is the code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MapView {

    public static void main(String[] args) {
        JFrame test = new JFrame("IsoView");
        test.setSize(800, 600);
        MapViewPane pane = new MapViewPane();
        test.getContentPane().setLayout(new BorderLayout());
        test.getContentPane().add(pane, BorderLayout.CENTER);
        test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        test.setVisible(true);
    }

    private static class MapViewPane extends JPanel
            implements MouseMotionListener, MouseListener {

        private BufferedImage BackImage;
        BufferedImage GrassTile, SelectedBorder;
        private Point MousePoint, PrevView, ViewLocation, Selected;
        private boolean Dragging;
        private int mapwidth, mapheight, tilecount;

        public MapViewPane() {
            super();
            this.setOpaque(true);
            createAssets();
            tilecount = 30;
            mapwidth = GrassTile.getWidth() * tilecount;
            mapheight = GrassTile.getHeight() * tilecount;
            ViewLocation = new Point(0, mapheight / 2);
            Selected = new Point(-1, -1);
            addMouseListener(this);
            addMouseMotionListener(this);
        }

        private void createAssets() {
            GraphicsConfiguration gc =
                    GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
            GrassTile = gc.createCompatibleImage(128,
                    64, Transparency.TRANSLUCENT);
            Graphics g = GrassTile.getGraphics();
            Polygon poly = new Polygon();
            poly.addPoint(0, 32);
            poly.addPoint(64, 0);
            poly.addPoint(128, 32);
            poly.addPoint(64, 64);
            g.setColor(Color.GREEN);
            g.fillPolygon(poly);
            g.setColor(Color.BLUE);
            g.drawPolygon(poly);
            g.dispose();
            SelectedBorder = gc.createCompatibleImage(128,
                    64, Transparency.TRANSLUCENT);
            g = SelectedBorder.getGraphics();
            g.setColor(Color.red);
            g.drawPolygon(poly);
            g.dispose();
        }

        @Override
        public void paint(Graphics g) {
            //super.paint(g);
            Rectangle visiblerec = this.getVisibleRect();
            g.setColor(Color.BLACK);
            g.fillRect(visiblerec.x, visiblerec.y,
                    visiblerec.width, visiblerec.height);
            checkBackImage();
            Graphics bg = BackImage.getGraphics();
            drawGrassGrid(bg);
            bg.dispose();
            g.drawImage(BackImage, 0, 0, this);
        }

        private void drawGrassGrid(Graphics g) {
            int dx = 0;
            int dy = 0;
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, BackImage.getWidth(), BackImage.getHeight());
            for (int x = 0; x < tilecount; x++) {
                for (int y = 0; y < tilecount; y++) {
                    dx = x * GrassTile.getWidth() / 2
                            - y * GrassTile.getWidth() / 2;
                    dy = x * GrassTile.getHeight() / 2
                            + y * GrassTile.getHeight() / 2;
                    dx -= ViewLocation.x;
                    dy -= ViewLocation.y;
                    g.drawImage(GrassTile, dx, dy, this);
                    if ((x == Selected.x) && (y == Selected.y)) {
                        g.drawImage(SelectedBorder, dx, dy, this);
                    }
                    g.drawString("(" + x + "," + y + ")", dx, dy
                            + GrassTile.getHeight() / 2);
                }
            }
        }

        private void checkBackImage() {
            if ((BackImage == null) || (BackImage.getWidth() != this.getWidth())
                    || (BackImage.getHeight() != this.getHeight())) {
                GraphicsConfiguration gc =
                        GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
                BackImage = gc.createCompatibleImage(this.getWidth(),
                        this.getHeight(), Transparency.BITMASK);
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (Dragging) {
                ViewLocation.x = PrevView.x + MousePoint.x - e.getX();
                ViewLocation.y = PrevView.y + MousePoint.y - e.getY();
                if (ViewLocation.x < -mapwidth / 2) {
                    ViewLocation.x = -mapwidth / 2;
                }
                if (ViewLocation.y < -mapheight / 2 + this.getHeight()) {
                    ViewLocation.y = -mapheight / 2 + this.getHeight();
                }
                if (ViewLocation.x > mapwidth / 2 - this.getWidth()
                        + GrassTile.getWidth()) {
                    ViewLocation.x = mapwidth / 2 - this.getWidth()
                            + GrassTile.getWidth();
                }
                if (ViewLocation.y > mapheight / 2 + this.getHeight()) {
                    ViewLocation.y = mapheight / 2 + this.getHeight();
                }
                repaint();
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            if (!Dragging) {
                int x = (GrassTile.getWidth() * (e.getY() + ViewLocation.y)
                        + GrassTile.getHeight() * (e.getX() + ViewLocation.x))
                        / (GrassTile.getWidth() * GrassTile.getHeight());
                int y = (GrassTile.getWidth() * (e.getY() + ViewLocation.y)
                        - GrassTile.getHeight() * (e.getX() + ViewLocation.x))
                        / (GrassTile.getWidth() * GrassTile.getHeight());

//      int x = (int) Math.floor((e.getY() + ViewLocation.y)
//              / (double) GrassTile.getHeight() - (e.getX() + ViewLocation.x)
//              / (double) GrassTile.getWidth());
//      int y = (int) Math.floor((e.getY() + ViewLocation.y)
//              / (double) GrassTile.getHeight() + (e.getX() + ViewLocation.x)
//              / (double) GrassTile.getWidth());

                Selected.setLocation(x, y);
                repaint();
                System.out.println("(" + x + "," + y + ")");
            }
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if ((e.getButton() == MouseEvent.BUTTON1) && !Dragging) {
                MousePoint = e.getPoint();
                PrevView = new Point(ViewLocation);
                Dragging = true;
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            Dragging = false;
            MousePoint = null;
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }
    }
}

I have some formulas in the click method commented out wich i tried, but they dont work (x and y axis are inverted that way, havent tried to figure out why yet). Would realy appreciate if someone can point me to the mistake i'm making.

Upvotes: 2

Views: 1293

Answers (2)

Charles Goodwin
Charles Goodwin

Reputation: 6642

I managed to fix it for you. Firstly I did a bit of algebra (hopefully explained by the inline comments) to simplify the calculation of which tile is hit. The next bit you did somewhat realise; there's issues casting. You need to convert everything to a double before you use it. If you do int/int then you already made a cast and lost precision.

The trick to making it hit the right tile is 1) casting early and 2) the +/- 0.5 which is used to force the return int cast to go one way or the other. To elaborate, (int)6.9 == 6.

Here's the working answer:

    @Override
    public void mouseClicked(MouseEvent e) {
        if (!Dragging) {
            /*
            // copy of the tile location assignment code as a reminder
            dx = x * GrassTile.getWidth() / 2
                    - y * GrassTile.getWidth() / 2;
            dy = x * GrassTile.getHeight() / 2
                    + y * GrassTile.getHeight() / 2;
            dx -= ViewLocation.x;
            dy -= ViewLocation.y;
            */
            int pickX = e.getX() + ViewLocation.x;
            int pickY = e.getY() + ViewLocation.y;
            int tileW = GrassTile.getWidth();
            int tileH = GrassTile.getHeight();
            /*
            // assignment code refactored
            x - y = 2 * pickX / tileW;
            x + y = 2 * pickY / tileH;

            // x+y= refactored to y=
            y = (2*pickY / tileH) - x;
            // substitute into x-y + refactor
            2x = (2 * pickX / tileW) + (2 * pickY / tileH);

            // x+y= refactored to x=
            x = (2*pickY / tileH) - y;
            // substitute x-y + refactor
            -2y = (2 * pickX / tileW) - (2 * pickY / tileH);
            2y = (2 * pickY / tileH) - (2 * pickX / tileW);
            */
            int hitx = (int)(((double)pickX / (double)tileW) + ((double)pickY / (double)tileH) - 0.5);
            int hity = (int)(((double)pickY / (double)tileH) - ((double)pickX / (double)tileW) + 0.5);
            Selected.setLocation(hitx, hity);
            repaint();
            //System.out.println("(" + x + "," + y + ")");
        }
    }

Upvotes: 1

trashgod
trashgod

Reputation: 205785

Polygon implements the Shape interface, so one of the several contain() variations may simplify your calculation. The createTransformedShape() method of AffineTransform may also be helpful, as suggested in this example.

Upvotes: 1

Related Questions