# Rechtecke drehen



## Max123 (21. Jan 2012)

Hallo alle zusammen,

ich habe eine Frage zum Drehen von Rechtecken...
Im groben habe ich 2 Java Dateien, zum einen die Datei "Board.java" und zum anderen die Datei "Rechtecke.java".
In der Klasse Board male ich mit Hilfe von Graphics2D herum, in der Klasse Recktecke erstelle ich (wie der Name auch schon sagt Rechtecke) die dann darauf in der Klasse Board gezeichnet werden.

Grob gesagt sollen die Rechtecke Autos darstellen. Nun sollen die Autos erst geradelinig fahren, was kein problem ist weil ich einfach nur die x bzw. y Koordinaten ändern muss...  Dann sollen die Autos jedoch auch Kurven oder im Kreis fahren können und hier kommt das Problem ... Damit das ganze halbwegs vernünftig und realitätsnah aussieht versuche ich jetzt schon einge Zeit die Rechtecke zu drehen damit sich sich der Kurve so zu sagen anpassen...
Und nun zu meiner Frage, wie bekomme ich die Rechtecke vernünftigt gedreht ? 

In Board.java erstelle ich sie wie folgt:


```
public Rechtecke(int x, int y) {
        visible = true;
        this.x = x;
        this.y = y;
        rect = new Rectangle2D.Double(x,y,15,22);
}
//[...]
public Rectangle2D getRect(){
        return rect;
}
```

Und in Board.java zeichne ich sie dann wie folgt:


```
public void paint(Graphics g) {
        super.paint(g);
                
            for (int i = 0; i < rechteckeListe.size(); i++) {
                Rechtecke e = (Rechtecke)rechteckeListe.get(i);
                if (e.isVisible()){
                    g2d.setColor(Color.red);
                    g2d.fill(e.getRect());
                    g2d.draw(e.getRect());
                }
                    
           }


        Toolkit.getDefaultToolkit().sync();
        g.dispose();
    }
```


Nur wie drehe ich die Rechtecke dann ? Am liebsten würde ich sie natürlich in der Recktecke Klasse drehen und dann in der Boardklasse nur aufzeichen ich weiss aber nicht ob dies möglich ist....

Ich habe mit Hilfe der Suchfunktion und Google schon herrausgefunden das ich wohl eine "AffineTransform" brauche um das zu bewerkstelligen .... Aber wie setze ich diese korrekt ein sodass ich nur das Rechteck drehe und dieses letzendlich eine schöne Kurve lasse fahren ? 

Für Hilfe wäre ich sehr dankbar...

Mit freundlichen Grüßen,
                             Max


EDIT: Nun gut wie ich mein Rechteck drehe habe ich herausgefunden:

```
g2d.setColor(Color.red);
                    g2d.fill(a.getRect());
                    g2d.draw(a.getRect());
                    g2d.rotate(Math.toRadians(a.getDeg()),a.getRect().getCenterX(),     a.getRect().getCenterY());
                    g2d.fill(a.getRect());
```
Problem daran, nun liegen 2 Rechtecke übereinander, das eine gedrehte, und das andere alte Rechtecke, wie lösche ich das alte Rechteck ?


----------



## xehpuk (21. Jan 2012)

Hey!



Max123 hat gesagt.:


> Problem daran, nun liegen 2 Rechtecke übereinander, das eine gedrehte, und das andere alte Rechtecke, wie lösche ich das alte Rechteck ?


Die Frage verstehe ich nicht ganz. In deinem Codeausschnitt zeichnest du das Rechteck, rotierst das Graphics-Objekt und zeichnest das Rechteck dann nochmal. Deswegen ist es dann natürlich doppelt drin.


----------



## Max123 (21. Jan 2012)

Ja dachte ich eigentlich auch -- wenn ich aber das 2. g2d.fill(...) weglasse dreht sich gar nichts ....

Ich habe mich jetzt erstmal mit dieser Lösung zufrieden gegeben: 


