# java 3d Steuerung (mit KSKB)



## darX (23. Jul 2011)

Hallo,

wusste leider nicht wie man hier Beiträge editieren kann, musste daher einen neuen Thread aufmachen. Sorry für den Spam.

Ich hatte ja ein Problem mit der Third-Person Steuerung, die nicht richtig klappte, wenn man sich nach oben oder nach unten bewegen wollte.

Habe anbei ein KSKB angefügt. Man kann sich momentan nur mit Mausrad nach oben und unten bewegen. Ansonsten einfach in der Third-Person Klasse die auskommentierten Aufrufe wieder aktivieren. Dann sollte es auch mit der normalen Mausbewegung klappen.

Gruß,
darX


----------



## Marco13 (23. Jul 2011)

Meine "aktivere" Java3D-Zeit ist schon eine Weile her, aber... Allgemein: Ein Transform3D ist ein ziemlich eng mit dem Szenegraphen verdrahtetes Objekt. Es erfüllt eigentlich fast die gleiche Funktion wie eine Matrix4f/d, ist aber an manchen Stellen etwas "schwergewichtiger" und weniger effizient, weil der Typ der Transformation immer wieder berechnet wird (die "classification" aus der JavaDoc). Wenn es wirklich nur darum geht, einen Zustand zu speichern und ein bißchen rumzurechnen, ist Matrix4f/d eigentlich meistens ausreichend. Die läßt sich ähnlich verwenden, der wichtigste Unterschied ist, dass man nach dem Erstellen "setIdentity" aufrufen muss.

Zum eigentlichen Problem: Ich glaube zwar, zu erahnen, was mit der Trennung von x- und y-Rotation erreicht werden sollte, aber habe es nicht vollständig im mathematischen Sinn nachvollzogen. Das Problem bei so einer Trennung nach Rotationen um X/Y/Z ist, dass man immer irgendeine Reihenfolge vorgibt (das ganze hat sogar einen Namen: Gimbal lock - Wikipedia, the free encyclopedia - aber ich glaube, das ist nicht direkt und nicht genau das, was dein Problem verursacht hat, sondern nur etwas ähnliches - also, vielleicht hätte man das gewünschte Verhalten auch mit Beibehaltung der getrennten X/Y-Rotation durch trickreiches ändern irgendwelcher Reihenfolgen erreichen können, aber... eigentlich ist das nicht notwendig). Das eigentlich wichtige ist in diesem Fall nur, dass man Translation und Rotation getrennt speichert (das hattest du ja auch schon). Die Rotation kann man eigentlich in einer Matrix zusammenfassen (theoretisch reicht ein Quaternion, aber irgendwann muss man das auch wieder zu einer Matrix machen...).

Hab' mal gebastelt, und dabei auch die Transform3Ds durch Matrix ersetzt (und ein paar "feingranulare" Objekterzeugungen vermieden... ist vielleicht nicht unbedingt notwendig, aber Gewohnheit...): Die Rotationen werden einfach alle von rechts an die "aktuelle" Rotation ranmultipliziert. Die Translation wird getrennt davon behandelt (und Bewegungen natürlich mit der "aktuellen" Rotation transformiert). Am Ende wird das ganze erst zur "Objekt-Transformation" zusammengebaut, und danach noch die Kamera hinter das Objekt gesetzt.


