package com.brackeen.javagamebook.math3D;

/**
    Klasa Vector3D implementuje trójwymiarowy wektor definiowany 
    przez liczby zmiennoprzecinkowe x, y oraz z. Taką reprezentację można 
    traktować jako punkt o współrzędnych (x,y,z) lub jako wektory wiodący 
    od punktu (0,0,0)do punktu (x,y,z).
*/
public class Vector3D implements Transformable {

    public float x;
    public float y;
    public float z;


    /**
        Tworzy nowy Vector3D z (0,0,0).
    */
    public Vector3D() {
        this(0,0,0);
    }


    /**
        Tworzy nowy Vector3D o tych samych wartościach, jakie
        ustalono w innym wektorze Vector3D.
    */
    public Vector3D(Vector3D v) {
        this(v.x, v.y, v.z);
    }


    /**
        Tworzy nowy Vector3D o podanych wartościach (x, y, z).
    */
    public Vector3D(float x, float y, float z) {
        setTo(x, y, z);
    }


    /**
        Sprawdza, czy ten Vector3D jest równy podanemu obiektowi (Object).
        Będą równe tylko, jeśli obiekt jest również wektorem typu Vector3D,
        a współrzędne x, y oraz z obu wektorów są równe.
    */
    public boolean equals(Object obj) {
        Vector3D v = (Vector3D)obj;
        return (v.x == x && v.y == y && v.z == z);
    }


    /**
        Sprawdza, czy dany Vector3D ma określone współrzędne
        x, y oraz z.
    */
    public boolean equals(float x, float y, float z) {
        return (this.x == x && this.y == y && this.z == z);
    }


    /**
        Przypisuje wektorowi takie same wartości, jakie ma
        inny Vector3D.
    */
    public void setTo(Vector3D v) {
        setTo(v.x, v.y, v.z);
    }


    /**
        Przypisuje temu wektorowi ustalone wartości (x, y, z).
    */
    public void setTo(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }


    /**
        Dodaje do tego wektora określone wartości(x, y, z).
    */
    public void add(float x, float y, float z) {
        this.x+=x;
        this.y+=y;
        this.z+=z;
    }


    /**
        Odejmuje od tego wektora określone wartości (x, y, z).
    */
    public void subtract(float x, float y, float z) {
        add(-x, -y, -z);
    }


    /**
        Dodaje do tego wektora inny, ustalony wektor.
    */
    public void add(Vector3D v) {
        add(v.x, v.y, v.z);
    }


    /**
        Odejmuje od tego wektora inny, ustalony wektor.
    */
    public void subtract(Vector3D v) {
        add(-v.x, -v.y, -v.z);
    }


    /**
        Mnoży ten wektor przez podaną wartość s. Nowa długość
        wektora będzie się równać length()*s.
    */
    public void multiply(float s) {
       x*=s;
       y*=s;
       z*=s;
    }


    /**
        Dzieli ten wektor przez określoną wartość s. Nowa długość
        wektora będzie się równać length()/s.
    */
    public void divide(float s) {
       x/=s;
       y/=s;
       z/=s;
    }


    /**
        Zwraca długość wektora jako liczbę zmiennoprzecinkową typu float.
    */
    public float length() {
        return (float)Math.sqrt(x*x + y*y + z*z);
    }


    /**
        Przekształca ten Vector3D na wektor jednostkowy lub
        innymi słowy na wektor o długości 1. Równoważna wywołaniu 
        v.divide(v.length()).
    */
    public void normalize() {
        divide(length());
    }


    /**
        Przekształca ten Vector3D na reprezentację łańcuchową.
    */
    public String toString() {
        return "(" + x + ", " + y + ", " + z + ")";
    }

    /**
        Obróć ten wektor naokoło osi x o określony kąt.
        Kąt podawany jest w radianach. Funkcja Math.toRadians() 
        pozwala na konwersję ze stopni na radiany.
    */
    public void rotateX(float angle) {
        rotateX((float)Math.cos(angle), (float)Math.sin(angle));
    }


    /**
        Obróć ten wektor naokoło osi y o określony kąt.
        Kąt podawany jest w radianach. Funkcja Math.toRadians() 
        pozwala na konwersję ze stopni na radiany.
    */
    public void rotateY(float angle) {
        rotateY((float)Math.cos(angle), (float)Math.sin(angle));
    }