```
g2d.setColor(Color.red);
                    g2d.fill(a.getRect());
                    g2d.draw(a.getRect());
                    g2d.clearRect((int)a.getRect().getX(),(int)a.getRect().getY() ,16 ,23 );
                    g2d.rotate(Math.toRadians(a.getDeg()),a.getRect().getCenterX(), a.getRect().getCenterY());
                    
                    g2d.fill(a.getRect());
```

Ist zwar alles andere als schön aber funktioniert erstmal 


Nunja ich stehe nur nun gerade vor dem Problem wie ich die autos im kreis fahren lasse...

Habe dazu auch eine kurze Skizze im Anang hinterlassen...

Ich möchte das das rote Rechteck auf der schwarzen Kreisbahn sich bewegt ...

Dazu muss ich es ja immer drehen lassen.... Ich denke wie stark ich es drehen lasse hängt vom Radius des Kreises und der Geschwindigkeit ab, aber wie zwinge ich das auf eine möglichst gute Kreisbahn ? :/


----------



## Marco13 (22. Jan 2012)

Mehr Kontext wäre gut. An sich ist das beschriebene nicht soo schwer, ... ein bißchen aufwändiger wird, es wenn man es "gut" machen will (d.h. wenn beliebige Shapes einem beliebigen Path folgen können sollen - aber das ist ja das mindeste :smoke:  ). Relevant wären vielleicht noch so fragen wie um wie viele Autos es geht, und ob die gedrehten Rechtecke noch für Kollisionserkennung oder z.B. zum anklicken gebraucht werden, oder ob man sie NUR zeichnen will...


----------



## Max123 (22. Jan 2012)

Generell geht es darum das die Autos in einen Kreisverkehr fahren sollen und da eine beliebige Ausfahrt rausfahren sollen ...
Die Fahrt der Autos besteht also aus der Gerade zum Kreisverkehr, der Fahrt im Kreisverkehr, und der Gerade nach der Ausfahrt aus dem Kreisverkehr...

Der 1. und letzte Punkte sind ja nicht das Problem da man schön bei geradlinigen Bewegungen bleibt, aber beim fahren in Kreis müssen sich die Rechtecke auch irgendwie mitdrehen danach es halbwegs realistisch aussieht :/

Die Anzahl der Autos ist beliebig ich möchte da immer mehr reinschicken ... Kollisionen soll es jedoch nicht geben und angeklickt werden müssen sie auch nicht mehr ... Sprich wirklich nur zeichnen und durch den Kreisverkehr fahren...

Was ich brauche ist irgendwie eine Position zum Beispiel MaxY bzw. MaxX des Shapes und dann soll es noch halbwegsschön aussehen aber mehr müssen die eigentlich nicht können  Trotzdem scheitert es bei mir gerade an der schönen "Kreisvefahrung" :/


----------



## Marco13 (23. Jan 2012)

Hm... wenn man das vernünftig machen will, ist's doch etwas frickeliger, als ich zuerst dachte. Hab' gerade mal was gebastelt, aber das werd' ich noch ein bißchen "aufräumen", bevor ich es poste...

Das interessante ist allgemein, wie du den Pfad, auf dem sich die Autos bewegen, angeben willst.
Und speziell: Wie willst du die _Geschwindigkeit_ der Autos vorgeben?


----------



## Max123 (23. Jan 2012)

Okay....

Zur Geschwindigkeit habe ich mir folgendes überlegt:

Ich lege für jedes Auto in der Auto-Klasse eine Variable "speed" fest...

In der Paint-Funktion baue ich dann folgendes ein: 


```
int zahler = 0;

//[...]

void paint(){

zaehler++;

      for([Auto-Arraylist durchgehen....]){

            if(zahler modulo (Konstante / auto.getSpeed()) == 0){
            // lasse auto 
            }

      }

}
```

So dürften sich zumindest die Geschwindigkeiten der Autos getrennt voneinander regeln lassen.... Welchen Wert die Konstante annehmen muss damit es realistisch wird, darüber habe ich mir noch nicht allzu viele Gedanken gemacht, dass müsste ich dann nochmal schnell schauen (hat dann denke ich irgendetwas damit zu tuen wie oft die Paint Funktion aufgerufen wird  )  

