Wuerfel
Pyramide
Oktaeder
Tetraeder

myJava.rwp

3D-Animationen

Beispiele für 3D-Animationen mit Quellcode und javadoc

Rotation um die x-Achse

Rotation um die y-Achse

Rotation um die z-Achse

3D-Animationen heißt, dass alle Objekte über 3 Ebenen, nämlich der x-, der y- und der z-Achse gezeichnet werden. Die z-Achse definiert, dass entferntere Seiten der Objekte kleiner erscheinen. So erscheint z.B. die hintere Seite eines Würfels kleiner als die dem Betrachter zugewandte. 3D bedeutet also nicht die maßstabsgetreue Wiedergabe einer “technischen” Zeichnung sondern  die perspektivische Darstellung von Objekten.

Der Kernpunkt der korrekten perspektivischen Darstellung von 3D-Objekten ist die Projektion der Objekte, also die Umrechnung der x-, y- und z-Koordinaten in die 2D-Koordinaten des Bildschirms. Die Projektion geschieht hier ausschließlich durch Matrixmultiplikation. Um dieses auf einfache Weise für beliebig viele Objekte durchzuführen, habe ich einen Datentyp Matrix (Quellcode) programmiert. Die darzustellenden Objekte sind alle vom Datentyp Polyeder (Quellcode). Jeder Polyeder besteht wiederum aus mehreren Instanzen des Datentyps   NewPolygon ( Quellcode), eine Unterklasse von java.awt.Polygon.

In den Klassen, die nun Objekte beschreiben und über die paint()-Methode visualisieren(beispielsweise einen Würfel), finden wir jetzt jeweils eine Instanz der Klasse Polyeder und eine des Typs Matrix. Im Zusammenwirken dieser beiden Instanzen können jetzt problemlos die drei Grundbewegungsarten errechnet werden, nämlich die Translation, die Rotation und die Skalierung.

Die Dokumentation zu den Klassen findest Du unter javadoc

Quelltext von Matrix.java    

/*

 * Matrix.java

 *

 * Created on 30. September 2004, 13:55

 */

 

/**

 * {@link NewPolygon}

 * {@link Polyeder}

 *

 * Diese Klasse stellt eine 4 * 4 main_matrix bereit, mit deren Hilfe

 * Rotationen, Skalierung und Translation von 3D-Objecten berechnet werden

 * koennen. Das Besondere hieran ist, das zunaechst alle Bewegungen ueber

 * die entsprechenden Methoden uebergeben werden und dann die main_matrix

 * alle Berechnungen in einem Vorgang uebernimmt. Die Berechnung uebergebener

 * Punkte wird durch die Methode update durchgefuehrt.

 *

 * @author  Rüdiger Witte-Petersen

 */

public class Matrix {

   

    /**Die Hauptmatrix*/

    double[][] main_matrix = new double[4][4];

  

    /** Neue Instanz der Klasse Matrix uebergibt eine Einheitsmatrix*/

    public Matrix() {

        clear();

    }

   

    /** initialisiert ein Matrixobjekt als Einheitsmatrix. Wird im Konstruktor

     *  aufgerufen und dient beim Verwenden dieser Klasse dazu, die gespei-

     *  cherten Multiplikationen einer main_matrix zu loeschen.

     */

    public void clear() {

        main_matrix[0][0] = 1;

        main_matrix[0][1] = 0;

        main_matrix[0][2] = 0;

        main_matrix[0][3] = 0;

        main_matrix[1][0] = 0;

        main_matrix[1][1] = 1;

        main_matrix[1][2] = 0;

        main_matrix[1][3] = 0;

        main_matrix[2][0] = 0;

        main_matrix[2][1] = 0;

        main_matrix[2][2] = 1;

        main_matrix[2][3] = 0;

        main_matrix[3][0] = 0;

        main_matrix[3][1] = 0;

        main_matrix[3][2] = 0;

        main_matrix[3][3] = 1;

    }

   