    /**
        Obróć ten wektor naokoło osi z o określony kąt.
        Kąt podawany jest w radianach. Funkcja Math.toRadians() 
        pozwala na konwersję ze stopni na radiany.
    */
    public void rotateZ(float angle) {
        rotateZ((float)Math.cos(angle), (float)Math.sin(angle));
    }

    /**
        Obróć ten wektor dookoła osi x o określony kąt,
        wykorzystując już wyliczone wartości cosinusa i sinusa kąta,
        o który obracamy.
    */
    public void rotateX(float cosAngle, float sinAngle) {
        float newY = y*cosAngle - z*sinAngle;
        float newZ = y*sinAngle + z*cosAngle;
        y = newY;
        z = newZ;
    }


    /**
        Obróć ten wektor dookoła osi y o określony kąt,
        wykorzystując już wyliczone wartości cosinusa i sinusa kąta,
        o który obracamy.
    */
    public void rotateY(float cosAngle, float sinAngle) {
        float newX = z*sinAngle + x*cosAngle;
        float newZ = z*cosAngle - x*sinAngle;
        x = newX;
        z = newZ;
    }


    /**
        Obróć ten wektor dookoła osi x o określony kąt,
        wykorzystując już wyliczone wartości cosinusa i sinusa kąta,
        o który obracamy.
    */
    public void rotateZ(float cosAngle, float sinAngle) {
        float newX = x*cosAngle - y*sinAngle;
        float newY = x*sinAngle + y*cosAngle;
        x = newX;
        y = newY;
    }


    /**
        Dodaje do tego wektora określoną transformację. Wektor 
        jest najpierw obracany, a dopiero potem wykonywana jest translacja.
    */
    public void add(Transform3D xform) {

        // obróć
        addRotation(xform);

        // wykonaj translację
        add(xform.getLocation());
    }


    /**
        Odejmuje do tego wektora określoną transformację. Na wektorze 
        najpierw wykonywana jest translacja, a dopiero potem jest obracany.
    */
    public void subtract(Transform3D xform) {

        // wykonaj translację
        subtract(xform.getLocation());

        // obróć
        subtractRotation(xform);
    }


    /**
        Obraca wektor o kąt podany 
        w transformacji.
    */
    public void addRotation(Transform3D xform) {
        rotateX(xform.getCosAngleX(), xform.getSinAngleX());
        rotateZ(xform.getCosAngleZ(), xform.getSinAngleZ());
        rotateY(xform.getCosAngleY(), xform.getSinAngleY());
    }


    /**
        Obraca wektor o kąt przeciwny do podanego 
        w transformacji.
    */
    public void subtractRotation(Transform3D xform) {
        // zauważcie, że sin(-x) == -sin(x) i cos(-x) == cos(x)
        rotateY(xform.getCosAngleY(), -xform.getSinAngleY());
        rotateZ(xform.getCosAngleZ(), -xform.getSinAngleZ());
        rotateX(xform.getCosAngleX(), -xform.getSinAngleX());
    }

    /**
        Zwraca iloczyn skalarny tego wektora i innego podanego
        wektora.
    */
    public float getDotProduct(Vector3D v) {
        return x*v.x + y*v.y + z*v.z;
    }

    /**
        Przypisuje temu wektorowi wynik iloczynu wektorowego  dwóch
        podanych wektorów. Jeden z podanych wektorów może być 
        tym wektorem (this).
    */
    public void setToCrossProduct(Vector3D u, Vector3D v) {
        // przypisz najpierw do zmiennych lokalnych, bo u lub v mogą być wektorem 'this'
        float x = u.y * v.z - u.z * v.y;
        float y = u.z * v.x - u.x * v.z;
        float z = u.x * v.y - u.y * v.x;
        this.x = x;
        this.y = y;
        this.z = z;
    }


    /**
        Zwraca podniesioną do kwadratu odległość pomiędzy tym wektorem
        a wektorem przekazanym w postaci argumentu.
    */
    public float getDistanceSq(Vector3D v) {
        float dx = v.x - x;
        float dy = v.y - y;
        float dz = v.z - z;
        return dx*dx + dy*dy + dz*dz;
    }


    /**
        Zwraca odległość pomiędzy tym wektorem a wektorem przekazanym
        w postaci argumentu.
    */
    public float getDistance(Vector3D v) {
        return (float)Math.sqrt(getDistanceSq(v));
    }


    /**
        Ustawia długość tego wektora.
    */
    public void setLength(float newLength) {
        normalize();
        multiply(newLength);
    }
}