Ja der Pfad für die Autos.... Damit stellt sich wahrscheinlich das größte Problem :/ Vielleicht irgendwie über eine Kreisfunktion oder so ? :/


----------



## Marco13 (23. Jan 2012)

Kreisfunktion...? ???:L Wie auch immer :bahnhof: Abgesehen von der "Hauptklasse", dem "PathFollower", ist das weitgehend kommentarfrei, aber vielleicht hilft's ja trotzdem (weiß ja keiner, was du vorhast). Ja, der rote gewinnt 


```
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class PathFollowerTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                JFrame f = new JFrame();
                final AnimationPaintPanel paintPanel = new AnimationPaintPanel();
                f.getContentPane().add(paintPanel);
                
                Animation animation = new Animation(paintPanel);
                animation.start();
                
                f.setSize(600,600);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setVisible(true);
            }
        });
    }
}

class Animation
{
    static Shape createPath()
    {
        Shape s0 = new Line2D.Float(50, 0, 150, 0);
        Shape s1 = new Arc2D.Float(100,0,100,100, 90, -180, Arc2D.OPEN);
        Path2D.Float part = new Path2D.Float();
        part.moveTo(150, 100);
        part.curveTo(125, 100, 125, 100, 125, 125);
        part.curveTo(125, 150, 125, 150, 100, 150);
        part.curveTo( 75, 150,  75, 150,  75, 125);
        part.curveTo( 75, 100,  75, 100,  50, 100);
        Shape s2 = part;
        Shape s3 = new Arc2D.Float(0,0,100,100, -90, -180, Arc2D.OPEN);
        Path2D p = new Path2D.Float();
        p.append(s0, true);
        p.append(s1, true);
        p.append(s2, true);
        p.append(s3, true);
        AffineTransform at = AffineTransform.getTranslateInstance(100,100);
        at.scale(2.0, 2.0);
        return at.createTransformedShape(p);
    }
    
    private List<AnimatedObject> animatedObjects;
    private AnimationPaintPanel animationPaintPanel;
    private PathFollower pathFollower;
    private double time = 0;
    private double timeStep = 0.005;
    
    public Animation(AnimationPaintPanel animationPaintPanel)
    {
        this.animationPaintPanel = animationPaintPanel;
        
        Shape path = createPath();
        pathFollower = new PathFollower(path);
        animationPaintPanel.addPaintable(new PaintableShape(path));
        
        animatedObjects = new ArrayList<AnimatedObject>();
        createObject(Color.RED, 0.39);
        createObject(Color.GREEN, 0.23);
        createObject(Color.BLUE, 0.13);
        createObject(Color.YELLOW, 0.29);
        createObject(Color.CYAN, 0.19);
        createObject(Color.MAGENTA, 0.31);
    }
    
    private void createObject(Color color, double speed)
    {
        AnimatedObject animatedObject = new AnimatedObject(color, speed);
        animatedObjects.add(animatedObject);
        animationPaintPanel.addPaintable(animatedObject);
    }
    
    public void start()
    {
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                doRun();
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
    
    private void doRun()
    {
        while (true)
        {
            for (AnimatedObject animatedObject : animatedObjects)
            {
                time += timeStep;
                animatedObject.setTime(time);
                double alpha = animatedObject.getAlpha();
                AffineTransform at = pathFollower.computeTransform(alpha);
                animatedObject.applyTransform(at);
            }
            animationPaintPanel.repaint();
            try
            {
                Thread.sleep(30);
            }
            catch (InterruptedException e)
            {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }
    
        
}


interface Paintable 
{
    void paintObject(Graphics2D g);
}

interface Transformable
{
    void applyTransform(AffineTransform at);
}

class PaintableShape implements Paintable
{
    private final Shape shape;
    
    public PaintableShape(Shape shape)
    {
        this.shape = shape;
    }

    @Override
    public void paintObject(Graphics2D g)
    {
        g.setColor(Color.BLACK);
        g.draw(shape);
    }
    
}

class AnimatedObject implements Paintable, Transformable
{
    private final Shape initialShape;
    private Shape currentShape;
    private double alpha;
    private Color color;
    private double speed;
    
    public AnimatedObject(Color color, double speed)
    {
        this.color = color;
        this.speed = speed;
        this.initialShape = new Rectangle2D.Float(-20, -10, 40, 20);
        this.currentShape = initialShape;
    }
    
    
    public double getAlpha()
    {
        return alpha;
    }
    
    public void setTime(double time)
    {
        this.alpha = (time * speed) % 1.0;
    }
    
    @Override
    public void paintObject(Graphics2D g)
    {
        g.setColor(color);
        g.fill(currentShape);
    }

    @Override
    public void applyTransform(AffineTransform at)
    {
        currentShape = at.createTransformedShape(initialShape);
    }
}


class AnimationPaintPanel extends JPanel
{
    private final List<Paintable> paintables = new ArrayList<Paintable>();
    
    public void addPaintable(Paintable paintable)
    {
        paintables.add(paintable);
    }
    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        
        for (Paintable paintable : paintables)
        {
            paintable.paintObject(g);
        }
    }
}



 /**
  * Utility class that may return the position and orientation of
  * an object that moves along a path.    
  */
class PathFollower
{
    /**
     * The length of the path
     */
    private double length = 0;
    
    /**
     * An approximation of the path as a list of points
     */
    private final List<Point2D> points;

    /**
     * The map from length values to indices in the point list
     */
    private final TreeMap<Double, Integer> map;
    
    /**
     * Creates a new PathFollower for the given path
     * 
     * @param path The path
     * @throws IllegalArgumentException If the maximum length
     * of all segments of the path is smaller than a small
     * epsilon value. 
     */
    public PathFollower(Shape path)
    {
        map = new TreeMap<Double, Integer>();
        points = new ArrayList<Point2D>();

        double coords[] = new double[6];
        PathIterator pi = path.getPathIterator(null, 0.1);
        Point2D previousPoint = new Point2D.Double();
        Point2D currentPoint = new Point2D.Double();
        double maxSegmentLength = 0;
        while (!pi.isDone())
        {
            switch (pi.currentSegment(coords))
            {
                case PathIterator.SEG_MOVETO:
                    currentPoint = new Point2D.Double(coords[0], coords[1]);
                    map.put(length, points.size());
                    points.add(currentPoint);
                    previousPoint.setLocation(coords[0], coords[1]);
                    break;

                case PathIterator.SEG_LINETO:
                    currentPoint = new Point2D.Double(coords[0], coords[1]);
                    double segmentLength =previousPoint.distance(currentPoint);
                    maxSegmentLength = Math.max(maxSegmentLength, segmentLength);
                    length += segmentLength; 
                    map.put(length, points.size());
                    points.add(currentPoint);
                    previousPoint.setLocation(coords[0], coords[1]);
                    break;
            }
            pi.next();
        }
        if (maxSegmentLength < 1e-4)
        {
            throw new IllegalArgumentException(
                "Path length is too small: "+length);
        }
        
    }
    
    /**
     * Return the length of the path
     * 
     * @return The length of the path
     */
    public double getLength()
    {
        return length;
    }
    
    /**
     * Compute the position and direction for the specified <i>relative</i>
     * position on the path. The alpha value will be clamped to be in [0,1].
     * 
     * @param alpha The relative position on the path
     * @param position Will store the position
     * @param direction Will store the direction
     */
    public void computeOrientation(double alpha, Point2D position, Point2D direction)
    {
        alpha = Math.min(1, Math.max(0, alpha));
        
        // Compute the map entries that define the relevant path segment
        double relativeLength = alpha * length;
        Entry<Double, Integer> fEntry = map.floorEntry(relativeLength);
        Entry<Double, Integer> cEntry = map.ceilingEntry(relativeLength);

        // Obtain the points that specify the relevant path segment
        int i0 = fEntry.getValue();
        int i1 = cEntry.getValue();
        Point2D p0 = points.get(i0);
        Point2D p1 = points.get(i1);

        // Compute the length of the current segment, and make sure
        // that it is a non-null value, by stepping forward to the
        // next segment if necessary
        double segmentLength = p1.distance(p0);
        while (segmentLength < 1e-6)
        {
            i1 = (i1 + 1) % points.size();
            p1 = points.get(i1);
            segmentLength = p1.distance(p0);
        }
        
        // Compute the direction 
        double dx = p1.getX() - p0.getX();
        double dy = p1.getY() - p0.getY();
        direction.setLocation(dx, dy);

        // Compute the relative location in the current segment
        double relativeLengthRemainder = relativeLength - fEntry.getKey();
        double remainderAlpha = relativeLengthRemainder / segmentLength;

        // Compute the interpolated position
        double x = p0.getX() + remainderAlpha * dx;
        double y = p0.getY() + remainderAlpha * dy;
        position.setLocation(x, y);
    }
    
    /**
     * Computes an AffineTransform that describes the orientation
     * (position and rotation) at the given <i>relative</i> location
     * on the path. The alpha value will be clamped to be in [0,1].
     * 
     * @param alpha The relative location
     * @return The transform describing the orientation
     */
    public AffineTransform computeTransform(double alpha)
    {
        Point2D p = new Point2D.Double();
        Point2D d = new Point2D.Double();
        computeOrientation(alpha, p, d);
        double angle = Math.atan2(d.getY(), d.getX());
        AffineTransform result = new AffineTransform();
        result.translate(p.getX(), p.getY());
        result.rotate(angle);
        return result;
    }
    
}
```


