# [JAVA] Java 3D und Hierarchisches Modell



## truesoul (19. Apr 2010)

Hallo zusammen, 

ich bin auf der Suche nach Tutorials, Nachschlagewerke (pdf),Links oder evtl. Tipps wo mein anliegen erklärt wird.

Also mein Problem bzw Wissenslücke ist das Hierarchische Modell (Baumstrucktur).
Ich habe ein Beispiel aufgezeichnet um zu verdeutlichen was ich genau meine.







Also bei Bild 1 ist die StandartPosition dieses Objektes "soll eine Arm darstellen".
So nun bewege ich den Oberarm nach Oben dabei soll der Unterarm in der gleichen Position bleiben nur die höhe ändert sich.
Oberarm und Unterarm sind zwei Objekte (Cylinder z.B) .

Oder dieses Problem: 






Nicht gewollt:






Aber so:






Also in diesem fall sind es 9 Objekte , zwei seitliche Boxen und 7 Cylinder.
Sobald ich die seitlichen Boxen bewege müssen die "Rollen" sich logisch mitbewegen.
Ich vermute das ich in beiden Fällen mit Sinus und Cosinus und Co. arbeiten muss.

Nur sind Beispiele sehr rar zu finden wo mir aufgezeigt wird wie diese berechnung durchzogen wird.
In diesem Fall habe ich auf Code verzichtet da ich ja nach Erklärung suche 

Herzlichen dank schonmal im vorraus.

Mfg


----------



## Marco13 (19. Apr 2010)

Die Bilder helfen da nicht so viel. Willst du auf eine Kinematische Kette ? Wikipedia hinaus?


----------



## truesoul (19. Apr 2010)

