Selbstständiges Fahren - 2. Fahrzeug und Strecke

Bevor wir uns unserem eigentlichen Vorhaben, einem selbstfahrenden Fahrzeug, zuwenden können, müssen wir die Grundlage schaffen. In diesem Post soll das Fahrzeug, die Strecke und ein kleines Testprogramm um die Fahrtüchtigkeit erstellt werden.

Für die grafische Darstellung benötigen wir Punkte die in einem Koordinatensystem gedreht und verschoben werden. Dazu wird eine Klasse Punkt erstellt, die über die notwendigen Methoden verfügt. Ein Punkt läßt sich verschieben (translate) und um den Nullpunkt drehen (rotate0). Die Koordinaten als Integerwert werden über die Methoden intX und intY geliefert.

package car;

public class Point {

    public double x=0;
    public double y=0;
   
    public Point(Point p) {
        this.x = p.x;
        this.y = p.y;
    }
   
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
   
    public void setXY(double x, double y) {
        this.x = x;
        this.y = y;
    }
   
    public void translate(Point p) {
        this.x += p.x;
        this.y += p.y;
    }
   
    public void translate(double x, double y) {
        this.x += x;
        this.y += y;
    }
   
    public void translate(int x, int y) {
        this.x += x;
        this.y += y;
    }
   
    public void translateMinus(Point p) {
        this.x -= p.x;
        this.y -= p.y;
    }
   
    public void rotate0(int grad) {
        double d = Math.toRadians(grad);
        double cosinus = Math.cos(d);
        double sinus = Math.sin(d);
        double xn = cosinus * x - sinus * y;
        double yn = sinus * x + cosinus * y;
        x = xn;
        y = yn;
    }
   
    public int intX() {
        return (int) Math.round(x);
    }
   
    public int intY() {
        return (int) Math.round(y);
    }
}


Die zweite Klasse ist das Fahrzeug - der Rennwagen. Das Fahrzeug ist sehr einfach gehalten, ein Rechteck. Es ist daher nur notwendig die vier Ecken zu speichern.
Wie nimmt der Wagen die Informationen vom Untergrund auf? Die 15 Sensoren liefern eine 1, wenn der Untergrund unter einem Sensor schwarz ist und eine 0, wenn er weiß ist. Diese Nullen und Einsen werden hintereinander als  binäre Zahl geschrieben und ergeben einen Zahlenwert. (0b000000000000000) zwischen 0 und 65535.

package car;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.util.Arrays;

/**
* Es wird ein Wagen mit seiner Position und Sicht definiert.
*
* Um die Berechnung möglichst einfach zu halten besteht die
* Sicht aus Linen mit einen bestimmten Abstand es wird nur der
* Punkt ermittelt.
*
* @author Thomas Aldag
*/

public class Racer {

   // Der Untergrund - die Strecke
   private Ground ground;

   // zur unterscheidung der Fahrzeuge 
   private int nr;
   // Richtung des Fahrzeugs in Grad
   protected int degree=0;
   // lenkung in grad -5 = links 5 = rechts
   protected int direction=0;

   // Geschwindigkeit in Pixel
   protected int speed=2;
   // Sehwert enthält die ermittelte Information der Sensoren als Zahlenwert
   protected int sensorValue=0;
   // Zurückgelegte Strecke (aufaddieren der Speedwerte)
   private int distance=0;

   // der Wagen ist außerhalb der Strecke
   protected boolean stop=false;
   // der Wagen merkt sich, ob er über die Ziellinie gefahren ist
   protected boolean win=false;

   // Die Startpunkte des Objektes (Fahrzeugs). Es werden die vier Eckpunkte
   // benötigt. Der Mittelpunkt des Fahrzeug befindet sich im Punkt 0/0. Das
   // Fahrzeug hat eine Breite von 10 und eine Länge von 20 Pixel.
   private int[][] objekt = {
      {  10,  5 },  // vorne links
      {  10, -5 },  // vorne rechts
      { -10, -5 },  // hinten rechts
      { -10,  5 }   // hinten links
    };

