# LWJGL Egoshooterproblem



## Tokolosh (30. Sep 2011)

Hallo, ich habe ein kleines Problem. Ich komme einfach mit der implementierung der Maussteuerung nicht weiter. Alles funktioniert, kann mich umschauen usw.. Mein Problem ist, dass ich mich zu sehr drehe. Also beim nach oben und nach unten schauen schaffe ich eine volle Drehung, was ich aber nicht will.

Welche Werte soll ich da von der Maus auslesen. Mit der mouse.getDY beschränkung klappt es nicht, da er bei jedem Aufruf neu anfängt zu zählen. Habe auch versucht, den Winkel selber zu berechnen, aber auch da scheint meine Steuerung zu versagen. Hat jemand ein Beispiel. Schonmal danke...


----------



## Spacerat (30. Sep 2011)

Hmm... mit Egoshootern hab' ich auch meine Probleme... liegt aber am Genre. 
Dein Problem ist aber nicht Egoshooterspezifisch und obendrein nicht mal wirklich ein Problem. Die Mausposition musst du als Differenzvector betrachten, welche letztenendes zu den jeweiligen Richtungen der ViewMatrix hinzugezählt oder abgezogen wird. Daraus folgt, dass du diese Richtungen bzw. Winkel in der ViewMatrix beschränken musst.
Diese Matrix besteht bekanntermassen aus 16 Elementen und ist in der Regel wie folgt aufgebaut:

```
S->Scale
T->Translation
R->Rotation

 Sx -Rz +Ry 0.0
+Rz  Sy -Rx 0.0
-Ry +Rx  Sz 0.0
 Tx  Ty  Tz 1.0

bzw:

 Sx +Rz -Ry  Tx
-Rz  Sy +Rx  Ty
+Ry -Rx  Sz  Tz
0.0 0.0 0.0 1.0
```


----------



## Marco13 (30. Sep 2011)

Hm - so einfach sieht die Matrix glaub' ich dann doch nicht aus  
Es geht ja erstmal nur um die Drehung. Du müßtest dir genau überlegen, welches Verhalten dort implementiert werden soll. Vermutlich wäre es hilfreich, sich die (lokale) Rotation um die X-Achse (also wie weit man hoch und runter schaut) und die eigentliche Drehung um die Y-Achse (also in welche Himmelsrichtung man schaut) komplett getrennt zu speichern. Erstere könnte man dann als Winkel speichern, der eben nur +/- 80° sein darf...


----------



## Spacerat (30. Sep 2011)

@Marco: Also die Benennung der Elemente passt so und beruhen auf Experimenten mit übereinander gelegten Einzelmatritzen für Rotation, Scale, und Translation, wie die aussehen weisst du, denk' ich. Scale und Position (Translation) der Matrix können direkt ausgelesen werden, die Rotation um die Achsen gestaltet sich jedoch ein wenig anders.
	
	
	
	





```
xr = asin(-rx) * 180 / PI
yr = -ry * 180 / PI
zr = -rz * 180 / PI
```
Schleierhaft ist mir dabei, wieso grad' bei xr noch asin berechnet werden muß. Problematisch bleibt aber nach wie vor das Setzen der Scale und vor allem der Rotationswerte. Glücklicherweise hat der TS ja danach nicht gefragt.


----------



## Marco13 (30. Sep 2011)

Hm... "Übereinanderlegen" von Matrizen hat (so direkt erstmal) keine mathematische Bedeutung. Wenn man eine Rotation um die z-Achse hat

```
ca -sa  0  0
sa  ca  0  0
0    0  1  0
0    0  0  1
```
und dort noch eine Skalierung dazukommt, was steht dann in der Diagonalen?


----------



## Spacerat (30. Sep 2011)

Habe ich doch oben schon gesagt... das Setzen bzw. errechnen der Werte bleibt problematisch, zumindest was die Rotation und das Scaling angeht.


----------



## EgonOlsen (30. Sep 2011)

