package engine.object3d;

import org.joml.*;

import java.util.ArrayList;

public class Object3d {

    //TODO: matrix caching booleans

    protected Vector3f position;
    protected Quaternionf rotation;
    protected Vector3f scale;

    protected Matrix4f localToWorld;
    protected Matrix4f worldToLocal;

    protected Object3d parent;
    protected ArrayList<Object3d> children;

    public Object3d() {
        position = new Vector3f();
        rotation = new Quaternionf();
        scale = new Vector3f(1, 1, 1);

        localToWorld = new Matrix4f();
        worldToLocal = new Matrix4f();

        parent = null;
        children = new ArrayList<>();
    }

    public void addChild(Object3d obj) {
        if (this == obj) {
            throw new IllegalArgumentException("Cannot add object as a child to itself.");
        }
        children.add(obj);
        obj.parent = this;
    }

    public void translate(Vector3f v) {
        position.add(v);
    }

    public void translateRelativeToRotation(Vector3f v) {
        translate(rotation.transform(v));
    }

    public void moveForward(float amt) {
        translateRelativeToRotation(new Vector3f(0, 0, -amt));
    }

    public void moveRight(float amt) {
        translateRelativeToRotation(new Vector3f(amt, 0, 0));
    }

    public void moveUp(float amt) {
        translateRelativeToRotation(new Vector3f(0, amt, 0));
    }

    public void rotate(Vector3f axis, float degrees) {
        rotation.rotateAxis((float)Math.toRadians(degrees), axis);
    }

    public void rotateX(float degrees) {
        Vector3f localRotationAxis = new Vector3f(1, 0, 0);
        rotate(localRotationAxis, degrees);
    }

    public void rotateY(float degrees, boolean maintainWorldUpVector) {
        Vector3f localRotationAxis = new Vector3f(0, 1, 0);

        if (maintainWorldUpVector) {
            Quaternionf inverseRotation = new Quaternionf();
            rotation.invert(inverseRotation);

            localRotationAxis.rotate(inverseRotation);
        }

        rotate(localRotationAxis, degrees);
    }

    public void rotateZ(float degrees) {
        Vector3f localRotationAxis = new Vector3f(0, 0, 1);
        rotate(localRotationAxis, degrees);
    }

    public void scale(Vector3f v) {
        scale.x *= v.x;
        scale.y *= v.y;
        scale.z *= v.z;
    }

    public void scale(float f) {
        scale.mul(f);
    }

    public Matrix4f getLocalToWorld() {
        /*
            Because we want to do the following in this order:
                1. scale the object
                2. rotate it to the correct orientation
                3. translate it to it's correct position
            And because we are using column major matrices (and they are post-multiplied together), we must apply these
            transformations to an identity matrix in reverse order to get the desired effect.
         */

        if (parent != null) {
            localToWorld = parent.getLocalToWorld();
        } else {
            localToWorld = new Matrix4f();
        }

        localToWorld.translate(position);
        localToWorld.rotate(rotation);
        localToWorld.scale(scale);

        return localToWorld;
    }

    public Matrix4f getWorldToLocal() {
        worldToLocal = new Matrix4f();

        getLocalToWorld().invert(worldToLocal);

        return worldToLocal;
    }

    public Vector3f getPosition() {
        return position;
    }

    public Quaternionf getRotation() {
        return rotation;
    }

    public Vector3f getScale() {
        return scale;
    }

    public Object3d getParent() {
        return parent;
    }

    public ArrayList<Object3d> getChildren() {
        return children;
    }

    public void destroy() {
        position = null;
        rotation = null;
        scale = null;

        localToWorld = null;
        worldToLocal = null;

        parent = null;
        children = null;
    }
}

@@ -96,44 +96,102 @@
 d %7B%0A
+    
 %0A    
-//TODO: matrix caching booleans
+private boolean localToWorldNeedsUpdate;%0A    private boolean worldToLocalNeedsUpdate;
 %0A%0A  
@@ -459,24 +459,105 @@
 bject3d() %7B%0A
+        localToWorldNeedsUpdate = true;%0A        worldToLocalNeedsUpdate = true;%0A%0A
         posi
@@ -796,32 +796,221 @@
 ist%3C%3E();%0A    %7D%0A%0A
+    private void setUpdateFlags(boolean b) %7B%0A        localToWorldNeedsUpdate = b;%0A        worldToLocalNeedsUpdate = b;%0A%0A        children.forEach((child) -%3E child.setUpdateFlags(b));%0A    %7D%0A%0A
     public void 
@@ -1288,16 +1288,46 @@
 add(v);%0A
+        setUpdateFlags(true);%0A
     %7D%0A%0A 
@@ -1874,24 +1874,54 @@
 es), axis);%0A
+        setUpdateFlags(true);%0A
     %7D%0A%0A    p
@@ -2722,16 +2722,46 @@
 *= v.z;%0A
+        setUpdateFlags(true);%0A
     %7D%0A%0A 
@@ -2809,14 +2809,30 @@
 cale
-.mul(f
+(new Vector3f(f, f, f)
 );%0A 
@@ -2873,24 +2873,183 @@
 ToWorld() %7B%0A
+        if (localToWorldNeedsUpdate) %7B%0A            updateLocalToWorld();%0A        %7D%0A        return localToWorld;%0A    %7D%0A%0A    private void updateLocalToWorld() %7B%0A
         /*%0A 
@@ -3756,25 +3756,24 @@
 scale);%0A
-%0A
         
 return l
@@ -3756,39 +3756,32 @@
 scale);%0A        
-return 
 localToWorld;%0A  
@@ -3768,32 +3768,51 @@
     localToWorld
+NeedsUpdate = false
 ;%0A    %7D%0A%0A    pub
@@ -3844,32 +3844,36 @@
 cal() %7B%0A        
+if (
 worldToLocal = n
@@ -3868,35 +3868,150 @@
 dToLocal
- = new Matrix4f();%0A
+NeedsUpdate) %7B%0A            updateWorldToLocal();%0A        %7D%0A        return worldToLocal;%0A    %7D%0A%0A    private void updateWorldToLocal() %7B
 %0A       
@@ -4051,25 +4051,24 @@
 Local);%0A
-%0A
         
 return w
@@ -4051,39 +4051,32 @@
 Local);%0A        
-return 
 worldToLocal;%0A  
@@ -4063,32 +4063,51 @@
     worldToLocal
+NeedsUpdate = false
 ;%0A    %7D%0A%0A    pub

add matrix caching booleans to object3d