   // Speichern der aktuellen Position
   private Point[] pob;
   /**
    * Punkte die Informationen liefern. Jeder Punkt liefert den Farbwert

    * (0 oder 1) zurück (schwarz oder weiß). Die Positon der Sensoren bezieht
    * sich auf ein Fahrzeug, dessen Mittelpunkt im Punkt 0/0 liegt. Siehe objekt.
    */

   private int[][] sensor = {
      { 20,  12 }, { 30,  12 }, { 40,  12 }, // links außen
      { 20,   6 }, { 30,   6 }, { 40,   6 }, // links
      { 20,   0 }, { 30,   0 }, { 40,   0 }, // mitte
      { 20,  -6 }, { 30,  -6 }, { 40,  -6 }, // rechts
      { 20, -12 }, { 30, -12 }, { 40, -12 }  // rechts außen
   };

   // Speichern der aktuellen Position der Sensoren
   private Point[] psensor;

   public Racer(int n) {
      init();
      nr=n;
   }
   /**

    * Das Fahrzeug wird angelegt und mit der Position im Punkt 0/0 vorbelegt.
    */
   private void init() {
      pob = new Point[objekt.length];
      for (int i=0; i < pob.length; i++)

         pob[i] = new Point(objekt[i][0],objekt[i][1]);
      psensor = new Point[sensor.length];
      for (int i=0; i < psensor.length; i++)

         psensor[i] = new Point(sensor[i][0],sensor[i][1]);
    }
   /**
    * stop zurücksetzen und die zurückgelegte Strecke auf 0 setzen.
    */

   public void initStrecke() {
      stop = false;
      distance = 0;
   }
   /**
    * Den Untergrund für das Fahrzeug setzen, damit das Fahrzeug Informationen

    * vom Untergrund erfragen kann.
    *
    * @param g Der Untergrund
    */

   public void setGround(Ground g) {
      this.ground = g;
   }
   /**
    * Geschwindigkeit in Pixel setzen.
    *
    * @param s Geschwindigkeit
    */

   public void setSpeed(int s) {
      speed = s;
   }
   /**
    * Das Fahrzeug auf eine Position setzen. Der Punkt links, oben im

    * Koordinatensstem ist der Punkt  0,0. Außerdem wird die Ausrichtung 
    * des Fahrzeugs gesetzt.
    * Zuerst wird das Fahrzeug auf den Ursprung (0/0) gesetzt. Anschließend
    * wird das Fahrzeug in die Richtung (degree) gedreht und zum Schluß
    * auf die Position (x/y) verschoben.
    *
    * @param x
    * @param y
    * @param degree
    */

   public void setPosition(int x, int y, int degree) {
      stop = false;
      win = false;
      sensorValue= 1;
      this.degree = degree;
      for (int i=0; i < pob.length; i++) {
         pob[i].setXY(objekt[i][0], objekt[i][1]);
         pob[i].rotate0(degree);
         pob[i].translate(x, y);
      }
      for (int i=0; i < psensor.length; i++) {
         psensor[i].setXY(sensor[i][0], sensor[i][1]);
         psensor[i].rotate0(degree);
         psensor[i].translate(x,y);
      }
   }
   /**
    * Hat das Fahrzeug die Ziellinie erreicht?
    *
    * @return true, wenn die Sensoren die Ziellinie ermittelt haben
    */

   public boolean isWin() {
      return this.win;
   }
   /**
    * Wurde das Fahrzeug angehalten?
    *
    * Das Fahrzeug wird angehalten, wenn es mit einer Ecke auf den weißen

    * Untergrund stößt.
    *
    * @return true, wenn das Fahrzeug an den weißen Untergrund stößt.
    */

   public boolean isStop() {
      return this.stop;
    }
   /**
    * Hauptroutine die das Fahrzeug bewegt. Das Fahrzeug wird gedreht und

    * anschließend nach vorne bewegt.
    */

   public void drive() {
      rotate(direction);
      goStraightOn();
    }
   /**
    * Das Fahrzeug beweegt sich speed-Punkte in die Richtung (grad)
    */

   private void goStraightOn() {
      Point pd;
      pd = new Point(speed,0);
      pd.rotate0(degree);
      for (int i=0; i < pob.length; i++) pob[i].translate(pd);
      for (int i=0; i < psensor.length; i++) psensor[i].translate(pd);
      distance += speed;
   }
   /**
    * Das Fahrzeug dreht sich auf eines der beiden hinteren Rädern
    *
    * @param grad der Grad um
    */