```
import java.awt.AWTEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.Enumeration;

import javax.media.j3d.Behavior;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.WakeupCondition;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOr;
import javax.vecmath.*;

import com.sun.j3d.utils.universe.SimpleUniverse;

public class ThirdPersonControl extends Behavior
{
    private int forwardKey = KeyEvent.VK_W;
    private int backwardKey = KeyEvent.VK_S;
    private int leftKey = KeyEvent.VK_A;
    private int rightKey = KeyEvent.VK_D;

    private int forwardKeyA = KeyEvent.VK_UP;
    private int backwardKeyA = KeyEvent.VK_DOWN;
    private int leftKeyA = KeyEvent.VK_LEFT;
    private int rightKeyA = KeyEvent.VK_RIGHT;

    private TransformGroup testObject;

    private Matrix4d tempMatrix = new Matrix4d();
    private Matrix4d currentRotation = new Matrix4d();
    private Vector3d tempVector = new Vector3d();
    private Vector3d currentTranslation = new Vector3d();
    private Transform3D objectTrans = new Transform3D();
    private Transform3D cameraTrans = new Transform3D();

    //private Transform3D rotationX = new Transform3D();
    private double turnRight = -0.05;
    private double turnLeft = 0.05;
    double lookingUp;
    private double lookUp = 0.05;
    private double lookDown = -0.05;
    private float moveForward = -0.2f;
    private float moveBackward = 0.2f;
    private WakeupCondition keyPress, mouseMove, mouseWheel;
    private WakeupOr criterion;
    private SimpleUniverse sU;
    private int posX=0,posY=0;
    private boolean firsttime = true;

    public ThirdPersonControl(TransformGroup transform, SimpleUniverse sU)
    {
        this.sU = sU;
        this.testObject = transform;
        keyPress = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
        mouseMove = new WakeupOnAWTEvent(MouseEvent.MOUSE_MOVED);
        mouseWheel = new WakeupOnAWTEvent(MouseWheelEvent.MOUSE_WHEEL);

        currentRotation.setIdentity();
    }

    @Override
    public void initialize()
    {
        criterion = new WakeupOr(new WakeupCriterion[]{
                (WakeupCriterion) keyPress,
                (WakeupCriterion) mouseMove,
                (WakeupCriterion) mouseWheel });
        this.wakeupOn(criterion);
    }

    @SuppressWarnings("rawtypes")
    @Override
    public void processStimulus(Enumeration criteria)
    {
        WakeupCriterion wakeup;
        AWTEvent[] event;

        while (criteria.hasMoreElements())
        {
          wakeup = (WakeupCriterion) criteria.nextElement();

          if( wakeup instanceof WakeupOnAWTEvent )
          {
            event = ((WakeupOnAWTEvent)wakeup).getAWTEvent();

            for( int i = 0; i < event.length; i++ )
            {
              if(event[i].getID() == KeyEvent.KEY_PRESSED)
              {
                  processKeyEvent((KeyEvent)event[i]);
              }
              else if (event[i].getID() == MouseEvent.MOUSE_MOVED)
              {
                  processMouseEvent((MouseEvent)event[i]);
              }
              else if (event[i].getID() == MouseWheelEvent.MOUSE_WHEEL)
              {
                  processMouseWheelEvent((MouseWheelEvent)event[i]);
              }
            }
          }
        }
        this.wakeupOn(criterion);
    }

    //Mouse Wheel
    private void processMouseWheelEvent(MouseWheelEvent mouseEvent)
    {

        if (mouseEvent.getWheelRotation() < 0)
        {
            lookUp();
        }
        else
        {
            lookDown();
        }
    }

    //Mouse Wheel
    private void processMouseEvent(MouseEvent event)
    {
        if(!firsttime)
        {
//          lookY((-(event.getX()-posX)));
//          lookX(-(event.getY()-posY));

            posX= event.getX();
            posY= event.getY();
        }
        else
        {
            posX= event.getX();
            posY= event.getY();
            firsttime = false;
        }
    }

    //WASD UND PFEILTASTEN
    private void processKeyEvent(KeyEvent eventKey)
    {
        int keyCode = eventKey.getKeyCode();
        standardMove(keyCode);
    }

    private void standardMove(int keycode)
    {
        if(keycode == forwardKey || keycode == forwardKeyA)
        {
            moveForward();
        }

        else if(keycode == backwardKey || keycode == backwardKeyA)
        {
            moveBackward();
        }

        else if(keycode == leftKey || keycode == leftKeyA)
        {
            turnLeft();
        }

        else if(keycode == rightKey || keycode == rightKeyA)
        {
            turnRight();
        }
    }

    private void moveForward()
    {
        tempVector.set(0.0f,0.0f,moveForward);
        currentRotation.transform(tempVector);
        currentTranslation.add(tempVector);
        updateAll();
    }

    private void moveBackward()
    {
        tempVector.set(0.0f,0.0f,moveBackward);
        currentRotation.transform(tempVector);
        currentTranslation.add(tempVector);
        updateAll();
    }

    private void turnLeft()
    {
        tempMatrix.setIdentity();
        tempMatrix.rotY(turnLeft);
        currentRotation.mul(tempMatrix);
        updateAll();
    }

    private void turnRight()
    {
        tempMatrix.setIdentity();
        tempMatrix.rotY(turnRight);
        currentRotation.mul(tempMatrix);
        updateAll();
    }

    //mouseWheel
    private void lookUp()
    {
        tempMatrix.setIdentity();
        tempMatrix.rotX(lookUp);
        currentRotation.mul(tempMatrix);
        updateAll();
    }

    //mouseWheel
    private void lookDown()
    {
        tempMatrix.setIdentity();
        tempMatrix.rotX(lookDown);
        currentRotation.mul(tempMatrix);
        updateAll();
    }



    private void updateAll()
    {
        tempMatrix.setIdentity();
        tempMatrix.setTranslation(currentTranslation);
        tempMatrix.mul(currentRotation);
        objectTrans.set(tempMatrix);
        testObject.setTransform(objectTrans);

        updateCamera();
    }

    //update the camera
    private void updateCamera()
    {
        cameraTrans.setIdentity();
        cameraTrans.setTranslation(new Vector3f(0.0f, 2.0f, 8.1f));
        cameraTrans.mul(objectTrans, cameraTrans);

        sU.getViewingPlatform().getViewPlatformTransform().setTransform(cameraTrans);
    }
}
```