----------



## Max123 (23. Jan 2012)

Okay gut, das sieht ja alles ganz gut aus  Ich habe es gerade erstmal durchlaufen lassen sieht aber gut aus ... Ich versuche mich später mal durchzukämpfen und zu verstehen, ich hoffe ich kann es dann noch weiter an mein Ziel anpassen.... Ich möchte ja die Autos in dem Kreisverkehr fahren lassen und wenn der Kreisverkehr voll ist müssen die Autos an der Einfahrt anhalten um nicht in die anderne Autos kaputt zu fahren ... Ich hoffe ich bekomme das angepasst dann passt das ja perfekt  

Generell geht es einfach um eine Simulation des Kreisverkehrs  Danke aber auf jeden Fall !


----------



## Marco13 (23. Jan 2012)

Hmja... die eigentliche Animation steckt ja in diesen Testklassen, da kann man machen, was man will... 
Ob die PathFollower-Klasse auch noch angepasst werden muss, muss man sich überlegen: Wenn man einen "schönen" Übergang zum Kreisverkehr haben will, ist die Straße ja keine einzelne Linie mehr, sondern müßte sich so teilen

```
____...
           /
__________/
          |
__________|
          \
           \
            \____...
```
und so, und man müßte die Abbieg-Punkte genau bestimmen... Könnte noch ein bißchen tricky werden...