   private void rotate(int grad) {
      Point pt;
      if (grad == 0) return;
      // rechts herum
      if (grad > 0) pt = new Point(pob[3]); // rechte Ecke bleibt fest
      // links herum
      else pt = new Point(pob[2]);
// rechte Ecke bleibt fest
      for (int i=0; i < pob.length; i++) {
         pob[i].translateMinus(pt);
         pob[i].rotate0(grad);
         pob[i].translate(pt);
      }
      for (int i=0; i < psensor.length; i++) {
         psensor[i].translateMinus(pt);
         psensor[i].rotate0(grad);
         psensor[i].translate(pt);
      }
   }
   /**
    * Ermittelt das Sichtfeld und den Seh-Wert.
    *
    * Gestoppte Fahrzeuge liefern immer den Wert 0
    *
    * Der Sehwert ergibt sich aus den über die Sensoren ermittelten Werte.
    * Jeder Sensor der einen schwarzen Untergrund "sieht"
    * liefert den Wert 2 hoch Position. Z.B. liefert der
    * Sensor oben links den Wert 2^2 = 4. Alle Werte werden addiert. Das Ergebnis

    * wird in dem Attribut sensorValue als int-Wert gespeichert.
    *
    * 02 01 00
    * 05 04 03    +--------+
    * 08 07 06 <- |Fahrzeug|
    * 11 10 09    +--------+
    * 14 13 12
    *
    * @return ermittelter Sensorwert
    */

   public int getLook() {
      int value = 0;
      int wert = 1;
      if (stop) {
         sensorValue= 0;
         return 0;
      }
      for (int i=0; i < psensor.length; i++) {
         if (ground.isBlue(psensor[i].intX(),psensor[i].intY())) win=true;
         if (ground.isBlack(psensor[i].intX(),psensor[i].intY())) value += wert;
         wert *= 2;
      }
      sensorValue = value;
      return sehwert;
   }
 

   public int getDistance() {
      return distance;
   }
 

   public int getSensorValue() {
      return sensorValue; 

   }
   /**
    * Nach links um 5 Grad drehen.
    */

   public void left() {
      direction = -5;
      degree += direction;
      if (degree < 0) degree += 360;
   }
   /**
    * Nach rechts um 5 Grad drehen.
    */

   public void right() {
      direction = 5;
      degree += direction;
      if (degree > 360) degree -= 360;
   }
   /**
    * Das Fahrzeug zeichnen.
    *
    * @param g
    */

   public void paintIt(Graphics g) {
      // Das Fahrzeug zeichnen in rot
      Polygon p = new Polygon();
      g.setColor(new Color(200, 0, 0));
      for (int i = 0; i < pob.length; i++) {
         p.addPoint(pob[i].intX(), pob[i].intY());
         if (!ground.isBlack(pob[i].intX(), pob[i].intY())) stop = true;
      }
      g.drawPolygon(p);
      // Die Sensoren Zeichnen in grau
      g.setColor(new Color(150, 150, 150));
      for (int i = 0; i < psensor.length; i++) 

         g.drawOval(psensor[i].intX()-3,psensor[i].intY()-3, 5, 5);
   }

}


Das Fahrzeug benötigt eine Strecke auf der es sich bewegen kann. Dabei handelt es sich um ein einfaches Bild, auf dem die Strecke eingezeichnet wurde. Die Klasse Ground lädt das Bild und zeichnet es. Neben dem Bild kann ein Array von Fahrzeugen angegeben werden. Diese Fahrzeuge werden ebenfalls gezeichnet.
Mit einer Methode (isBlack) kann ermittelt werden, ob das Pixel an der Stelle x/y schwarz ist. Die Stelle muss nicht "ganz" schwarz sein. Es reicht aus, wenn sie dunkel ist.

package car;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

/**
 * Die Rennstrecke auf der das Fahrzeug fahren soll.
 *
 * @author Thomas Aldag
 *
 */
public class Ground extends Canvas {
   
    private static final long serialVersionUID = 1L;
   
    private BufferedImage img;
    private Racer[] raceCar;
 