    /** Methode uebergibt durch Matrizenmultiplikation die erhaltenen

     * Parameter xs, ys und zs an die main_matrix. Die main_matrix kann

     * die Skalierung zusammen mit allen anderen Bewegungen in einer

     * Berechnung durchfuehren. Eingabe 0 nicht moeglich, Eingabe 1 ergibt

     * fuer die jeweilige Richtung keine Aenderung, > 0 bis < 1 verkleinert,

     * > 1 vergroessert.

     * @param xs scale Wert x

     * @param ys scale Wert y

     * @param zs scale Wert z

     */

    public void scale(double xs, double ys, double zs) {

        double[][] sm = {{ xs, 0, 0, 0 },

                         { 0, ys, 0, 0 },

                         { 0, 0, zs, 0 },

                         { 0, 0, 0, 1 }};

        multiplicate(sm);

    }

   

    /** Methode uebergibt durch Matrizenmultiplikation die erhaltenen

     * Parameter xt, yt und zt an die main_matrix. Die main_matrix

     * kann die Translation zusammen mit allen anderen Bewegungen

     * in einer Berechnung ausfuehren.

     * @param xt Translationswert x

     * @param yt Translationswert y

     * @param zt Translationswert z

     */

    public void translate(double xt, double yt, double zt) {

        double[][] tm = {{ 1, 0, 0, 0 },

                         { 0, 1, 0, 0 },

                         { 0, 0, 1, 0 },

                         { xt, yt, zt, 1 }};

        multiplicate(tm);

    }

   

    /**

     * Methode uebergibt durch Matrizenmultiplikationen die erhaltenen

     * Parameter xa, ya und za an die main_matrix. Dadurch wird die

     * main_matrix in die Lage versetzt, die uebergebenen Rotationen

     * incl. aller anderen Bewegungen in einer Berechnung weiterzugeben.

     * Wenn um einige Achsen keine Rotation gewuenscht wird, 0 uebergeben.

     * @param xa Rotationswert x

     * @param ya Rotationswert y

     * @param za Rotationswert z

     */

    public void rotate(double xa, double ya, double za) {

        //Matrix Rotation um x-Achse

        double[][] rx = {{ 1, 0, 0, 0 },

                         { 0, Math.cos(xa), Math.sin(xa), 0 },

                         { 0, -Math.sin(xa), Math.cos(xa), 0 },

                         { 0, 0, 0, 1 }};

        //Matrix Rotation um y-Achse                

        double[][] ry = {{ Math.cos(ya), 0, -Math.sin(ya), 0 },

                         { 0, 1, 0, 0 },

                         { Math.sin(ya), 0, Math.cos(ya), 0 },

                         { 0, 0, 0, 1 }};

        //Matrix Rotation um z-Achse                

        double[][] rz = {{ Math.cos(za), Math.sin(za), 0, 0 },

                         { -Math.sin(za), Math.cos(za), 0, 0 },

                         { 0, 0, 1, 0 },

                         { 0, 0, 0, 1 }};

       

        //Rotationsmatrizen multiplizieren

        multiplicate(rx);

        multiplicate(ry);

        multiplicate(rz);

    }

 

    /**

     * Methode ermoeglicht das Multiplizieren von zwei 4 x 4 Matrizen.

     * Die Klassenmatrize main_matrix kann mit entsprechenden Matrizen

     * der Methoden rotate, translate und scale multipliziert werden und

     * somit die entsprechenden Bewegungen gemeinsam umsetzen.

     * Achtung: Die main_matrix steht bei dieser Multiplikation vorn und

     * Matrizen mit den Bewegungsvariablen an zweiter Stelle.

     */

    private void multiplicate(double[][] n_m) {

        //temporaere Matrix

        double[][] temp = new double[4][4];

       

        //Doppelschleife errechnet die neue Matrix, speichert in temp

        for(int row = 0; row < 4; row++) {

            for(int column = 0; column < 4; column++) {

                temp[row][column] = main_matrix[row][0] * n_m[0][column] +

                                    main_matrix[row][1] * n_m[1][column] +

                                    main_matrix[row][2] * n_m[2][column] +

                                    main_matrix[row][3] * n_m[3][column];

            }

        }

       

        //Kopieren von temp in die main_matrix

        for(int row = 0; row < 4; row++) {

            for(int column = 0; column < 4; column++) {

                main_matrix[row][column] = temp[row][column];

            }

        }

    }

   