Da eine Hoch-/Runterbewegung eigentlich immer (es sei denn, man steht schief) eine Drehung um die X-Achse ist, kannst du den Drehwinkel einfach merken. D.h. du änderst immer die Matrix UND den Winkel und wenn der absolute Winkel zu groß ist, drehst du einfach nicht mehr. Pseudocode für jPCT (die Idee ist dieselbe, nur die Matrixoperationen sieht man hier nicht direkt):FPS-like camera controls - JPCT


----------



## tdc (30. Sep 2011)

Ich hatte auch einige Probleme bei der Ego-Perspektive.

Speichere die Werte der Rotation einfach immer und addiere dy immer dazu. So habe ich das auch gemacht...


```
public void rotation(float deltah, float deltav) //Rotationsmethode
	{
		if(!firstvrot) //In dem Zusammenhang unwichtig
		{
			hrot += deltah*0.1;  //Für die gesamte horizontale Rotation
			if((deltav > 0 && vrot < 85) || (deltav < 0 && vrot > -85)) //Das hier sollte dein Problem lösen. es wird immer abgefragt ob die vertikale Rotation (vrot) unter 85, bzw. über -85 liegt
			{
				vrot += deltav*0.1; //gesamte vertikale Rotation
			}

			//Damit die Variable für die gesamte horizontale Rotation nicht zu groß wird:
			if(hrot > 360)
			{
				hrot -= 360;
			}
			if(hrot < -360)
			{
				hrot += 360;
			}
```

Edit: Mist, Egon war schneller.


----------



## Guest2 (30. Sep 2011)

Moin,

im Allgemeinen gelten die Matrizen für: Rotation, Skalierung und Verschiebung. Will man von einer bereits "vermultiplizierten" Transformationsmatrix zurück auf die Einzelmatrizen, müssen schwerere Geschütze aufgefahren werden (Polare Dekomposition).

Nichtsdestotrotz ist eine FPS-Steuerung keine Raketenwissenschaft. Für KSKBs nutze ich für gewöhnlich diese ModelView Klasse:


```
package work;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.media.opengl.GL4;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLCanvas;

public class Modelview implements KeyListener, MouseMotionListener, GLEventListener {

    private static final int            forward    = KeyEvent.VK_W;
    private static final int            backward   = KeyEvent.VK_S;
    private static final int            leftward   = KeyEvent.VK_A;
    private static final int            rightward  = KeyEvent.VK_D;

    private static final int            mouseSpeed = 2;
    private static final int            keySpeed   = 1;

    private static final int            rotVMax    = 1;
    private static final int            rotVMin    = -1;


    private final Map<Integer, Boolean> keys       = new ConcurrentHashMap<Integer, Boolean>();
    private final float[]               mv         = new float[16];
    private final float[]               nm         = new float[9];
    private final GLCanvas              canvas;
    private final Robot                 robot;

    private float                       rotX, rotY, rotZ, rotV;
    private float                       posX, posY, posZ;
    private int                         centerX, centerY;

    private long                        lastTime   = -1;

    private GL4                         gl         = null;


    public Modelview(final GLCanvas canvas) {

        Robot robot = null;
        try {

            robot = new Robot();

        } catch (final AWTException e) {

            e.printStackTrace();

        }

        if (robot != null) {

            final BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
            final Graphics2D gfx = cursorImg.createGraphics();
            gfx.setColor(new Color(0, 0, 0, 0));
            gfx.fillRect(0, 0, 16, 16);
            gfx.dispose();
            canvas.setCursor(canvas.getToolkit().createCustomCursor(cursorImg, new Point(), ""));

        }

        for (int i = 0; i < 4; i++)
            mv[i * 5] = 1.0f;

        for (int i = 0; i < 3; i++)
            nm[i * 4] = 1.0f;


        this.canvas = canvas;
        this.robot = robot;

        this.posZ = -10;
        this.posX = 0;

        canvas.addKeyListener(this);
        canvas.addMouseMotionListener(this);
        canvas.addGLEventListener(this);

    }


    public void bind() {

        calculatePosition();
        calculateModelview();

        final int hmv = gl.glGetUniformLocation(Shader.getCurrentShaderID(), "mv");
        final int hnm = gl.glGetUniformLocation(Shader.getCurrentShaderID(), "nm");

        gl.glUniformMatrix4fv(hmv, 1, false, mv, 0);
        gl.glUniformMatrix3fv(hnm, 1, false, nm, 0);

        // oder eben gl.glLoadMatrix...

    }


    private void calculatePosition() {

        if (lastTime == -1)
            lastTime = System.nanoTime();

        final double step = keySpeed * -((lastTime - (lastTime = System.nanoTime())) / 10E7);

        Boolean value = null;

        if ((value = keys.get(forward)) != null && value == true) {

            posX -= Math.sin(rotY) * step;
            posZ += Math.cos(rotY) * step;

        }

        if ((value = keys.get(backward)) != null && value == true) {

            posX += Math.sin(rotY) * step;
            posZ -= Math.cos(rotY) * step;

        }

        if ((value = keys.get(leftward)) != null && value == true) {

            posX += Math.cos(rotY) * step;
            posZ += Math.sin(rotY) * step;

        }

        if ((value = keys.get(rightward)) != null && value == true) {

            posX -= Math.cos(rotY) * step;
            posZ -= Math.sin(rotY) * step;

        }

    }


    private void calculateModelview() {

        final float sinX = (float) Math.sin(rotX);
        final float sinY = (float) Math.sin(rotY);
        final float sinZ = (float) Math.sin(rotZ);

        final float cosX = (float) Math.cos(rotX);
        final float cosY = (float) Math.cos(rotY);
        final float cosZ = (float) Math.cos(rotZ);

        mv[0] = nm[0] = cosY * cosZ + sinY * sinX * sinZ;
        mv[1] = nm[1] = cosX * sinZ;
        mv[2] = nm[2] = -sinY * cosZ + cosY * sinX * sinZ;
        mv[4] = nm[3] = -cosY * sinZ + sinY * sinX * cosZ;
        mv[5] = nm[4] = cosX * cosZ;
        mv[6] = nm[5] = sinY * sinZ + cosY * sinX * cosZ;
        mv[8] = nm[6] = sinY * cosX;
        mv[9] = nm[7] = -sinX;
        mv[10] = nm[8] = cosY * cosX;

        mv[12] = mv[0] * posX + mv[4] * posY + mv[8] * posZ;
        mv[13] = mv[1] * posX + mv[5] * posY + mv[9] * posZ;
        mv[14] = mv[2] * posX + mv[6] * posY + mv[10] * posZ;

    }


    @Override
    public void init(final GLAutoDrawable drawable) {

        gl = (GL4) drawable.getGL();

    }


    @Override
    public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {

        rotV = 0;

        final Rectangle r = canvas.getParent().getBounds();

        centerX = r.x + r.width / 2;
        centerY = r.y + r.height / 2;

        if (robot != null)
            robot.mouseMove(centerX, centerY);

    }


    @Override
    public void mouseMoved(final MouseEvent e) {

        rotY -= (centerX - e.getXOnScreen()) / 1000.0 * mouseSpeed;
        rotV -= (centerY - e.getYOnScreen()) / 1000.0 * mouseSpeed;

        if (rotV > rotVMax)
            rotV = rotVMax;

        if (rotV < rotVMin)
            rotV = rotVMin;

        rotX = (float) Math.cos(rotY) * rotV;
        rotZ = (float) Math.sin(rotY) * rotV;

        if (robot != null)
            robot.mouseMove(centerX, centerY);

    }


    @Override
    public void mouseDragged(final MouseEvent e) {

        mouseMoved(e);

    }


    @Override
    public void keyPressed(final KeyEvent e) {

        keys.put(e.getKeyCode(), true);

    }


    @Override
    public void keyReleased(final KeyEvent e) {

        keys.put(e.getKeyCode(), false);

    }


    @Override public void keyTyped(final KeyEvent e) { }
    @Override public void display(final GLAutoDrawable drawable) { }
    @Override public void dispose(final GLAutoDrawable drawable) { }

}
```

Gruß,
Fancy


----------



## Spacerat (30. Sep 2011)