    public Ground() throws IOException {
        this.readStrecke("Strecke0.png");
    }

    public Dimension getPreferredSize() {
        return new Dimension(img.getWidth(this), img.getHeight(this));
    }  
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }
    /**
     * Strecke aus der Grafikdatei lesen.
     *
     * @param streckenname
     * @throws IOException
     */

    public void readStrecke(String streckenname) throws IOException {
        img = ImageIO.read(new File("recourse/"+streckenname));
    }
    /**
     * Ermittelt, ob der Pixel an der Position x,y schwarz ist.
     *
     * Der Pixelwert wert mit 0xFFFFFF maskiert um die RGB-Werte zu ermitteln.
     *
     * @param x
     * @param y
     * @return true, wenn der Pixel an der Stelle x,y schwarz ist.
     */

    public boolean isBlack(int x, int y) {
        if (x < 0 || y < 0 ) return false;
        return (img.getRGB(x, y) & 0xFFFFFF) < 0x808080;
    }
    /**
     * Ermittelt, ob der Pixel an der Position x,y blau ist.
     *
     * Der Pixelwert wert mit 0xFFFFFF maskiert um die RGB-Werte zu ermitteln.
     *
     * @param x
     * @param y
     * @return true, wenn der Pixel an der Stelle x,y blau ist.
     */
   
    public boolean isBlue(int x, int y) {
        if (x < 0 || y < 0 ) return false;
        int p = img.getRGB(x, y) & 0xFFFFFF;
        return p <= 0x0000FF && p >=0x0000AA;
    }
    /**
     * Setzt die Liste der Fahrzeuge
     *
     * @param r
     */

    public void setRacer(Racer[] r) {
        this.raceCar = r;
    }
    /**
     * Strecke und Fahrzeuge zeichnen
     */

    public void paint(Graphics g) {
        g.clearRect(0, 0, img.getWidth(), img.getHeight());
        if (img != null) g.drawImage(img, 0, 0, this);
        if (this.raceCar != null) {
            for (int i=0; i<this.raceCar.length; i++) this.raceCar[i].paintIt(g);
        }
    }

}



Schließlich kommt die Basisklasse für die Hauptprogramme. Es können mehrere Fahrzeuge zeitgleich fahren. Daher gibt es eine Liste von Fahrzeugen. Diese Fahrzeuge werden bewegt und weiterentwickelt. Die Weiterentwicklung findet in den abgeleiteten Klassen statt.

package car;

import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * Schleife um das Fahrzeug zu bewegen.
 *
 * @author Thomas Aldag
 *
 */
 

public class Cars extends Frame {

   private static final long serialVersionUID = 1L;
   // Die Strecke
   protected Ground ground;
   // Eine Liste von Fahrzeugen
   protected Racer[] raceCar;
   // Der Index des besten Fahrzeugs
   protected int carindex;
   // Ein Fahrzeug ist im Ziel angekommen
   protected boolean finished=false;
   /**
    * Konstruktor. Das Fenster wird angelegt und die Fahrzeuge initialisiert.
    *
    * @param title Der Fenstertitel
    */

   public Cars(String title) {
      super(title);
        addWindowListener(
          new WindowAdapter() {
            public void windowClosing( WindowEvent ev ) {
              dispose();
              System.exit( 0 ); } } );
        try {
            screen();
        } catch (IOException e) {
            e.printStackTrace();
        }
        initRacer();
   }
   /**
    * Fenster erstellen
    *
    * @throws IOException
    */

   private void screen() throws IOException {
       setVisible(true);
       setSize(600,600);
       ground = new Ground();
       add (ground);
       pack();       
   }
   /**
    * Die folgenden beiden Methoden müssen überlagert werden.

    *
    * initRacer initialisiert die Fahrzeuge
    * development entwicklet die Fahrzeuge weiter
    */

   public void initRacer() {}
   public void development() {}
   /**
    * Schleife, bis ein Fahrzeug das Ziel erreicht hat.
    *
    * @throws IOException
    */