Ja genau sowas suche ich.
Ich mache zur Zeit eine 3D darstellung eines Roboters , so in etwa wie auf der Seite die du mir gegeben hast. Hast du vll. welche Informationquellen dafür ( pdf's , sonstige Links usw ) ?
Werde mich diesbezüglich natürlich direkt selber auf die Suche machen  

Danke für deine Mühe.


----------



## Marco13 (19. Apr 2010)

Naja, allgemein kenn' ich das auch nur aus der Robotikvorlesung  Wenn das schwarze der Oberarm und das rote ein senkrecht nach unten hängender Unterarm sein soll, muss der Rote Zylinder eben genau "umgekehrt" zum Schwarzen gedreht werden: Am Anfang hat der rote zum schwarzen 90°, wenn man den Schwarzen um 30° nach oben dreht, hat der rote zum Schwarzen nur noch (90-30)=60°... Je allgemeiner, desto komplizierter, natürlich


----------



## truesoul (20. Apr 2010)

Ich verzweifel ein wenig an dem Teil von Java3D.
Will erstmal einfach anfangen das ich nur ein Objekt ein Bewegungsablauf hat aber leider Scheiter ich kläglich daran.
So ich habe mal ein BeispielCode: 


```
public BranchGroup erstelleSzenenGraph(){

        BranchGroup RootBG = new BranchGroup();
        

        //Halterung der Transrollen
        TransformGroup ViewTG1 = new TransformGroup();
        Transform3D T3D_Box1 = new Transform3D();

        T3D_Box1.setTranslation(new Vector3f(0f, 0f, -15f));
        T3D_Box1.setRotation(new AxisAngle4f(0f, 0f, 1f, (float)Math.toRadians(90)));
        //ViewTG1.setTransform(T3D_Box1);

        
        ViewTG1.addChild(new Box(0.3f, 0.1f, 1, new Appearance()));
        ViewTG1.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        ViewTG1.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        PositionInterpolator posi = new PositionInterpolator(new Alpha(-1,8000), ViewTG1, T3D_Box1,-0.5f,0.5f);
        posi.setTarget(ViewTG1);
        posi.setSchedulingBounds(new BoundingSphere());


        RootBG.addChild(posi);
        RootBG.addChild(ViewTG1); 
        RootBG.addChild(new Shape3D(erstelleAchseX()));
        RootBG.addChild(new Shape3D(erstelleAchseY()));
        RootBG.addChild(erstelleDirectLight());
        RootBG.addChild(erstelleAmbientLight());

        RootBG.compile();
        return RootBG;
    }
```

So jetzt bewegt sich ja die Box von Y = -0.5 bis Y = 0.5


Mein erstes Problem ist , wie bekomme ich die Box weiter nach Hinten sprich -Z? 

Und jetzt zum eigentlichen Problem der Umsetzung.
Die Box soll von unten nach oben laufen ( was sie ja tut ) und wenn die Box oben angekommen ist soll der vordere teil der Box nach links schwenken.
Der hintere teil soll auf der Position bleiben.
Wie kann man das Umsetzen ?


----------



## Marco13 (20. Apr 2010)

Hm. Also, wenn das ein Arm werden soll, und meine bisherigen Annahmen richtig waren, dann wird dort nirgendwo eine Position verändert. Zumindest nicht direkt. Das ganze ist dann ja eine Kinematische Kette. D.h. der Unterarm fängt da an, wo der Oberarm aufhört. Alles andere wäre aua aua  

Ein Mensch/Roboter besteht so gesehen aus mehreren kinematischen Ketten. Die haben alle einen gemeinsamen Wurzelknoten, nämlich den Rumpf. Dort hängt an einer bestimmten Position untrennbar der Oberarm dran, und dort wiederum am Ende untrennbar der Unterarm (und daran die Hand, und daran die Finger....)

Ein bißchen ASCII-Art: Die 'o's sind Gelenke

```
|||||
  |ö ö|
  | u |
   \ /
 o--+--o---o
 |  |      |
 |  |      |
 o  |   
 |  |   
 |  |   
    |
    o
   / \
  |   |
  |   |
  o   o
  |   |
  |   |
--o   o--
```

Jetzt dreht man den Oberarm ein stück hoch - der Unterarm dreht sich erstmal "steif" mit:  

```
|||||
  |ö ö|   /o\
  | u |  /   \ 
   \ /  /     \
 o--+--o    
 |  |       
 |  |       
 o  |   
 |  |   
 |  |   
    |
    o
   / \
  |   |
  |   |
  o   o
  |   |
  |   |
--o   o--
```
 
Damit der Unterarm gerade bleibt, muss er in die entgegengesetzte Richtung gedreht werden:


```
|||||
  |ö ö|   /o 
  | u |  / |   
   \ /  /  |   
 o--+--o   |
 |  |       
 |  |       
 o  |   
 |  |   
 |  |   
    |
    o
   / \
  |   |
  |   |
  o   o
  |   |
  |   |
--o   o--
```

Die Position des Unterarms wird aber nicht direkt verändert. Sie verändert sich nur dadurch, dass er eben am Oberarm hängt.


Wenn man sich zu Kinematischen Ketten und Manipulatoren was durchliest, sieht das erstmal hochmatematisch aus ... ist es auch  aber vieles davon wird schon dadurch erledigt, dass man ja einen Szenegraphen hat, der ziemlich genau so einer kinematischen Kette entspricht.

Man hätte einen Wurzelknoten. In deinem Fall wäre das... ja, die Schulter sozusagen. Dort hängt ein Drehgelenk dran - also ein TransformNode, bei dem man NUR die Rotation ändert. Daran hängt der Oberarm. Das wäre der Zylinder, und ein TransformNode mit einer festen(!) Transformation, nämlich gerade einer Translation die der Länge des Oberarms/Zylinders entspricht. Dort hängt ein weiterer TransformNode dran, nämlich der Ellbogen, der auch NUR Rotiert werden kann (genaugenommen auch nur um eine Achse, aber sei das mal egal). Dort hängt dann der Unterarm dran - wieder ein Zylinder und ein TransformNode, dessen Translation die der Länge des Oberarms/Zylinders entspricht.


Im Pseudocode wäre der Arm dann etwa so aufgebaut

```
BranchGroup schulter = new BranchGroup();

Transform3D schulterDrehung = ...;
TransformGroup schultergelenk = new TransformGroup(schulterDrehung);
schulter.addChild(schultergelenk);

Cylinder oberarmObjekt = new Cylinder(0.5); // 0.5m lang
TransformGroup oberarm = new TransformGroup(0.5); // 0.5m lang
schultergelenk.addChild(oberarmObjekt);
schultergelenk.addChild(oberarm);

Transform3D ellbogenDrehung = ...;
TransformGroup ellbogenGelenk = new TransformGroup(ellbogenDrehung);
oberarm.addChild(ellbogenGelenk);

Cylinder unterarmObjekt = new Cylinder(0.4); // 0.4m lang
TransformGroup unterarm = new TransformGroup(0.4); // 0.4m lang
ellbogenGelenk.addChild(unterarmObjekt);
ellbogenGelenk.addChild(unterarm);


// Arm wie in der ASCII-Art anheben:
schulterDrehung.setRotationX(45); // 45 grad nach oben
ellbogenDrehung.setRotationX(-45); // 45 grad nach unten
```

So in etwa...


----------



## Guest2 (20. Apr 2010)

Moin,

nur der Vollständigkeit halber: Kinematische Ketten sind gut solange die einzelnen Glieder vollkommen starr sind und es an den Übergängen zwischen den Gliedern keine unschönen "Beugedellen" gibt. Für einen Roboter also vermutlich vollkommen ok.

Soll aus dem Roboter später mal was organisches werden, wird es an den Gelenkstellen zu hässlichen Artefakten kommen, da sich die Oberfläche nicht mehr so einfach über die Knochenstruktur ziehen lässt.

Solltest Du das irgendwann mal wollen, schon mal zum bookmarken: Skinning Mesh Animations
(imho besticht der Algorithmus durch seine pure Eleganz )

Gruß,
Fancy


----------



## Marco13 (20. Apr 2010)

Bei einem Satz wie "Ich vermute das ich in beiden Fällen mit Sinus und Cosinus und Co. arbeiten muss." gehe ich davon aus, dass fortschrittliche Skinning-Techniken nicht das nächst(liegend)e Ziel sind


----------



## Guest2 (21. Apr 2010)

öhm,... joa da könntest Du wohl auch wieder recht haben. 

(Kann es ja auch als Blick in die Zukunft sehen )

Gruß,
Fancy


----------



## truesoul (21. Apr 2010)

Erstmal Danke Marco für deine hilfe und deine Mühe.
Also die "Baumstruktur" hab ich schon fast aufm Kasten 
Ich habe mal dein Pseudocode umgesetzt um selbst dafür ein gefühl zu bekommen.

Der Unterarm soll sozusagen erstmal starr sich mitbewegen.


```
package test;
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import com.sun.j3d.utils.applet.MainFrame;

import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.universe.*;
import javax.vecmath.*;
import javax.media.j3d.*;

public class Pseudo extends Applet{
    SimpleUniverse u = null;
    private static final int Radius = 90; // Soll die Standart stellung sein

    Canvas3D canvas;
    BoundingSphere BigBounds = new BoundingSphere(new Point3d(), 1000);
    public Pseudo() {
    }

     public void destroy(){
        u.cleanup();
    }
    public void init(){

        setLayout(new BorderLayout());
        GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();

        canvas = new Canvas3D(config);

        add("Center",canvas);

        u = new SimpleUniverse(canvas);
        u.getViewingPlatform().setNominalViewingTransform();
        u.addBranchGraph(erstelleSzenenGraph());

    }

    public BranchGroup erstelleSzenenGraph(){
       BranchGroup schulter = new BranchGroup();


       schulter.addChild(erstelleAmbientLight());
       schulter.addChild(erstelleDirectLight());
       
       //Schulter bis oberarm
       int aendern = -15; // +15 für bewegung nach oben usw
       Transform3D schulterdrehung = new Transform3D();
       schulterdrehung.setTranslation(new Vector3f(0f, 0f, 0f));
       schulterdrehung.setRotation(new AxisAngle4f(0f, 0f, 1f, (float)Math.toRadians(Radius+aendern)));
       TransformGroup schultergelenk = new TransformGroup(schulterdrehung);
       schulter.addChild(schultergelenk);

       Cylinder oberarmobjekt = new Cylinder(0.05f, 0.6f, Sphere.GENERATE_NORMALS, erstelleAppearance(true));
       TransformGroup oberarm = new TransformGroup();
       oberarm.setTransform(schulterdrehung);
       schultergelenk.addChild(oberarmobjekt);
       schultergelenk.addChild(oberarm);


       Transform3D ellBogendrehung = new Transform3D();
       // set Translation muss bei jeder Arm Bewegen angepasst werden ??
       int unterarm_aendern = 0; // um den Unterarm zu bewegen ...  darf natürlich nicht über 90 sein
       ellBogendrehung.setTranslation(new Vector3f(-0.3f, 0.2f, 0f));
       ellBogendrehung.setRotation(new AxisAngle4f(0f, 0f, 1f, (float)Math.toRadians(-aendern+unterarm_aendern)));
       TransformGroup ellBogenGelenk = new TransformGroup(ellBogendrehung);
       oberarm.addChild(ellBogenGelenk);

       Cylinder unterarmobjekt = new Cylinder(0.05f, 0.4f, Sphere.GENERATE_NORMALS,erstelleAppearance(false));
       TransformGroup unterarm = new TransformGroup(schulterdrehung);
       ellBogenGelenk.addChild(unterarmobjekt);
       ellBogenGelenk.addChild(unterarm);


       schulter.compile();
       return schulter;
    }

    public static void main(String[] args) {
        new MainFrame(new Pseudo(), 600, 600);
    }

    public DirectionalLight erstelleDirectLight(){
        DirectionalLight directlight = new DirectionalLight(new Color3f(0.8f, 0.8f, 0.8f), new Vector3f(-0.5f, -1f, -0.5f));
        directlight.setInfluencingBounds(BigBounds);
        return directlight;
    }

    public AmbientLight erstelleAmbientLight(){
        AmbientLight amlight = new AmbientLight(new Color3f(0.8f, 0.8f, 0.8f));
        amlight.setInfluencingBounds(BigBounds);
        return amlight;
    }

    public Appearance erstelleAppearance(boolean wahr){
        if(wahr){
            Appearance app = new Appearance();
            app.setMaterial(new Material(new Color3f(0.6f, 0.1f, 0.5f),
                                            new Color3f(0f, 0f, 0f),
                                            new Color3f(0.9f, 0.9f, 0.9f),
                                            new Color3f(0.8f, 0.8f, 0.8f),
                                            1f));
            return app;
        }

        Appearance app2 = new Appearance();
        app2.setMaterial(new Material(new Color3f(0f, 0f, 1f),
                                        new Color3f(0f, 0f, 0f),
                                        new Color3f(0.9f, 0.9f, 0.9f),
                                        new Color3f(0.8f, 0.8f, 0.8f),
                                        1f));
        return app2;
    }
}
```

Ok sobald ich das schultergelenk nach oben oder unten bewege , sprich die Rotation änder , bewegt sich der oberarm dementsprechend mit da es am gelenk hängt und da der oberarm auch ein ellbogengelenk hat und an dem ellbogengelenk ein unterarm hängt bewegt sich der unterarm/ellbogengelenk auch mit.
Halt die Baumstruktur.

Allerdings hab ich es noch nicht ganz raus , wie ich dafür sorge das der unterarm an dem ellbogengelenk bleibt.
Sprich , sobald ich den oberarm bewegt habe , verschiebt sich der unterarm.
Mit setTranslation kann ich ja die Position ändern , richtig? 
Aber nach jeder gelenkbewegung die anzupassen ist sicherlich nicht der Sinn oder?
Vorallem weil du ja geschrieben hast , feste Werte.


----------



## Marco13 (21. Apr 2010)

Die Primitive bei Java3D sind etwas... naja, manchmal etwas unpraktisch: Ein Cylinder ist immer so ausgerichtet, dass seine Mitte im Ursprung liegt. Für viele Fälle ist es aber praktischer oder intuitiver, wenn der Zylinder im Ursprung anfängt, und eine gewisse Höhe (länge) hat. 

Außerdem hattest du z.B. für den Oberarm die "schulterdrehung" gesetzt. Die SchulterDrehung wird aber nur auf die Schulter angewendet. Der Oberarm ist eigentlich nur eine Strecke mit konstanter Länge. D.h. die Translation für den Oberarm ist gerade die Strecke von der Schulter bis zum Ellbogen.

Wenn man die ganzen "umständlichen" Sachen mal wegläßt (wie z.B. die Erzeugung der Cylinder und das erstellen der Transforms mit ihren Anfangswerten und so), dann baut man wirklich nur eine Kette zusammen:

```
schulter.addChild(schultergelenk); // An der Schulter hängt das Schultergelenk
schultergelenk.addChild(oberarm); // An der Schulter hängt der Oberarm
oberarm.addChild(ellBogenGelenk); // Am Oberarm hängt der Ellbogen
ellBogenGelenk.addChild(unterarm); // Am Ellbogen hängt der Unterarm
unterarm.addChild(new Sphere(0.05f)); // Am Unterarm hängt die Hand
```

Die besagten "umständlichen" Sachen kann man dann teilweise noch in eigene Hilfsmethoden packen, das macht das ganze evtl. etwas übersichtlicher.

Ein bißchen was zum Spielen:


```
import java.applet.Applet;
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import com.sun.j3d.utils.applet.MainFrame;

import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.universe.*;
import javax.vecmath.*;
import javax.media.j3d.*;

public class Pseudo extends Applet{
    SimpleUniverse u = null;

    private TransformGroup schultergelenk;
    private TransformGroup ellBogenGelenk;

    Canvas3D canvas;
    BoundingSphere BigBounds = new BoundingSphere(new Point3d(), 1000);
    public Pseudo() {
    }

     public void destroy(){
        u.cleanup();
    }
    public void init(){

        setLayout(new BorderLayout());
        GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();

        canvas = new Canvas3D(config);

        add("Center",canvas);
        add("East",createControlPanel());

        u = new SimpleUniverse(canvas);
        u.getViewingPlatform().setNominalViewingTransform();
        u.addBranchGraph(erstelleSzenenGraph());

    }

    private Component createControlPanel()
    {
        JPanel p = new JPanel(new GridLayout(0,2));

        final JCheckBox link = new JCheckBox();

        p.add(new JLabel("Schulter"));
        final JSlider ss = new JSlider(0,360,0);
        ss.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                float angleDeg = ss.getValue();
                setzeSchulterWinkel(angleDeg);
                if (link.isSelected())
                {
                    setzeEllbogenWinkel(180-angleDeg);
                }
            }
        });
        p.add(ss);

        p.add(new JLabel("Ellbogen"));
        final JSlider es = new JSlider(0,360,0);
        es.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                float angleDeg = es.getValue();
                setzeEllbogenWinkel(angleDeg);
                if (link.isSelected())
                {
                    setzeSchulterWinkel(180-angleDeg);
                }
            }
        });
        p.add(es);


        p.add(new JLabel("Verbinde"));
        p.add(link);


        return p;
    }


    private void setzeSchulterWinkel(float angleDeg)
    {
        System.out.println("Schulter: "+angleDeg);
        Transform3D t = new Transform3D();
        t.setRotation(new AxisAngle4f(0f, 0f, 1f, (float)Math.toRadians(angleDeg)));
        schultergelenk.setTransform(t);
    }

    private void setzeEllbogenWinkel(float angleDeg)
    {

        System.out.println("Ellbogen: "+angleDeg);
        Transform3D t = new Transform3D();
        t.setRotation(new AxisAngle4f(0f, 0f, 1f, (float)Math.toRadians(angleDeg)));
        ellBogenGelenk.setTransform(t);
    }



    public BranchGroup erstelleSzenenGraph(){
       BranchGroup schulter = new BranchGroup();
       schulter.addChild(new Sphere(0.07f));

       schulter.addChild(erstelleAmbientLight());
       schulter.addChild(erstelleDirectLight());

       float oberarmLaenge = 0.6f;
       float unterarmLaenge = 0.5f;


       schultergelenk = new TransformGroup();
       schultergelenk.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
       schulter.addChild(schultergelenk); // An der Schulter hängt das Schultergelenk

       Group oberarmObjekt = erstelleCylinder(0.05f, oberarmLaenge,
           Sphere.GENERATE_NORMALS, erstelleAppearance(true));
       schultergelenk.addChild(oberarmObjekt);

       // Oberarm ist eine TransformGroup, die KONSTANT
       // die Transformation von der Schulter bis zum
       // Ende des Oberarms beschreibt
       TransformGroup oberarm = erstelleKonstante(oberarmLaenge);
       oberarm.addChild(new Sphere(0.06f));
       schultergelenk.addChild(oberarm); // An der Schulter hängt der Oberarm


       ellBogenGelenk = new TransformGroup();
       ellBogenGelenk.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
       oberarm.addChild(ellBogenGelenk); // Am Oberarm hängt der Ellbogen

       Group unterarmobjekt = erstelleCylinder(0.05f, unterarmLaenge,
           Sphere.GENERATE_NORMALS,erstelleAppearance(false));
       ellBogenGelenk.addChild(unterarmobjekt);

       // Unterarm ist eine TransformGroup, die KONSTANT
       // die Transformation vom Ellbogen bis zur Hand
       // beschreibt
       TransformGroup unterarm = erstelleKonstante(unterarmLaenge);
       ellBogenGelenk.addChild(unterarm); // Am Ellbogen hängt der Unterarm
       unterarm.addChild(new Sphere(0.05f)); // Am Unterarm hängt die Hand

       schulter.compile();
       return schulter;
    }

    // Erstelle einen Cylinder, der im Ursprung anfängt und
    // die angegebene Höhe hat (normalerweise ist die MITTE
    // des Cylinders im Ursprung, was hier unpraktisch ist)
    private static Group erstelleCylinder(float radius, float length, int flags, Appearance app)
    {
       Transform3D t = new Transform3D();
       t.setTranslation(new Vector3f(0,length/2,0));
       TransformGroup tg = new TransformGroup(t);
       Cylinder c = new Cylinder(radius, length, flags, app);
       tg.addChild(c);
       return tg;
    }

    // Erstellt eine TransformGroup mit einer konstanten
    // Translation in y-Richtung
    private static TransformGroup erstelleKonstante(float dy)
    {
       Transform3D t = new Transform3D();
       t.setTranslation(new Vector3f(0,dy,0));
       TransformGroup tg = new TransformGroup(t);
       return tg;
    }


    public static void main(String[] args) {
        new MainFrame(new Pseudo(), 1000, 600);
    }

    public DirectionalLight erstelleDirectLight(){
        DirectionalLight directlight = new DirectionalLight(
            new Color3f(0.8f, 0.8f, 0.8f), new Vector3f(-0.5f, -1f, -0.5f));
        directlight.setInfluencingBounds(BigBounds);
        return directlight;
    }

    public AmbientLight erstelleAmbientLight(){
        AmbientLight amlight = new AmbientLight(new Color3f(0.8f, 0.8f, 0.8f));
        amlight.setInfluencingBounds(BigBounds);
        return amlight;
    }

    public Appearance erstelleAppearance(boolean wahr){
        if(wahr){
            Appearance app = new Appearance();
            app.setMaterial(new Material(new Color3f(0.6f, 0.1f, 0.5f),
                                            new Color3f(0f, 0f, 0f),
                                            new Color3f(0.9f, 0.9f, 0.9f),
                                            new Color3f(0.8f, 0.8f, 0.8f),
                                            1f));
            return app;
        }

        Appearance app2 = new Appearance();
        app2.setMaterial(new Material(new Color3f(0f, 0f, 1f),
                                        new Color3f(0f, 0f, 0f),
                                        new Color3f(0.9f, 0.9f, 0.9f),
                                        new Color3f(0.8f, 0.8f, 0.8f),
                                        1f));
        return app2;
    }
}
```


----------