    /**

     *

     * Methode multipliziert ein Array von uebergebenen Punkten P( mit den

     * Koordinaten x, y, z und 1) mit der main_matrix und berechnet ein Array

     * mit den neuen Punkten P'. Dieses wird dann returniert.

     * Achtung: Das uebergebene Array steht bei der Multiplikation vorn und

     * main_matrix an zweiter Stelle. Matrizenmultiplikation ist nicht

     * kommutativ.

     * @param koord uebergebene Matrix, die multipliziert werden soll

     * @return berechnete Matrix wird zurueckgegeben

     */

    public double[][] update(double[][] koord) {

        double[][] temp = new double[4][koord[0].length];

        if(main_matrix[0].length != koord.length) {

            System.out.println("Matrizen können nicht miteinander" +

                                            " multipliziert werden");

        } else {

            for(int i = 0; i < 4; i++) {

                for(int j = 0; j < koord[0].length; j++) {

                    for(int k = 0; k < 4; k++) {

                        temp[i][j] = temp[i][j] +

                                    (koord[k][j] * main_matrix[k][i]);

                    }

                }

            }

        }

        for(int i = 0; i < 4; i++) {

            for(int j = 0; j < koord[0].length; j++) {

                koord[i][j] = temp[i][j];

            }

        }

        return koord;

    }

   

    /**

     * Ermoeglicht das Anzeigen der main_matrix zu testzwecken

     */

    public void print() {

        for(int i = 0; i < 4; i++) {

            for(int j = 0; j < 4; j++) {

                System.out.print(main_matrix[i][j] + "  ");

            }

            System.out.println("");

        }

    }

}

Seitenanfang

Quelltext von Polyeder.java

import javax.swing.*;

import java.awt.*;

/*

 * Polyeder.java

 *

 * Created on 13. Oktober 2004, 13:52

 */

 

/**

 * {@link Matrix}

 * {@link NewPolygon}

 *

 * Klasse Thing erzeugt einen Polyeder aus den uebergebenen Parametern

 * koord[][] und poly_param[][]. Die erzeugten Polygone werden im Array

 * p[] gespeichert und koennen ueber eine Instanz von Thing ausgelesen

 * werden.

 *

 * @author  Rüdiger Witte-Petersen

 */

public class Polyeder {

   

    /** Array fuer die Aufnahme der Polygone */

    public NewPolygon[] p;

   

    /** Array fuer die Speicherung der Mittelpunktkoordinaten */

    public int[] wpos = new int[3];

   

    /** Zugriffskonstanten fuer die uebergebene Matrix */

    private final int wx = 0;

    private final int wy = 1;

    private final int wz = 2;

   

    /** Korrekturparameter fuer die z-Koordinate, siehe sichtbar() */

    double zKorr = 0;

   

    /** Array fuer die Speicherung der uebergebenen Koordinaten */

    private double[][] vertex;

   

    /** Array fuer die Speicherung der uebergebenen Polygon Parameter */

    private int[][] poly_param;

   

    /** Variablen fuer Positionierung des Polyeders */

    private int width, height;

      

    /** Konstruktor uebergibt die Parameter an die hiesigen Variablen und

     *  ruft die Methode buildPolygon auf

     * @param koord 3D Koordinaten

     * @param poly_param Zugriffsparameter fuer die Polygone

     * @param width Breite Panel

     * @param height Hoehe Panel

     * @param zKorr Korrekturparameter fuer z

     */

    public Polyeder(double[][] koord, int[][] poly_param, int width,

                        int height, double zKorr) {

        this.poly_param = poly_param;

        vertex = koord;

        this.width = width;

        this.height = height;

        this.zKorr = zKorr;

        p = new NewPolygon[poly_param.length];

        center();

        buildPolygon();     

    }

   