   public void go(String streckenname) throws IOException {
      int runde=0;
       ground.readStrecke(streckenname);
       finished=false;
       while (!finished) {
          System.out.println("Runde="+(++runde)

                            +" max.Strecke="+raceCar[carindex].getStrecke());
          aRound();
          carindex = bestCar();
          development();
       }
   }
   /**
    * Die Fahrzeuge werden auf die Startposition gesetzt.
    */

   private void startPositionRacer() {
       for (int i=0; i < raceCar.length; i++) {
           raceCar[i].setPosition(290, 224,180);
           raceCar[i].initStrecke();
       }       
   }
   /**
    * Warteschleife
    */

   protected void waiting() {
       try {
           TimeUnit.MILLISECONDS.sleep(50);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }       
   }
   /**
    * Eine Runde bis zur nächsten Weiterentwicklung durchlaufen
    */
 

   private void aRound() {
       boolean weiter=true;
       startPositionRacer();
       while (weiter) {
           waiting();
           weiter = false;
           for (int j=0; j < this.raceCar.length; j++) {
               raceCar[j].drive();
               if (!weiter) weiter=!raceCar[j].isStop();
               if (raceCar[j].isWin()) {
                   finished = true;
                   return;
               }
           }
           ground.repaint();
       }       
   }
   /**
    * Heraussuchen des besten Wagen.
    *
    * D.h. den Wagen mit der längsten Strecke.
    *
    * @return Index des Wagens
    */

    private int bestCar() {
       int dist=0;
       int ci=0;
       System.out.print("Best Car ");
       for (int i=0; i < raceCar.length; i++) {
           System.out.print(" " + raceCar[i].getDistance()

                           +" ("+raceCar[i].getSpeed()+")");
           if (dist <= raceCar[i].getDistance()) {
               ci = i;
               dist = this.raceCar[i].getDistance();
           }
       }
       System.out.println(" Best "+ci);
       return ci;
   }
}


Für einen runden Abschluss brauchen wir noch unser erstes Hauptprogramm, dass die Fahrtüchtigkeit des Fahrzeugs testet. Hierbei handelt es sich um eine Sub-Klasse von Cars.

package car;

import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * Das Fahrzeug wird getestet.
 *
 * Siehe Testmethoden
 *
 * @author Thomas Aldag
 *
 */

public class CarsTest extends Cars {

   public CarsTest() {
       super("Cars Test");
   }
   /**
    * Fahrzeuge erzeugen und zur Rennstrecke hinzufügen.
    */

   public void initRacer() {
       raceCar = new Racer[1];
       raceCar[0] =new Racer(1);
       this.raceCar[0].setGround(this.ground);
       ground.setRacer(raceCar);       
   }

   /**
    * Geradeaus nach rechts fahren
    */

   public void test1() {
       raceCar[0].setPosition(200, 100, 0);
       raceCar[0].setSpeed(6);
       for (int i=0; i < 20; i++) {
           waiting();
           System.out.println("runde="+i);
           raceCar[0].drive();
           ground.repaint();
       }
   }
   /**
    * Geradeaus nach links fahren
    */

   public void test2() {
       raceCar[0].setPosition(200, 100, 180);
       raceCar[0].setSpeed(6);
       for (int i=0; i < 20; i++) {
           waiting();
           System.out.println("runde="+i);
           raceCar[0].drive();
           ground.repaint();
       }       
   }
   /**
    * Im Kreis nach links fahren
    */

   public void test3() {
       raceCar[0].setPosition(200, 200, 0);
       raceCar[0].setSpeed(4);
       for (int i=0; i < 60; i++) {
           waiting();
           raceCar[0].left();
           System.out.println("runde="+i);
           raceCar[0].drive();
           ground.repaint();
       }       
   }
   /**
    * Im Kreis nach rechts fahren
    */

   public void test4() {
       raceCar[0].setPosition(200, 200, 0);
       raceCar[0].setSpeed(4);
       for (int i=0; i < 60; i++) {
           waiting();
           raceCar[0].right();
           System.out.println("runde="+i);
           raceCar[0].drive();
           ground.repaint();
       }       
   }
   /**
    * Hauptprogramm
    *
    * @param args
    */

   public static void main(String[] args) {
       CarsTest rt = new CarsTest();
       rt.test1();
       rt.test2();
       rt.test3();
       rt.test4();
   }
}

Kommentare

Beliebte Posts