----------



## darX (24. Jul 2011)

Super vielen Dank! Funktioniert alles bestens! :toll:


----------



## darX (25. Jul 2011)

Hallo, ich bins mal wieder. Die Steuerung und so klappt jetzt pefekt, allerdings hätte ich noch eine klitzekleine Sache, die mich noch stört. Und zwar bewegt sich das Objekt ja jetzt wie gewollt in alle Richtung, jedoch soll er, wenn er gerade nach oben guckt, und sich dann dreht, die Blickrichtung nach oben behalten. Schwer sowas mit Worten zu erklären, darum hier ne "anschauliche" Skizze^^

Die linke Abbbildung zeigt wie es momentan ist. Die rechte zeigt wie es sein soll. Beim drehen soll der momentane Winkel den das Objekt gerade hat gleich bleiben. Ich habe daran gedacht für einmal für die X-Rotation und einmal für die Y-Rotation je eine zusätzliche Transform3D zu erzeugen, allerdings kriege ich das irgendwie nicht gebacken es richtig einzubauen.

Gruß,
darX


----------



## Marco13 (25. Jul 2011)

Meinst du das, was rauskommt, wenn man

```
private void turnLeft()
    {
        tempMatrix.setIdentity();
        tempMatrix.rotY(turnLeft);
        currentRotation.mul(tempMatrix, currentRotation); // <--- hier
        updateAll();
    }

    private void turnRight()
    {
        tempMatrix.setIdentity();
        tempMatrix.rotY(turnRight);
        currentRotation.mul(tempMatrix, currentRotation); // <--- und hier
        updateAll();
    }
```
die neue Rotation von links dranmultipliziert?


----------



## darX (25. Jul 2011)

Genau das meinte ich!!^^

Du bist mein Held!! Danke dir nochmal!

Gruß,
darX


----------



## darX (26. Jul 2011)

So, eine letzte Frage:

Ich möchte einen RotationsInterpolator benutzen, um die Flügel eines bewegenden Objektes zu animieren.

Dabei habe ich 2 Probleme festgestellt:

1.) Die Flügel drehen sich um ihre eigene Achse, was total komisch aussieht.

2.) Sobald die Animation startet sind die Flügel nicht mehr an ihrer Position, sondern verschieben sich vor das Objekt und animiereren da. Dabei habe ich sie ja pe TransformGroup an das Objekt geheftet. Die Ani scheint das allerdings komplett zu ignorieren.


Letztendlich erstelle ich einen Rotationsinterpolator, übergeben ihm die TransformGroup, in der die Flügel drinne sind und füge dem Interpolator dem Szenegraphen hinzu. Sollte doch alles gewesen sein oder nicht?

Interpolatoren scheinen ein generelles Problem in java3d zu sein habe ich das Gefühl.???:L


----------



## Marco13 (26. Jul 2011)

Kapier' ich nicht. KSKB?


----------



## darX (26. Jul 2011)

Hab mal die Animationsmethode in der hauptklasse erstmal auskommentiert, damit man sieht wie das objekt aussehen soll. Links und rechts habe ich jeweils 2 Flügel an die Box geheftet.


----------



## Marco13 (26. Jul 2011)

Hmja... erstmal die pragmatische Änderung: Zwischen die TransformGroup, die den Flügel neben der Box positioniert, und den Flügel selbst muss man eine weitere TransformGroup hängen - (hier linkerFlügelTransform*2* - nur ein Hack...). DIESE "linkerFlügelTransform2" wird dann von der Animation verändert. (Die Animation "weiß" ja sonst nicht, dass diese Transformation nicht nur die Rotation, sondern auch das Verschieben neben die Box enthalten soll)

Zusätzlich habe ich noch im BewegeFlügel-Konstruktor "animation.setEnable(true);" gesetzt. Jetzt dreht sich der Flügel da ein bißchen rum.


```
private void createFlugObjekt(BranchGroup objRoot,SimpleUniverse su)
    {
         Box box = new Box();
         Box linkerFlügel = new Box(1.0f,0.1f,0.2f,null);
         Box rechterFlügel = new Box(1.0f,0.1f,0.2f,null);

         boxTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
         boxTransform.addChild(box);


         Transform3D linkerFlügelTransform3D = new Transform3D();
         linkerFlügelTransform3D.setTranslation(new Vector3f(-2f,0.5f,0));

         Transform3D rechterFlügelTransform3D = new Transform3D();
         rechterFlügelTransform3D.setTranslation(new Vector3f(2f,0.5f,0));

         linkerFlügelTransform.setTransform(linkerFlügelTransform3D);
         TransformGroup linkerFlügelTransform2 = new TransformGroup();

         linkerFlügelTransform.addChild(linkerFlügelTransform2);
         linkerFlügelTransform2.addChild(linkerFlügel);

         rechterFlügelTransform.setTransform(rechterFlügelTransform3D);
         rechterFlügelTransform.addChild(rechterFlügel);

         boxTransform.addChild(linkerFlügelTransform);
         boxTransform.addChild(rechterFlügelTransform);

         ThirdPersonControl tPC = new ThirdPersonControl(boxTransform,su);
         tPC.setSchedulingBounds(new BoundingSphere(new Point3d(0.0,0.0,0.0),1000));


         //Animation für den linken Flügel wird hier gesetzt bzw. initialisiert
       BewegeFlugel tauchAni = new BewegeFlugel(linkerFlügelTransform2,tPC, objRoot, su);
       tauchAni.setSchedulingBounds(new BoundingSphere(new Point3d(0.0,0.0,0.0),1000));


         objRoot.addChild(boxTransform);
         objRoot.addChild(tPC);
    }
```

Aber ACHTUNG: Das ist nur ein Hack. Du meintest, das wäre "_eine letzte Frage_" - das stimmt so nicht. Wenn du so weitermachst, wirst du ziemlich schnell in Chaos versinken, und das kann dann sehr frustrierend sein. So ein Spiel, wo man frei in 3D in der Gegend rumschwimmt und Sachen ausweicht und vielleicht Goodies aufsammelt und so könnte richtig unterhaltsam werden :toll: aber im Moment sieht es aus, als würde es immer undurchschaubarer und hakeliger, und früher oder später werde ("sogar") ich (  ) die KSKBs (oder dann: GSKBs) nicht mehr nachvollziehen wollen. 

