# Mousepicking bei Iso-Heightmap/Slopes



## mantax (28. Jun 2009)

Hallo
Ich bin gerade dran ein isometrisches Spiel mit unterscheidlichen Höhen zu schreiben. Dargestellt wird auch alles schon ganz gut, jdeoch habe ich probleme beim Mousepicking. Dh ich möchte anhand der Mausposition das Tile darunter bestimmen können.
Bei einer flachen Isomap bekomm ich das locker hin, mit den Schrägen aber gibt es Tiles die quasi "aus dem Raster ragen" und andere Felder überschneiden. 
Ich habe schon dieses Tutorial gefunden, komme aber nicht so ganz dahinter.. :-/

Beispiel: 




Kann mir jemand helfen?

Vielen Dank schon mal


----------



## Marco13 (28. Jun 2009)

Ggf. Müßtest du die Frage präzisieren. Wenn ich das richtig an-überflogen habe, wird in dem Tutorial der "Trick" verwendet, dass jedes Tile seine eigene Farbe bekommt, und man dann Anhand der angeklickten Farbe das Tile rausfinden kann... ganz grob und halb im Pseudocode

Malt tiles mit unterschiedlichen Farben

```
class TilePanel extends JPanel
{
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.setColor(Color.RED);
        drawTile(0,0);
        g.setColor(Color.GREEN);
        drawTile(0,1);
    }
}
```

An das TilePanel kann man einen MouseListener hängen, und dort dann sowas wie