----------



## Max123 (25. Jan 2012)

Soo ich habe das in den letzten Tagen schonmal etwas angepasst .... Ich habe dafür jetzt unterschiedliche Pfade erstellt .... Kann das Endergebniss vielleicht später dann mal posten. Ich habe aber noch eine Frage dazu: Undzwar sind eine Pfade ja nicht abgeschlossen... Einfachstes Beispiel wäre das einer einfachen Linie.... Dabei fahren die Autos dann entlang der Linien und wenn sie am Ende sind starten sie am Anfang wieder neu.... 
Wie kann ich das ändern ? Ich möchte das das Objekt wenn es am Ende des Pfades ist gelöscht wird, wie schaffe ich das ? Das muss wohl irgendwie in der PathFollower-Klasse bewerkstelligt werden denke ich das ist aber leider die einzige Klasse durch die ich bisher noch nicht so ganz durchsteigen konnte :/


----------



## Marco13 (26. Jan 2012)

Max123 hat gesagt.:


> Wie kann ich das ändern ? Ich möchte das das Objekt wenn es am Ende des Pfades ist gelöscht wird, wie schaffe ich das ? Das muss wohl irgendwie in der PathFollower-Klasse bewerkstelligt werden



Nööö nicht so direkt. Man müßte jetzt wissen, wo und wie du die Autos an sich verwaltest. Pragmatisch: Das Auto muss dann gelöscht werden, wenn es am Ende des Pfades ist. 
(PathFollower-Klasse hat ja nur eine sehr "überschaubare" Funktion: Man gibt ihr einen Wert zwischen 0 und 1, und sie gibt einem eine passende AffineTransform - oder anders formuliert: Der _Zustand_ des Autos in bezug auf seine Position auf dem Pfad ist nur durch den Wert zwischen 0 und 1 beschrieben - und wenn der Wert 1.0 ist, ist es am Ende - ob man da irgendwo so eine PathFollower-Klasse verwendet oder nicht hat damit nichts zu tun  )