Versuche, das ganze etwas klarer und systematischer zu strukturieren. Ein Patentrezept kann ich dafür nicht liefern, aber auf Basis der bisherigen Fragen könnte das vielleicht sowas sein: 
- Die Klasse "StartHere" ist die Hauptklasse, die erstmal nur _irgendein_ Objekt anzeigt, das an einer TransformGroup hängt.
- Erstelle dann eine ThirdPersonControl, mit der du NUR irgendein Objekt (+Camera), das an einer TransformGroup hängt, wie gewünscht steuern kannst. Am besten ohne direkte Anhängigkeiten zu SimpleUniverse & Co: Eigentlich braucht diese Klasse kein SimpleUniverse, sondern nur eine TransformGroup für das Objekt und eine für die ViewPlatform. Das erstellen dieser ThirdPersonControl sollte in einer, dedizierten Methode "createControl" in der StartHere passieren. Wenn du damit fertig bist, nimm' die ThirdPersonControl wieder raus (!).  
- Erstelle dann eine Klasse "AnimatedShip" oder so. Überlege dir, welche Methoden diese Klasse braucht. Vermutlich nicht soo viele: Eine wie "getMainTransformGroup", die den "Wurzelknoten des Schiffes" liefert (also der, wo Box und Flügel dranhängen, mit welchen Transforms auch immer). Zusätzlich die Methoden, die du brauchst, um das Paddeln zu steuern. Vielleicht sowas wie [c]void paddle(float geschwindigkeit)[/c], wo man 0 übergibt um zu stoppen, und 1 um mit maximaler Geschwindigkeit zu Paddeln. 
- Wenn das fertig ist, versuche, die ThirdPersonControl und das AnimatedShip zu kombinieren - das wird bedeuten, dass man vielleicht bei der ThirdPersonControl einen Listener braucht, um mitzukriegen, welche Taste gedrück ist, um daraufhin "ship.paddle(1)" aufzurufen oder so, das weiß ich jetzt nicht. Idealerweise sollten sich diese beiden Klassen aber trotzdem nicht direkt kennen.
- Vermutlich wird es noch weitere Klassen geben: "Hindernis", "Goodie", "Enemy" oder sonstwas. Überlege dir, welche Methoden diese Klassen brauchen, wie die Vererbungshierarchie sein kann, und wie man diese Klassen gut modellieren und ihre Funktionalität gut kapseln kann. Später soll ja vermutlich mal sowas gehen wie

```
void baueLevel()
{
    world.setPlayer(new Player(-10,20,10));
    world.addObstacle(new Stone(10,20,10));
    world.addObstacle(new Mine(20,20,10));
    world.addGoodie(new ExtraLife(30,40,50));
    world.addEnemy(new Octopus(30,40,50));
}
```
oder so, ohne dass man sich noch mit TransformGroups oder Alphas herumschlagen muss...


Dabei immer darauf achten, den Klassen klar definierte Methoden und möglichst wenige Fields zu geben. Genau das, was sie brauchen, um ihre Aufgabe zu erfüllen. Im Moment liegen z.B. 

```
TransformGroup boxTransform = new TransformGroup();
    TransformGroup linkerFlügelTransform = new TransformGroup();
    TransformGroup rechterFlügelTransform = new TransformGroup();
```
in der StartHere-Klasse. Die haben da nichts zu suchen! Die boxTransform könnte in der Ship-Klasse liegen, und bei "getMainTransformGroup" zurückgegeben werden, aber die anderen TransformGroups braucht man nur dort, wo die Flügel gebaut werden (z.B. im Konstruktor der "Ship"-Klasse!)


----------