Eigentlich ist's doch recht simpel: In jeder Spalte und in jeder Reihe stehen X-, Y- und Z-Werte. In der untersten Reihe die Translationswerte, die ihrerseits weder etwas mit Rotation oder Scaling zu tun haben. Bei Rotationen um die X-Achse ändert sich für X selber nichts, für Y- und Z-Achse gilt selbiges. Daraus folgt, dass in der Diagonalen stets die Scalewerte verbleiben. Die restlichen Werte des 9x9 Sektors in der Matrix oben Links müssen also zwangsläufig etwas mit Rotation zu tun haben oder sehe ich das falsch? Witzigerweise lieferten die dahingehenden Experimente stets korrekte Werte.


----------



## Marco13 (30. Sep 2011)

Spacerat hat gesagt.:


> Daraus folgt, dass in der Diagonalen stets die Scalewerte verbleiben. Die restlichen Werte des 9x9 Sektors in der Matrix oben Links müssen also zwangsläufig etwas mit Rotation zu tun haben oder sehe ich das falsch? Witzigerweise lieferten die dahingehenden Experimente stets korrekte Werte.



Dort stehen nicht nur Skalierungswerte, sondern, wie im Beispiel oben, auch die "sin"-Elemente der Rotationsmatrix. Die kann man ggf. versuchen rauszuziehen, aber selbst dann ist es schwierig bis unmöglich (und auf jeden Fall nicht eindeutig) eine "Belieibige" Matrix in Rotation, Drehung und Translation zu zerlegen (von Scherung und irgendwelchen ganz anderen, "willkürlichen" Operationen mal abgesehen) - da gibt es Papers drüber, wie das von Fancy schon verlinkte, oder auch alles, was man findet, wenn man nach sowas wie computing euler angles from rotation matrix sucht (was für sich schon kompliziert ist, obwohl es ja explizit NUR um Rotationen geht...)


----------



## Tokolosh (1. Okt 2011)

Hallo, sind gute Antworten dabei. Hab das Problem schon so gut wie gelöst. Hab die Methode mit dem addieren genommen. Nun habe ich ein neues Problem. Wenn ich an die Grenzen stoße, also wenn die Werte zu MAX oder MIN ankommen, verschiebt sich der 0-Punkt. Besonders bei schnellen Bewegungen. Das heißt wenn ich wieder bei 0 bin, schaue ich nicht mehr exakt wie vorher. Obwohl der Winkel bei 0 ist. 

Hier mal 2 Screen von der gleichen Position mit der Winkelangabe.
Screen 1 zeigt die Sicht vor der Drehung, also gleich nach dem Start.

Screen 2 zeigt die Sicht nach einigen Drehungen, also mehrmals nach oben und unten.

spdy ist hier der Winkel.


----------



## Guest2 (2. Okt 2011)

Also zumindest mir ist nicht klar was Du nun wie implementiert hast, sodass man höchstens raten kann.

Bei dem beschriebenen Problem könnte ich mir vorstellen, dass Du die "Kamera" über fortlaufende Matrixoperationen von OpenGL realisiert hast, den Winkel um die Horizontale aber separat erfasst und es keinen Abgleich zwischen Transformationsmatrix und Winkel gibt?

Falls dem so ist: Die Transformationsoperationen bei OpenGL sind Matrixmultiplikationen auf einer 4x4 Matrix mit Float-Werten. Wenn da oft genug eine neue Rotationsmatrix aufmultipliziert wird, kommt es zu einer numerischen Abweichung zwischen den in der Transformationsmatrix codierten Winkeln und dem extern erfassten Winkel. Mein Beispiel oben berechnet deshalb die ModelView Matrix jeweils aus den Positions- und Winkelangaben.

Gruß,
Fancy


----------



## Tokolosh (3. Okt 2011)

Also ich hab da den Winkel einfach draufaddiert. Also direkt von der Steigung des Mauswinkels. Das Problem ist gelöst, da der Fehler sich wieder ausgleicht. Keine Ahnung warum es jetzt wie gewünscht funktioniert. Nochmals danke an die vielen Infos...


----------