    /** Methode liefert true, wenn das uebergebene Polygon im sichtbaren

     *  Bereich ist und false, wenn es im fuer den Betrachter nicht sichtbaren

     *  Bereich ist. Dieses geschieht durch Errechnung des Kreuzproduktes

     *  der beiden Vektoren vom Punkt koord3D[0] aus.

     *  Wenn man ein 3D Object direkt von vorn betrachtet, sind die Seiten-

     *  elemente (z.B. eines Wuerfels) nicht im Winkel von 90 Grad nach hinten

     *  geneigt sondern etwas zum nullPunkt hin. Dadurch bleiben die Seiten-

     *  polygone zu lange sichtbar. Um diesen Effekt zu korrigieren, wird der

     *  Vergleichswert von t fuer jedes Object mit uebergeben (zKorr). Das muss

     *  individuell geschehen in Abhaengigkeit der Translation von z

     * @param poly uebergebenes Polygon

     * @return boolescher Wert

     */

    public boolean sichtbar(NewPolygon poly) {

        //Errechnung letzter Punkt des Polygons

        int lastPoint = poly.koord3D[0].length - 1;

        //Kreuzprodukt der beiden Vektoren bilden

        double t = (poly.koord3D[wx][lastPoint] - poly.koord3D[wx][0]) *

                    (poly.koord3D[wy][1] - poly.koord3D[wy][0]) -

                    (poly.koord3D[wy][lastPoint] - poly.koord3D[wy][0]) *

                    (poly.koord3D[wx][1] - poly.koord3D[wx][0]);

        if(t > zKorr)   //siehe Erklaerung oben fuer 6

            return true;

        else

            return false;

    }

   

    /** Methode erzeugt aus den uebergebenen Parametern Polygone und

     *  speichert diese im Array p ab.

     */

    public void buildPolygon() {

        // i-Schleife laeuft fuer die Anzahl der zu erstellenden Polygone

        for(int i = 0; i < this.poly_param.length; i++) {

            //Variable temp speichert nacheinander das jeweils zu erstellenden

            //Array. 4 Koordinaten x Anzahl der Polygonecken.

            double[][] temp = new double[4][this.poly_param[i].length-3];

            //j-Schleife laeuft fuer die Anzahl der Polygonecken

            for(int j = 0; j < this.poly_param[i].length-3; j++) {

                //k-Schleife laeuft fuer die 4 zu uebergebenden Parameter

                for(int k= 0; k < 4; k++) {

                    //temp wird gefuellt

                    temp[k][j] = vertex[k][this.poly_param[i][j]];

                }              

            }

            //i Polygone werden produziert und in p gespeichert

            p[i] = new NewPolygon(temp, width, height);

        }

    }

   

    /** Methode multipliziert koord mit der uebergebenen Matrix (main_matrix)

     * und ermittelt so die neuen Koordinaten fuer die Positionierung dieses

     * Polyeders. Achtung: koord  steht bei der Multiplikation an erster

     * Stelle und main_matrix an zweiter. Matrizenmultiplikation ist nicht

     * kommutativ. Desweiteren wird der Mittelpunkt wpos aktualisiert.

     * @param m uebergebene Matrix

     */

    public void update(Matrix m) {

        //Mittelpunkt aktualisieren

        wpos[wx] = (int)m.main_matrix[3][0];

        wpos[wy] = (int)m.main_matrix[3][1];

        wpos[wz] = (int)m.main_matrix[3][2];

       

        double[][] temp = new double[4][vertex[0].length];

        if(m.main_matrix[0].length != vertex.length) {

            System.out.println("Matrizen können nicht miteinander" +

                                            " multipliziert werden");

        } else {

            for(int i = 0; i < 4; i++) {

                for(int j = 0; j < vertex[0].length; j++) {

                    for(int k = 0; k < 4; k++) {

                        temp[i][j] = temp[i][j] +

                                    (vertex[k][j] * m.main_matrix[k][i]);

                    }

                }

            }

        }

        //temp an koord uebergeben

        for(int i = 0; i < 4; i++) {

            for(int j = 0; j < vertex[0].length; j++) {

                vertex[i][j] = temp[i][j];

            }

        }

        //Polygonkoordinaten neu berechnen

        buildPolygon();

    }  

   

    /** Methode errechnet den Mittelpunkt des Polyeders */