----------



## Max123 (26. Jan 2012)

Okay. Habe dann das Problem wie folgt gelöst:


```
if(animatedObject.containsP(animatedObject.getEndPoint())){
                    animationPaintPanel.removePaintable(animatedObjects.get(i));
                    animatedObjects.remove(animatedObject);                    
                }
```
Das wird in der Animation Klasse immer wieder für jedes Auto überprüft.

Jedem Auto wird ein Endpunkt mitübergeben wenn es erstellt wird, sobald es seinen Endpunkt erreicht wird es zerstört, bzw. nicht mehr gezeichnet und die Referenz zu dem Objekt gelöscht. Funktion ganz gut. 

Bin froh das ich jetzt soweit gekommen bin. Die Autos fahren schön in den Kreisverkehr nehmen eine zufällige Ausfahrt und wenn sie ihr Ende erreicht haben werden sie "zerstört". Dafür ein großes Danke für die Hilfe  

Ich muss den Autos jetzt nur noch die Verkehrsregeln bei bringen und dann ist das Modell ja fertig  Mal sehen wie gut ich mich als Fahrschullehrer anstelle :b


----------



## Marco13 (26. Jan 2012)

Ich hatte "befürchtet", dass du von dem Test-Code (AnimationPaintPanel & Co) einiges in "dein" Proramm übernimmst. Da mußt du ggf. ein bißchen aufpassen: Wenn das Entfernen des Autos von der "run"-Methode aus (d.h. in einem anderen Thread) gemacht wird, kann es beim Zeichnen zu einer "ConcurrentModificationException" kommen - weil die Liste ggf. vom Animationsthread verändert wird, während der Zeichen-Thread drüberläuft. Eine "Silver Bullet"-Lösung dafür gibt's nicht, das muss man von Fall zu Fall entscheiden. Am einfachsten und sichersten wäre vermutlich, die Liste im Panel zu einer
private final List<Paintable> paintables = new *CopyOnWrite*ArrayList<Paintable>();
zu machen. Das ist zwar nicht besonders effizient wenn es um _viele_ Objekte geht, aber in diesem Fall wäre das wohl noch OK. Es gibt viele Threads dazu, kürzlich z.B. http://www.java-forum.org/awt-swing...-currentmodificationexception.html#post852420


----------



## Max123 (26. Jan 2012)

Ja, ich hatte den Fehler auch schon, nachdem ich aber 
	
	
	
	





```
for (AnimatedObject animatedObject : animatedObjects)
```
 zu 
	
	
	
	





```
//[...]
for (int i=0;i<animatedObjects.length();i++)
{ 
AnimatedObject animatedobject = animatedObjects.get(i);
//[...] 
}
//[...]
```
geändert habe, kam der Fehler (komischerweise) erstmal nicht mehr...


----------



## Marco13 (26. Jan 2012)

Dadurch wird nur die Wahrscheinlichkeit verringert, dass er kommt (bzw. dort kommt seltener (aber manchmal unter bestimmten Bedingungen eben vielleicht doch noch) eine ArrayIndexOutOfBoundsException). Verwende lieber eine CopyOnWriteArrayList und die for-Schleife so, wie sie vorher war.


----------