```
void mouseClicked(...)
{
    BufferedImage image = new BufferedImage(...so groß wie TilePanel...)
    tilePanel.paintComponent(image.getGraphics());
    int pixel = image.getRGB(event.getX(), event.getY());
    Color c = new Color(pixel);
    
    if (c.equals(Color.RED)) clickedTile(0,0);
    if (c.equals(Color.GREEN)) clickedTile(0,1);
}
```
(wirklich nur GANZ grob - beschreib' ggf. das Problem genauer...)


----------



## mantax (28. Jun 2009)

Hallo Marco
erstmal vielen Dank für deine Antwort!
Ich glaube nicht, dass jedes Tile eine andere Farbe hat, sondern er nur diese Schablone oder "mousemap", wie der Verfasser sie nennt, als Hilfe benutzt um zu ermitteln, ob der Klick innerhalb des Tiles oder nicht erfolgt. Ergibt die Abfrage beispielsweise, dass der Cursor auf einem türkisfarbenen Feld steht, muss das Tile unten drunter angesprochen werden.

Ich verstehe jedoch nicht, wann ich welche Mousemap überprüfen muss und wie sich das bei beliebig vielen Schichten von Tiles verhält. Anscheinend hat bisher noch niemand so etwas implementiert?


----------



## Marco13 (28. Jun 2009)

Hm. Unten schreibt er ja, dass das bei Höhe 2 Probleme geben kann. Man müßte sich mal die TileWalk-Methode ansehen. Aber ehrlich gesagt glaube ich, dass das 1. nicht besonders robust ist was größere Höhenunterschiede angeht, und 2. das ggf. einfacher wäre, wenn man entweder die angesprochene Farb-Map oder gleich eine Methode zur Berechnung des Schnittpunktes zwischen Dreiecken und Picking-Strahl verwenden würde - aber ich kann mich auch täsuchen. Mal schauen, was Quaxli et al. noch dazu sagen


----------



## tuxedo (29. Jun 2009)

Schau dir das mal an:

https://widelands.svn.sourceforge.net/svnroot/widelands/trunk/doc/geometry/index.xhtml

Da müsste das mit der Maus etc. recht gut erklärt sein...

Gruß
Alex


----------



## mantax (30. Jun 2009)

Hmm nach reichlicher Überlegung und noch viel mehr Rumprobieren glaube ich jetzt endlich auf nem ganz guten Weg zu sein:

Dazu sei vorweggenommen, dass ich ein 3-dimensionales Array als Map verwende.

Ich ermittle zuerst mit einer Mousemap das Tile der "Grundebene" auf dem es liegen würde, wenn es nicht überdeckt wäre. Dann überprüfe ich rekursiv von der obersten Ebene nach unten gehend, jeweils die 3 Tiles unter dem bereits ermittelten (links unten, mitte unten, rechts unten) mit einem negativen y-Achsen Verschub entsprechend der Z-Ebene, ob diese leer sind oder nicht und prüfe gegebenenfalls wieder mit einer Mousemap. Sobald ich ein Tile habe, steige ich aus der Funktion aus und habe das entsprechende Tile  

Bin mit der Umsetzung noch nicht ganz fertig, sollte aber funktionieren. Ich hoffe mal, dass meine Methode nicht gerade die rechenintensivste ist, ist nunmal abhängig von der Anzahl der übereinandergelegten Ebenen... wird sich zeigen.

Ich bedanke mich nochmal bei allen die mir geholfen haben! 

PS: Hehe ich glaube das wird keiner beim ersten Lesen verstehen xD Bei Interresse erkläre ichs nochmal ausführlich und vllt mit ein paar Schaubildern


----------



## Quaxli (1. Jul 2009)

Hier gibt's auch noch ein Tutorial: Chapter 13. An Isometric Tile Game

Evtl. findest Du da ja auch noch ein paar Ansätze. :rtfm:


----------



## mantax (3. Jul 2009)

Ich habe es nun endlich geschafft. Vielen dank für eure Hilfe. Wer an der Lösung interessiert ist, kann sich ja nochmal mit meinem voherigen Post beschäftigen oder mich nochmal anschreiben!


----------



## Quaxli (3. Jul 2009)

Könntest Du wenigstens skizzieren wie Du es machst? So wie oben beschrieben oder anders?
Könnte ja sein, daß jemand mal das gleiche Problem hat und der findet dann hier nur eine unvollständige Hilfe... ;(


----------



## mantax (3. Jul 2009)

Okay.. dazu muss ich aber erst mal vorwegnehmen, dass meine Tiles eine Größe von 48x35 Pixel haben und ich ein 3d Array verwende.

1.Schritt: 
Man muss zuerst herausfinden, auf welchem Tile die Maus läge, wenn die Map aus einer Ebene bestünde.
Dazu unterteile ich die Map in Rechtecke von 48x35 und findet heraus in welchem die Maus liegt.

Die Tilekoordinaten errechnen sich nun folgendermaßen
(recx*2/recy)
Da auf diese Weise jedoch nur Tiles mit einer geraden X-Koordinate bestimmen lassen, müssen wir diese Methode überdenken. 
Jetzt kommt diese Mousemap ins Spiel:







Finde heraus auf welcher Farbe die Maus liegt.
Jeder Farbe hat eine Bedeutung.
Grün: TileX-1 / TileY-1
Gelb: TileX+1 / TileY-1
Rot:   TileX+1
Blau:  TileX-1

2.Schritt:
Wir wissen nun welche die Koordinaten für die unterste Ebene dem Mausklick entspricht. Was aber nun wenn Das Tile verdeckt wird? 
Dazu legen wir uns nun eine for-Schleife an die z (Anzahl der Ebenen) bis z > 0 herunterzählt. In jedem durchlauf werden nun 4 Tiles der jeweiligen zEbene per Mousemap abgefragt. Undzwar das Tile direkt unten drunter, linksunten, rechtsunten und oben (in dieser Reihenfolge!), weil diese Tiles die einzigen sind, die unser bereits ermitteltes Tile überdecken können. Lästig ist nur, dass wir für jede Form die ein Tile annehmen kann, eine Mousemap für Unten, Links und Rechts anlegen müssen, das macht man ja aber auch nur einmal.

Mousemaps sind ne klasse sache. Ich habe die ganze Zeit mit Berechnungen rumgespielt, aber es ist meiner Meinung nach fast nicht möglich damit was gescheites (pixelgenaus!) hinzubekommen, da die Tiles eben diese Schrägen haben (2 auf 1 Pixel).

Hier mal zum besseren Verständnis meine Tiles und die dazugehörigen Mousemaps.







So und zu guter letzt und ein bissl Code dazu 


```
public int[] getTile(int[] mouse) {
    // welches rechteck?
    int recx = (int) Math.round(mouse[0] / 48);
    int recy = (int) Math.round(mouse[1] / 23);

    int[] pos = new int[3];

    if ((mask.getRGB(mouse[0] - recx * 48, mouse[1] - recy * 23) == -1)) {
      // weiss
      pos[0] = recx * 2;
      pos[1] = recy;
    } else if ((mask.getRGB(mouse[0] - recx * 48, mouse[1] - recy * 23) == -16711936)) {
      // grün
      pos[0] = recx * 2 - 1;
      pos[1] = recy - 1;
    } else if ((mask.getRGB(mouse[0] - recx * 48, mouse[1] - recy * 23) == -256)) {
      // gelb
      pos[0] = recx * 2 + 1;
      pos[1] = recy - 1;
    } else if ((mask.getRGB(mouse[0] - recx * 48, mouse[1] - recy * 23) == -65536)) {
      // rot
      pos[0] = recx * 2 + 1;
      pos[1] = recy;
    } else if ((mask.getRGB(mouse[0] - recx * 48, mouse[1] - recy * 23) == -16776961)) {
      // blau
      pos[0] = recx * 2 - 1;
      pos[1] = recy;
    }


      pos = overlappingTiles(pos, new int[]{mouse[0] - recx * 48, mouse[1] - recy * 23});
    
    return pos;
  }

  private int[] overlappingTiles(int[] pos, int[] mouse) {

    for (int z = this.map.length - 1; z > 0; z--) {
      // X gerade oder ungerade? -> Tile versetzt oder nicht?
      if (pos[0] % 2 == 0) {
        // U
        if (pos[1] + z < this.map[0].length) {
          if (!this.map[z][pos[1] + z][pos[0]].startsWith("-,-")) {
            if (mask2.getRGB(mouse[0] + 48 * Integer.parseInt(this.map[z][pos[1] + z][pos[0]].split(",")[1]), mouse[1]) == -1) {
              return new int[]{pos[0], pos[1] + z, z};
            }
          }
        }
        // R
        if (pos[1] + z - 1 < this.map[0].length && pos[0] + 1 < this.map[0][0].length) {
          if (!this.map[z][pos[1] + z - 1][pos[0] + 1].startsWith("-,-")) {
            if (mask2.getRGB(mouse[0] + 48 * Integer.parseInt(this.map[z][pos[1] + z - 1][pos[0] + 1].split(",")[1]), mouse[1] + 23) == -1) {
              return new int[]{pos[0] + 1, pos[1] + z - 1, z};
            }
          }
        }
        // L
        if (pos[1] + z - 1 < this.map[0].length && 0 <= pos[0] - 1) {
          if (!this.map[z][pos[1] + z - 1][pos[0] - 1].startsWith("-,-")) {
            if (mask2.getRGB(mouse[0] + 48 * Integer.parseInt(this.map[z][pos[1] + z - 1][pos[0] - 1].split(",")[1]), mouse[1] + 46) == -1) {
              return new int[]{pos[0] - 1, pos[1] + z, z};
            }
          }
        }
        // O
        if (pos[1] + z - 1 < this.map[0].length) {
          if (!this.map[z][pos[1] + z - 1][pos[0]].startsWith("-,-")) {
            return new int[]{pos[0], pos[1] + z - 1, z};
          }
        }
      } else {
        // U
        if (pos[1] + z < this.map[0].length) {
          if (!this.map[z][pos[1] + z][pos[0]].startsWith("-,-")) {
            if (mask2.getRGB(mouse[0] + 48 * Integer.parseInt(this.map[z][pos[1] + z][pos[0]].split(",")[1]), mouse[1]) == -1) {
              return new int[]{pos[0], pos[1] + z, z};
            }
          }
        }
        // R
        if (pos[1] + z < this.map[0].length && pos[0] + 1 < this.map[0][0].length) {
          if (!this.map[z][pos[1] + z][pos[0] + 1].startsWith("-,-")) {
            if (mask2.getRGB(mouse[0] + 48 * Integer.parseInt(this.map[z][pos[1] + z][pos[0] + 1].split(",")[1]), mouse[1] + 23) == -1) {
              return new int[]{pos[0] + 1, pos[1] + z, z};
            }
          }
        }
        // L
        if (pos[1] + z < this.map[0].length && 0 <= pos[0] - 1) {
          if (!this.map[z][pos[1] + z][pos[0] - 1].startsWith("-,-")) {
            if (mask2.getRGB(mouse[0] + 48 * Integer.parseInt(this.map[z][pos[1] + z][pos[0] - 1].split(",")[1]), mouse[1] + 46) == -1) {
              return new int[]{pos[0] - 1, pos[1] + z, z};
            }
          }
        }
        // O
        if (pos[1] + z - 1 < this.map[0].length) {
          if (!this.map[z][pos[1] + z - 1][pos[0]].startsWith("-,-")) {
            return new int[]{pos[0], pos[1] + z - 1, z};
          }
        }
      }
    }
    // nicht überdeckt
    return pos;
  }
```

PS: Ach ja diese Dinger hier "...startsWith("-,-")) {.." sollen keine Smilys sein, sondern überpüfen ob das Tile leer ist


----------



## mantax (14. Jul 2009)

Habe heute zufällig ein Video gefunden, was beim Verständis der Mousemaps helfen sollte 
YouTube - phpciv week 13: Feudal Empires - Mouse Maps and Sprite Sheet


----------