    private void center() {

        //lokale Variablen fuer die groessten bzw. kleinsten vertex-Werte

        double xs, xg, ys, yg, zs, zg;

        //Variablen initialisieren

        xs = xg = vertex[wx][0];

        ys = yg = vertex[wy][0];

        zs = zg = vertex[wz][0];

       

        //groesste bzw. kleinste Werte suchen

        for(int i = 0; i < vertex[0].length; i++) {

            if(vertex[wx][i] < xs)

                xs = vertex[wx][i];

            if(vertex[wx][i] > xg)

                xg = vertex[wx][i];

            if(vertex[wy][i] < ys)

                ys = vertex[wy][i];

            if(vertex[wy][i] > yg)

                yg = vertex[wy][i];

            if(vertex[wz][i] < zs)

                zs = vertex[wz][i];

            if(vertex[wz][i] > zg)

                zg = vertex[wz][i];

        }

        //Mittelpunktwerte uebergeben

        wpos[wx] = (int)(xs + xg) / 2;

        wpos[wy] = (int)(ys + yg) / 2;

        wpos[wz] = (int)(zs + zg) / 2;

    } 

}

Seitenanfang

Quelltext von NewPolygon.java

/*

 * NewPolygon.java

 *

 * Created on 13. Oktober 2004, 14:12

 */

 

/**

 * {@link Matrix}

 * {@link Polyeder}

 *

 * Diese Klasse erzeugt eine Instanz der Klasse Polygon. Als Parameter werden

 * die 3D-Koordinaten des Polygons sowie die Groesse des Ausgabefensters

 * erwartet.

 *

 * @author  Rüdiger Witte-Petersen

 */

public class NewPolygon extends java.awt.Polygon {

   

    /** Die Projectionskonstante */

    private final int projection_const = 100;

   

    /** Zugriffskonstanten fuer die uebergebene Matrix */

    private final int wx = 0;

    private final int wy = 1;

    private final int wz = 2;

   

    /** Breite des Panels */

    int width;

    /** Hoehe des Panels */

    int height;

   

    /** Array fuer die Aufnahme der 2D-Koordinaten */

    private int[][] xyWerte;

   

    /** Array fuer die Aufnahme der 3D-Koordinaten */

    public double[][] koord3D;

      

    /** Konstruktor erwartet als Parameter die 3D-Koordinaten fuer ein

     *  beliebiges Polygon einer Matrix[4][x] sowie Hoehe und Breite des

     *  Panels, in dem das Polygon gezeigt werden soll.

     *  Der Konstruktor erzeugt dann eine Instanz der Superklasse ohne

     *  Parameter, weil diese in 2D-Form noch errechnet werden sollen. Dies

     *  geschieht durch den Aufruf der Methode koord2D. Anschließend wird

     *  die erzeugte Instanz der Klasse Polygon mit Hilfe der addPoint-

     *  Methode aktualisiert.

     * @param koord 3D Koordinaten

     * @param width Breite des Applets

     * @param height Hoehe des Applets

     */

    public NewPolygon(double[][] koord, int width, int height) {

        super();

        this.width = width;

        this.height = height;

        koord3D = koord;

        koord2D(koord3D);      

    }

   

    /** Umrechnung der 3D-Koordinaten in 2D-Koordinaten nach den Formeln

     *  sx = wx / wz * projection_const + width / 2 und

     *  sy = wy / wz * (-projection_const) + height / 2

     * @param koord3D 3D Koordinaten

     */ 

    void koord2D(double[][] koord3D) {

        reset();

        xyWerte = new int[koord3D[0].length][2];

        for(int i = 0; i < koord3D[0].length; i++) {

            xyWerte[i][0] = (int)(koord3D[wx][i] / koord3D[wz][i] *

                                    projection_const + width / 2);

            xyWerte[i][1] = (int)(koord3D[wy][i] / koord3D[wz][i] *

                                    (-projection_const) + height / 2);

        }

        for(int i = 0; i < koord3D[0].length; i++) {

            addPoint(xyWerte[i][0], xyWerte[i][1]);

        }

    }

}

Seitenanfang