public interface UpdateCallback {
	public void update(double deltaTime);
} public class World  {
    private MainLoop mainLoop;
    private Thread mloopThread = null;
    private boolean isRunning = false;

    /**
     * Initializes the elements to create the window
     *
     * @param pInitX      initX
     * @param pInitY      initY
     * @param pInitWidth  initWidth
     * @param pInitHeight initHeight
     * @param pInitVSYNC  initVSYNC
     */
    public World(int pInitX, int pInitY, int pInitWidth, int pInitHeight, boolean pInitVSYNC) {
        this.mainLoop = new MainLoop(pInitX, pInitY, pInitWidth, pInitHeight, pInitVSYNC);
    }

    /**
     * Initializes the elements to create the window
     *
     * @param pInitX      initX
     * @param pInitY      initY
     * @param pInitWidth  initWidth
     * @param pInitHeight initHeight
     */
    public World(int pInitX, int pInitY, int pInitWidth, int pInitHeight) {
        this.mainLoop = new MainLoop(pInitX, pInitY, pInitWidth, pInitHeight);
    }

    public World() {
        this.mainLoop = new MainLoop();
    }

    public void launch() {
        if(isRunning) {
            throw new Error("World is already running (multiple calls to launch() method)");
        }
        isRunning = true;
        mloopThread = new Thread(mainLoop);
        mloopThread.start();
    }

    /**
     * Input of critical section, allows to protect the resource share loop.
     * Stop the game loop and stop the thread that manage the world.
     */
    public void terminate() throws InterruptedException {
        mainLoop.close();
    }

    public void waitFortermination() {
        try {
            mloopThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    /**
     * Create an item with the specified parameters and add it to the  world
     *
     * @param id        id
     * @param x         x coordinate
     * @param y         y coordinate
     * @param z         z coordinate
     * @param pScale    scale
     * @param pMesh     mesh
     * @return the item
     */
    public ItemObject createItemObject(String id, float x, float y, float z, float pScale, Mesh pMesh) {
        ItemObject lItem = new ItemObject(id, new Vector3f(x, y, z)
                , new Quaternionf(), pScale, pMesh);

        mainLoop.addToScene(lItem);
        return lItem;
    }

    /**
     * Create a mesh with the specified parameters
     * MeshOld won't be load into the grahpic cards unless you bind it to an item
     *
     * @param pVertices    vetices
     * @param pTextCoords  texture coordonates
     * @param pNormals     normals
     * @param pIndices     indices
     * @param pWeight      weight
     * @param rgb          rgb array
     * @param pTextureName texture path
     * @return the mesh
     */
    public Mesh createMesh(float[] pVertices, float[] pTextCoords, float[] pNormals, int[] pIndices, int pWeight, float[] rgb, String pTextureName) {
        if (rgb.length != 3) {
            throw new RuntimeException("RGB must represent 3 colors");
        }
        Vector3f lMaterialColor = new Vector3f(rgb[0], rgb[1], rgb[2]);
        Material lMaterial = new Material(lMaterialColor);
        //Texture part
        if (pTextureName != null && !pTextureName.isEmpty()) {
            Texture lTexture = mainLoop.fetchTexture(pTextureName);
            if (lTexture == null) {
                lTexture = new Texture(pTextureName);
            } else {
                System.err.println("[Warning] Texture not found: " + pTextureName);
            }
            lMaterial.setTexture(lTexture);
        }
        Mesh lMesh = new Mesh(pVertices, pTextCoords, pNormals, pIndices, pWeight);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        lMesh.setMaterial(lMaterial);
        return lMesh;
    }

    /**
     * Create a mesh with the specified parameters
     * MeshOld won't be load into the grahpic cards unless you bind it to an item
     *
     * @param pVertices    vetices
     * @param pNormals     normals
     * @param pIndices     indices
     * @return the mesh
     */
    public Mesh createMesh(float[] pVertices, float[] pNormals, int[] pIndices, float[] rgb) {
        if (rgb.length != 3) {
            throw new RuntimeException("RGB must represent 3 colors");
        }
        Vector3f lMaterialColor = new Vector3f(rgb[0], rgb[1], rgb[2]);
        Material lMaterial = new Material(lMaterialColor);
        Mesh lMesh = new Mesh(pVertices, pNormals, pIndices);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        lMesh.setMaterial(lMaterial);
        return lMesh;
    }



    /**
     * Create a bounding box with the specified parameters and add it to the  world
     *
     * @param id        id
     * @param x         x coordinate
     * @param y         y coordinate
     * @param z         z coordinate
     * @param pScale    scale
     * @return BoundingBox
     */
    public HitBox createHitBox(String id, float x, float y, float z, float pScale, float xLength, float yLength, float zLength){
        HitBox hb = new HitBox(id, new Vector3f(x, y, z), new Quaternionf(), pScale, xLength, yLength, zLength);
        mainLoop.addToScene(hb);
        return hb;

    }

    /**
     * Remove the specified item from the world
     *
     * @param pItem the item
     */
    public void removeItem(ItemObject pItem) {
        mainLoop.removeFromScene(pItem);
    }

    public boolean isInCollision(HitBox hb1, HitBox hb2) {
        return hb1.collidesWith(hb2);
    }

    public void registerUpdateCallback(UpdateCallback cb) {
        mainLoop.registerUpdateCallback(cb);
    }

    public void registerInputCallback(InputCallback callback) {
        mainLoop.registerInputCallback(callback);
    }

    //3D click
    public void registerMouseCallback(Mouse3DClickCallBack callback) {
        mainLoop.registerMouse3DClickCallBack(callback);
    }




    /**
     * Gets an empty camera list
     */
    public void clearCameras() {
        mainLoop.clearCameras();
    }

    /**
     * Adds a camera and its index in the list of cameras (not in live)
     *
     * @param pIndex  index
     * @param pCamera camera
     */
    public void addCamera(int pIndex, Camera pCamera) {
        mainLoop.addCamera(pIndex, pCamera);
    }

    public Camera getCamera() {
        return mainLoop.getCamera();
    }

    public void setCamera(Camera pCamera) {
        mainLoop.setCamera(pCamera);
    }

    public Vector<Camera> getCamerasList() {
        return mainLoop.getCamerasList();
    }

    /**
     * Create a group of item
     *
     * @return group of item
     */
    public ItemGroup createGroup(String id) {
        return mainLoop.createGroup(id);
    }

    /**
     * Remove a group of item from the world
     *
     * @param pGroup the specified group
     */
    public void removeGroup(ItemGroup pGroup) {
        mainLoop.removeGroup(pGroup);
    }

    public Vector<ItemGroup> getItemGroupArrayList() {
        return mainLoop.getItemGroupArrayList();
    }

    public SceneLight getSceneLight() {
        return mainLoop.getSceneLight();
    }

    /**
     * Create a skybox with all its parameters and replace the current skybox
     *
     * @param pWidth  width
     * @param pLength length
     * @param pHeight height
     * @param pR      r
     * @param pG      g
     * @param pB      b
     */
    public void setSkybox(float pWidth, float pLength, float pHeight, float pR, float pG, float pB) {
        Skybox lSkybox = new Skybox(pWidth, pLength, pHeight, new Vector3f(pR, pG, pB));
        setSkybox(lSkybox);
    }

    public void removeSkybox() {
        mainLoop.removeSkybox();
    }

    public Skybox getSkybox() {
        return mainLoop.getSkybox();
    }

    /**
     * Allows to change the skybox by the new Skybox passed in arguments.
     * If a skybox already exists, it's added to the list of mSkyboxToBeRemoved
     *
     * @param pSkybox skybox
     */
    public void setSkybox(Skybox pSkybox) {
        mainLoop.setSkybox(pSkybox);
    }

} public class HitBox extends ItemObject {
    /**
     * @param id          Name of the HitBox
     * @param orientation Initial Rotation
     * @param position    Initial Position
     * @param scale       HitBox scale
     */
    public HitBox(String id, Vector3f position, Quaternionf orientation, float scale
            , float xLength, float yLength, float zLength) {
        super(id, position, orientation, scale, MeshBuilder.generateBoundingBox(xLength, yLength, zLength));getMesh().getMaterial().setColor(new Vector3f(0, 255, 0));
    }


    /**
     * Testing the collision with another HitBox
     *
     * @param item the second hitbox to test collision with
     * @return true if item is touching the hitbox which is calling the method
     */
    public boolean collidesWith(HitBox item) {
        ArrayList<Vector4f> listVertexboundingBox1 = tabToListVertex(this);
        ArrayList<Vector4f> listVertexboundingBox2 = tabToListVertex(item);

        // index of faces
        // implication of vertex in faces
        // example: face(0,1,2,3) / face(1,2,6,5) / ...
        int[] tabIndexFaces = {0, 1, 2, 3, 1, 2, 6, 5, 0, 3, 7, 4, 1, 5, 4, 0, 2, 3, 7, 6, 4, 5, 6, 7};

        // index of edges
        // implication of vertex in edges
        // example: edge(0,1) / edge(1,2) / edge(2,3) / ...
        int[] tabIndexEdges = {0, 1, 1, 2, 2, 3, 3, 0, 1, 5, 5, 4, 4, 0, 5, 6, 4, 7, 6, 7, 6, 2, 3, 7};

        // test if edges of boundingBox1 intersect faces of boundingBox2
        for (int i = 0; i < tabIndexEdges.length; i += 2)
            for (int j = 0; j < tabIndexFaces.length; j += 4)
                if (isIntersectSegmentAndFace(listVertexboundingBox1.get(tabIndexEdges[i]), listVertexboundingBox1.get(tabIndexEdges[i + 1]),
                        listVertexboundingBox2.get(tabIndexFaces[j]), listVertexboundingBox2.get(tabIndexFaces[j + 1]),
                        listVertexboundingBox2.get(tabIndexFaces[j + 2]), listVertexboundingBox2.get(tabIndexFaces[j + 3])))
                    return true;
        return false;
    }

    /**
     * Testing the inclusion of a hitbox in another
     *
     * @param item the second Hitbox to test the inclusion
     * @return true if the calling Hitbox is Contained in the one placed as an argument
     */
    public boolean isIncludedIn(HitBox item) {

        ArrayList<Vector4f> listVertexboundingBox1 = tabToListVertex(this);
        ArrayList<Vector4f> listVertexboundingBox2 = tabToListVertex(item);

        int[] tabIndexFaces = {0, 1, 2, 3, 1, 2, 6, 5, 0, 3, 7, 4, 1, 5, 4, 0, 2, 3, 7, 6, 4, 5, 6, 7};
        int[] tabOppositeVertice = {4, 3, 2, 2, 1, 0 };
        //if the two hitboxes collide there is no containment
        if (!this.collidesWith(item)) {
            boolean isIn = true;
            int k = 0;
            for(int i =0; i<tabIndexFaces.length;i+=4){
                //Using 3 points to create a plane for the face
                Vector4f A = listVertexboundingBox2.get(tabIndexFaces[i]);
                Vector4f B = listVertexboundingBox2.get(tabIndexFaces[i+1]);
                Vector4f C = listVertexboundingBox2.get(tabIndexFaces[i+2]);
                // Creating two vectors with these points
                Vector3f AB = new Vector3f(B.x-A.x, B.y-A.y,B.z-A.z);
                Vector3f AC = new Vector3f(C.x-A.x, C.y-A.y,C.z-A.z);
                //Cross product of the two vectors
                Vector3f crossProductABC = new Vector3f((AB.y*AC.z)-(AB.z*AC.y),(AB.z*AC.x)-(AB.x*AC.z),(AB.x*AC.y)-(AB.y*AC.x));
                // Taking a point that we know it don't belongs to the plane
                Vector4f opposite = listVertexboundingBox2.get(tabOppositeVertice[k]);
                Vector3f opp = new Vector3f(opposite.x-A.x, opposite.y-A.y, opposite.z-A.z);
                // computing the dot product to get it's sign and to know what is "the right face" of the plane (negative dot product or positive)
                float dotProductABC_Op = crossProductABC.x*opp.x+crossProductABC.y*opp.y+crossProductABC.z*opp.z;


                //Taking one point of the contained HitBox
                Vector4f point = listVertexboundingBox1.get(0);
                Vector3f p = new Vector3f(point.x-A.x, point.y-A.y , point.z-A.z);
                // computing the dot product to get it's sign
                float  dotProductABC_P = crossProductABC.x*p.x+crossProductABC.y*p.y+crossProductABC.z*p.z;
                //if the two dot products have different signs, the point is not on the right face so it's not contained in the HitBox
                if(dotProductABC_Op*dotProductABC_P<0){
                    isIn=false;
                }
                k++;
            }
            //returns true if all the dotProducts have the right sign
            return isIn;
        }else{
            return false;
        }
    }



    /**
     * Testing the inclusion of a hitbox in another
     *
     * @param item the second Hitbox to test the inclusion
     * @return true if the calling Hitbox contains the one placed as an argument
     */
    public boolean includes(HitBox item) {

        ArrayList<Vector4f> listVertexboundingBox1 = tabToListVertex(item);
        ArrayList<Vector4f> listVertexboundingBox2 = tabToListVertex(this);

        int[] tabIndexFaces = {0, 1, 2, 3, 1, 2, 6, 5, 0, 3, 7, 4, 1, 5, 4, 0, 2, 3, 7, 6, 4, 5, 6, 7};
        int[] tabOppositeVertice = {4, 3, 2, 2, 1, 0 };
        //if the two hitboxes collide there is no containment
        if (!this.collidesWith(item)) {
            boolean isIn = true;
            int k = 0;
            for(int i =0; i<tabIndexFaces.length;i+=4){
                //Using 3 points to create a plane for the face
                Vector4f A = listVertexboundingBox2.get(tabIndexFaces[i]);
                Vector4f B = listVertexboundingBox2.get(tabIndexFaces[i+1]);
                Vector4f C = listVertexboundingBox2.get(tabIndexFaces[i+2]);
                // Creating two vectors with these points
                Vector3f AB = new Vector3f(B.x-A.x, B.y-A.y,B.z-A.z);
                Vector3f AC = new Vector3f(C.x-A.x, C.y-A.y,C.z-A.z);
                //Cross product of the two vectors
                Vector3f crossProductABC = new Vector3f((AB.y*AC.z)-(AB.z*AC.y),(AB.z*AC.x)-(AB.x*AC.z),(AB.x*AC.y)-(AB.y*AC.x));
                // Taking a point that we know it don't belongs to the plane
                Vector4f opposite = listVertexboundingBox2.get(tabOppositeVertice[k]);
                Vector3f opp = new Vector3f(opposite.x-A.x, opposite.y-A.y, opposite.z-A.z);
                // computing the dot product to get it's sign and to know what is "the right face" of the plane (negative dot product or positive)
                float dotProductABC_Op = crossProductABC.x*opp.x+crossProductABC.y*opp.y+crossProductABC.z*opp.z;


                //Taking one point of the contained HitBox
                Vector4f point = listVertexboundingBox1.get(0);
                Vector3f p = new Vector3f(point.x-A.x, point.y-A.y , point.z-A.z);
                // computing the dot product to get it's sign
                float  dotProductABC_P = crossProductABC.x*p.x+crossProductABC.y*p.y+crossProductABC.z*p.z;
                //if the two dot products have different signs, the point is not on the right face so it's not contained in the HitBox
                if(dotProductABC_Op*dotProductABC_P<0){
                    isIn=false;
                }
                k++;
            }
            //returns true if all the dotProducts have the right sign
            return isIn;
        }else{
            return false;
        }
    }





    /**
     * Returns true if the segment passes through the face
     * Segment define by 2 points (segmentStart, segmentEnd)
     * face define by 4 points (corner1Face, corner2Face, corner3Face, corner4Face)
     *
     * @param segmentStart
     * @param segmentEnd
     * @param corner1Face
     * @param corner2Face
     * @param corner3Face
     * @param corner4Face
     * @return
     */
    public static boolean isIntersectSegmentAndFace(Vector4f segmentStart, Vector4f segmentEnd, Vector4f corner1Face, Vector4f corner2Face, Vector4f corner3Face, Vector4f corner4Face) {

        Vector3f vecSegment = new Vector3f(segmentEnd.x - segmentStart.x, segmentEnd.y - segmentStart.y, segmentEnd.z - segmentStart.z);

        Vector3f vecCD = new Vector3f(corner2Face.x - corner1Face.x, corner2Face.y - corner1Face.y, corner2Face.z - corner1Face.z);

        Vector3f vecCF = new Vector3f(corner4Face.x - corner1Face.x, corner4Face.y - corner1Face.y, corner4Face.z - corner1Face.z);

        float a1 = vecSegment.x;
        float a2 = vecSegment.y;
        float a3 = vecSegment.z;

        float b1 = -vecCD.x;
        float b2 = -vecCD.y;
        float b3 = -vecCD.z;

        float c1 = -vecCF.x;
        float c2 = -vecCF.y;
        float c3 = -vecCF.z;

        float d1 = segmentStart.x - corner1Face.x;
        float d2 = segmentStart.y - corner1Face.y;
        float d3 = segmentStart.z - corner1Face.z;


        float det = -(a1 * (b2 * c3 - c2 * b3) - b1 * (a2 * c3 - a3 * c2) + c1 * (a2 * b3 - a3 * b2));
        if (det != 0) {
            float x = (d1 * (b2 * c3 - c2 * b3) - b1 * (d2 * c3 - d3 * c2) + c1 * (d2 * b3 - d3 * b2)) / (det);
            float y = (a1 * (d2 * c3 - c2 * d3) - d1 * (a2 * c3 - a3 * c2) + c1 * (a2 * d3 - a3 * d2)) / (det);
            float z = (a1 * (b2 * d3 - d2 * b3) - b1 * (a2 * d3 - a3 * d2) + d1 * (a2 * b3 - a3 * b2)) / (det);


            if (x >= 0 && x <= 1 && y >= 0 && y <= 1 && z >= 0 && z <= 1)
                return true;
            else
                return false;

        }
        return false;
    }
    //Utility methods
    /**
     * Construct a list of vertex with the vertices of specified item
     *
     * @param item item
     * @return listVertex
     */
    public static ArrayList<Vector4f> tabToListVertex(HitBox item) {
        ArrayList<Vector4f> listVertex = new ArrayList<>();
        for (int i = 0; i < item.getMesh().getVertices().length; i += 3) {
            Vector4f vec = new Vector4f(item.getMesh().getVertices()[i], item.getMesh().getVertices()[i + 1]
                    , item.getMesh().getVertices()[i + 2], 1.0f);
            if (!isVecAlreadyAdd(listVertex, vec))
                listVertex.add(vec);
        }

        //get Real position of vertex after rotation or translation
        for (int i = 0; i < listVertex.size(); i++)
            listVertex.set(i, listVertex.get(i).mul(item.getWorldMatrix()));


        return listVertex;
    }

    public static boolean isVecAlreadyAdd(ArrayList<Vector4f> listVertex, Vector4f vec) {
        for (Vector4f vector : listVertex) {
            if (vector.x == vec.x && vector.y == vec.y && vector.z == vec.z)
                return true;
        }
        return false;
    }

} public class ItemGroup extends Item {
    private Map<String, Item> items;

    public ItemGroup(String id, Vector3f position, Quaternionf orientation, float scale){
       super(id, position, orientation, scale);
       items = new HashMap<String, Item>();
    }

    public ItemGroup(String id) {
        this(id, new Vector3f(0f, 0f, 0f), new Quaternionf(), 1.0f);
    }

    /** this method adds an item to the group, updates the weight and the center.
     *
     * @param id the identity of the item (must be unique)
     * @param item the Item to add
     * @throws Error if the group already contains an item with the same id
     */
    public void add(String id, Item item) {
        if(items.containsKey(id)) {
            throw new Error("The group already contains an item with identity: " + id);
        }
        items.put(id, item);
        positionAtCentroid();
    }


    /** remove the item i from the group, update the center and the weight
     *
     * @param id the item to remove
     * @throws Error if there is no item with such identity inside the group
     */
    public void remove(String id) {
        if(!items.containsKey(id)) {
            throw new Error("The group does not contains an item with identity: " + id);
        }
        items.remove(id);
        positionAtCentroid();
    }

    public void invalidate() {
        // do nothing for group
    }


    public Vector3f computeCentroid() {
        double x = 0, y = 0, z = 0;
        for (Item item  : items.values()) {
            x += item.getPosition().x ;
            y += item.getPosition().y ;
            z += item.getPosition().z ;

        }
        x /= (items.size()*1.f);
        y /= (items.size()*1.f);
        z /= (items.size()*1.f);
        return new Vector3f((float) x, (float) y, (float) z);
    }

    /**
     * Update the center of the group so that it is placed at the centroid
     * wrt. the contained items.
     * This must be called when the position of an item of the group also
     * changes.
     */
    public void positionAtCentroid(){
        setPosition(computeCentroid());
    }

    @Override
    public void translate(float tx, float ty, float tz) {
        super.translate(tx, ty, tz);
        for(Item item : items.values()) {
            item.translate(tx, ty, tz);
        }
    }

    /**
     * Fetch an item contained in the group
     * @param id the identifier of the item in the group
     * @return the found item, if any
     * @throws Error in case there is no such child
     */
    public Item fetchItem(String id) {
        Item child = items.get(id);
        if (child == null) {
            throw new Error("No such child in group: " + id);
        }
        return child;
    }

    public HitBox fetchHitBox(String id) {
        Item item = fetchItem(id);
        if(!(item instanceof HitBox)) {
            throw new Error("The group does not contain an hitbox with id: "+id);
        }
        return (HitBox) item;
    }

    @Override
    public void rotateX(float angle) {
        //super.rotateX(angle);
        for(Item item : items.values()) {
            item.rotateXAround(angle, position);
        }
        positionAtCentroid();
    }

    @Override
    public void rotateY(float angle) {
        //super.rotateY(angle);
        for(Item item : items.values()) {
            item.rotateYAround(angle, position);
        }
        positionAtCentroid();
    }

    @Override
    public void rotateZ(float angle) {
        //super.rotateZ(angle);
        for(Item item : items.values()) {
            item.rotateZAround(angle, position);
        }
        positionAtCentroid();
    }

    @Override
    public void rotateXYZ(float angleX, float angleY, float angleZ) {
        //super.rotateXYZ(angleX, angleY, angleZ);
        for(Item item : items.values()) {
            item.rotateXYZAround(angleX, angleY, angleZ, position);
        }
        positionAtCentroid();
    }

    @Override
    public void rotateAxis(float angle, Vector3f axis) {
        //super.rotateAxis(angle, axis);
        for(Item item : items.values()) {
            item.rotateAxisAround(angle, axis.normalize(), position);
        }
        positionAtCentroid();
    }

    @Override
    public void rotateAxisAround(float angle, Vector3f axis, Vector3f center) {
        //super.rotateAxisAround(angle, axis, center);
        for(Item item : items.values()) {
            item.rotateAxisAround(angle, axis.normalize(), center);
        }
        positionAtCentroid();
    }

    /**
     * Rotation along three axes (Euler angles rotation), around center
     * @param angleX angle of rotation along axis X (in degrees)
     * @param angleY same for axis Y
     * @param angleZ same for axis Z
     * @param center the center of rotation
     */
    public void rotateXYZAround(float angleX, float angleY, float angleZ, Vector3f center) {
        AxisAngle4f aaxis = new AxisAngle4f(new Quaternionf().rotationXYZ(toRadians(angleX)
                , toRadians(angleY)
                , toRadians(angleZ))).normalize();
        rotateAxisAround(toDegrees(aaxis.angle), new Vector3f(aaxis.x, aaxis.y, aaxis.z), center);
    }


    /**
     * Rotate along X axis, around center
     * @param angle of rotation (in degree)
     * @param center the center of rotation
     */
    public void rotateXAround(float angle, Vector3f center) {
        rotateAxisAround(angle, new Vector3f(1.0f, 0.0f, 0.0f), center);
    }

    /**
     * Rotate along Y axis, around center
     * @param angle of rotation (in degree)
     * @param center the center of rotation
     */
    public void rotateYAround(float angle, Vector3f center) {
        rotateAxisAround(angle, new Vector3f(0.0f, 1.0f, 0.0f), center);
    }

    /**
     * Rotate along Z axis, around center
     * @param angle of rotation (in degree)
     * @param center the center of rotation
     */
    public void rotateZAround(float angle, Vector3f center) {
        rotateAxisAround(angle, new Vector3f(0.0f, 0.0f, 1.0f), center);
    }

    public void removeAll() {
        items.clear();
        position=new Vector3f(0f, 0f, 0f);
    }
} public class ItemObject extends Item {

    /** The mesh (geometry) of the object. */
    private Mesh mesh;

    /** The transformation matrix to world coordinates */
    private Matrix4f worldMatrix;



    public ItemObject(String id, Vector3f position, Quaternionf orientation, float scale, Mesh mesh){
        super(id, position, orientation, scale);
        this.mesh= mesh;
        worldMatrix = new Matrix4f();
        invalidate();
    }

    public void buildWorldMatrix() {
        worldMatrix.identity()
                .translate(getPosition())
                .rotate(getOrientation())
                .scale(getScale());
    }

    public Matrix4f getWorldMatrix() {
        return worldMatrix;
    }

    @Override
    public void invalidate() {
        buildWorldMatrix();
    }

    public void repelBy(Vector3f center, float dist) {
        Vector3f dif = new Vector3f(position.x - center.x, position.y - center.y, position.z - center.z);
        float norm = dif.length();
        if (norm != 0) {
            float move = (dist / norm) + 1;
            dif.mul(move);
            dif.add(center);
            position = dif;
        }
    }


    /**
     * Get the mesh (geometry) of the item
     */
    public Mesh getMesh(){ return mesh; }

    /**
     * Rotate along X axis
     * @param angle of rotation (in degree)
     */
    public void rotateX(float angle) {
        orientation.rotateX(toRadians(angle));
        invalidate();
    }

    /**
     * Rotate along Y axis
     * @param angle of rotation (in degree)
     */
    public void rotateY(float angle) {
        orientation.rotateY(toRadians(angle));
        invalidate();
    }

    /**
     * Rotate along Z axis
     * @param angle of rotation (in degree)
     */
    public void rotateZ(float angle) {
        orientation.rotateZ(toRadians(angle));
        invalidate();
    }

    /**
     * Rotation along three axes (Euler angles rotation)
     * @param angleX angle of rotation along axis X (in degrees)
     * @param angleY same for axis Y
     * @param angleZ same for axis Z
     */
    public void rotateXYZ(float angleX, float angleY, float angleZ) {
        orientation.rotateXYZ(toRadians(angleX), toRadians(angleY), toRadians(angleZ));
        invalidate();
    }

    /**
     * Rotation of given angle along an axis
     * @param angle angle of rotation in degrees
     * @param axis axis of rotation
     */
    public void rotateAxis(float angle, Vector3f axis) {
        orientation.rotateAxis(toRadians(angle), axis);
        invalidate();
    }

    /**
     * Rotation of given angle along an axis, and aroung the specified center
     * @param angle the angle of rotation (in degrees)
     * @param axis the axis of rotation
     * @param center the center of rotation
     */
    public void rotateAxisAround(float angle, Vector3f axis, Vector3f center) {
        axis = axis.normalize();
        // change orientation
        orientation.rotateAxis(toRadians(angle), axis);

        // change position
        new Matrix4f().rotateAround(new Quaternionf(new AxisAngle4f(toRadians(angle), axis)), center.x, center.y, center.z)
                .transformPosition(position);

//        new Matrix4f()
//                .translate(center)
//                .rotate(toRadians(angle), axis)
//                .translate(new Vector3f(center).negate())
//                .transformPosition(position);

        invalidate();
    }

    /**
     * Rotate along X axis, around center
     * @param angle of rotation (in degree)
     * @param center the center of rotation
     */
    public void rotateXAround(float angle, Vector3f center) {
        rotateAxisAround(angle, new Vector3f(1.0f, 0.0f, 0.0f), center);
    }

    /**
     * Rotate along Y axis, around center
     * @param angle of rotation (in degree)
     * @param center the center of rotation
     */
    public void rotateYAround(float angle, Vector3f center) {
        rotateAxisAround(angle, new Vector3f(0.0f, 1.0f, 0.0f), center);
    }

    /**
     * Rotate along Z axis, around center
     * @param angle of rotation (in degree)
     * @param center the center of rotation
     */
    public void rotateZAround(float angle, Vector3f center) {
        rotateAxisAround(angle, new Vector3f(0.0f, 0.0f, 1.0f), center);
    }

    /**
     * Rotation along three axes (Euler angles rotation), around center
     * @param angleX angle of rotation along axis X (in degrees)
     * @param angleY same for axis Y
     * @param angleZ same for axis Z
     * @param center the center of rotation
     */
    public void rotateXYZAround(float angleX, float angleY, float angleZ, Vector3f center) {
        if(!(angleX == 0 && angleY == 0 && angleZ == 0)) {
            AxisAngle4f aaxis = new AxisAngle4f(new Quaternionf().rotationXYZ(toRadians(angleX)
                    , toRadians(angleY)
                    , toRadians(angleZ))).normalize();
            System.out.println(aaxis.x + " " + aaxis.y + " " + aaxis.z + " ");
            rotateAxisAround(toDegrees(aaxis.angle), new Vector3f(aaxis.x, aaxis.y, aaxis.z), center);
        }
    }







} public class AmbientLight {
    private Vector3f mColor;
    private  float mIntensity;
    // Value put in the shader
    private Vector3f mShaderValue;

    /**
     * Constructor with the specified color and intensity
     * Create the shaderValue with the color and intensity.
     *
     * @param color     color
     * @param intensity intensity
     */
    public AmbientLight(Vector3f color, float intensity){
	this.mColor = color;
	this.mIntensity = intensity;
	this.mShaderValue= new Vector3f(color.x*intensity,color.y *intensity,color.z*intensity);
    }

    /**
     * Constructor with the specified color (cx,cy,cz) and intensity
     * Create the shaderValue with the color and intensity.
     *
     * @param red        Red value
     * @param green      Green value
     * @param blue       Blue value
     * @param intensity  intensity
     */
    public AmbientLight(float red, float green, float blue, float intensity){
	this.mColor = new Vector3f(red, green, blue);
	this.mIntensity = intensity;
	this.mShaderValue= new Vector3f(mColor.x*intensity,mColor.y *intensity,mColor.z*intensity);
    }

    /**
     * Constructor without parameters
     * Create the shaderValue with 1.
     */
    public AmbientLight(){
	this.mColor = new Vector3f(1f,1f,1f);
	this.mIntensity=1f;
	this.mShaderValue = new Vector3f(1f,1f,1f);
    }

    /**
     * Constructor with the specified intensity
     * Create the shaderValue with the color and intensity.
     *
     * @param intensity intensity
     */
    public AmbientLight(float intensity){
	this.mColor = new Vector3f(1f,1f,1f);
	this.mIntensity=intensity;
	this.mShaderValue = new Vector3f(mColor.x *intensity,mColor.y *intensity,mColor.z *intensity);
    }

    /**
     * Setter for the color.
     * Initialize the color.
     * Create the shaderValue with the color and intensity.
     *
     * @param color color
     */
    public void setColor(Vector3f color) {
	this.mColor = color;
	this.mShaderValue= new Vector3f(color.x *mIntensity,color.y *mIntensity,color.z *mIntensity);
    }

    /**
     * Initialize the intensity.
     * Create the shaderValue with the color and intensity.
     *
     * @param intensity intensity
     */
    public void setIntensity(float intensity) {
	this.mIntensity = intensity;
	this.mShaderValue= new Vector3f(mColor.x *intensity,mColor.y *intensity,mColor.z*intensity);
    }

    /**
     * getters
     */
    public Vector3f getColor() {
	return mColor;
    }

    public float getIntensity() {
	return mIntensity;
    }

    public Vector3f getShaderValue() {
	return mShaderValue;
    }	
}
 public class DirectionalLight extends AmbientLight {
    public Vector3f mDirection;

    /**
     * Constructor with the parameters direction and the intensity = 0.
     *
     * @param direction direction
     */
    public DirectionalLight(Vector3f direction){
	super(0);
	this.mDirection = direction;
    }

    /**
     * Constructor with the parameters source.
     *
     * @param source source
     */
    public DirectionalLight(DirectionalLight source){
	super(source.getColor(),source.getIntensity());
        mDirection=source.mDirection;
    }

    /**
     * Constructor with the parameters color, intensity and direction.
     *
     * @param color     color
     * @param intensity intensity
     * @param direction direction
     */
    public DirectionalLight(Vector3f color, float intensity,Vector3f direction){
	super(color, intensity);
	this.mDirection = direction;
    }

    /**
     * Constructor with the specified color (cx,cy,cz), intensity and direction (dx,dy,dz),
     *
     * @param red           Red value
     * @param green         Green value
     * @param blue          Blue value
     * @param intensity     intensity
     * @param dx            Direction x
     * @param dy            Direction y
     * @param dz            Direction z
     */
    public DirectionalLight(float red, float green, float blue, float intensity, float dx, float dy, float dz){
	super(red, green, blue, intensity);
	this.mDirection = new Vector3f(dx, dy, dz);
    }
	
    //Default: vertical white light
    public DirectionalLight(){
	super(0);
        mDirection = new Vector3f(0f,-1f,0f);
    }

    /**
     * getter and setters
     */
    public Vector3f getDirection() {
	return mDirection;
    }

    public void setDirection(Vector3f direction) {
	this.mDirection = direction;
    }
	
    public void setDirection(float x,float y,float z){
	this.mDirection=new Vector3f(x,y,z);
    }

}
 public class Mesh {
    private final List<Integer> vboIdList;
    //reference to the VAO(wrapper)
    private int mVaoId;
    //VBO's ID

    //VBO
    private float[] mVertices;//mVertices
    private float[] mNormals;
    private int[] mIndices; //order into which  mVertices should be drawn by referring to their  position
    private float[] mTextCoords;
    private int mWeight;  // the mWeight of an object in a group (e.g. a mass in a group planets)
    private Material mMaterial;
    private Map<String, String> mOptionalAttributes;
    //strategy when we draw the elements
    private MeshDrawingStrategy mDrawingStrategy;

    /**
     * Construct a Mesh with the specified mMaterial , mVertices, mNormals , mTextureCoordinate and mIndices.
     *
     * @param pVertices   Vertex array
     * @param pTextCoords texture coodinate
     * @param pNormals    mNormals
     * @param pIndices    order into which  mVertices should be drawn by referring to their  position
     */
    public Mesh(float[] pVertices, float[] pTextCoords, float[] pNormals, int[] pIndices) {
        this(pVertices, pTextCoords, pNormals, pIndices, pVertices.length);
    }

    /**
     * Construct a Mesh with the specified mVertices, mNormals and mIndices.
     *
     * @param pVertices   Vertex array
     * @param pNormals    Normal vectors
     * @param pIndices    Triangles
     */
    public Mesh(float[] pVertices, float[] pNormals, int[] pIndices) {
        this(pVertices, null, pNormals, pIndices, pVertices.length);
    }

    /**
     * Construct a Mesh with the specified  mVertices, mNormals, mIndices , mTextureCoordinate and mWeight
     *
     * @param pVertices   Vertex array
     * @param pTextCoords texture coodinate
     * @param pNormals    mNormals
     * @param pIndices    order into which  mVertices should be drawn by referring to their  position
     * @param pWeight     mWeight numbre of vertices
     */
    public Mesh(float[] pVertices, float[] pTextCoords, float[] pNormals, int[] pIndices, int pWeight) {
        this.mMaterial = new Material();
        this.mVertices = pVertices;
        this.mNormals = pNormals;
        this.mIndices = pIndices;
        this.mWeight = pWeight;
        this.mTextCoords = pTextCoords == null ? new float[1] : pTextCoords;
        this.mOptionalAttributes = new HashMap<>();
        this.vboIdList = new ArrayList<>();
    }

    /**
     * Initialize  vertex, mNormals, mIndices and mTextureCoordinate buffer
     */
    public void init() {
        //initialization order is important do not change unless you know what to do
        mVaoId = glGenVertexArrays();
        glBindVertexArray(mVaoId);

        //Initialization of VBO

        //VBO of vertex layout 0 in vertShader.vs
        FloatBuffer verticeBuffer = BufferUtils.createFloatBuffer(mVertices.length);
        verticeBuffer.put(mVertices).flip();
        int lVboVertexId = glGenBuffers();
        vboIdList.add(lVboVertexId);
        glBindBuffer(GL_ARRAY_BUFFER, lVboVertexId);
        glBufferData(GL_ARRAY_BUFFER, verticeBuffer, GL_STATIC_DRAW);

        //We explain to OpenGL how to read our Buffers.
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

        // Texture coordinates VBO
        int lVboCoordTextureId = glGenBuffers();
        vboIdList.add(lVboCoordTextureId);
        FloatBuffer textCoordsBuffer = BufferUtils.createFloatBuffer(mTextCoords.length);
        textCoordsBuffer.put(mTextCoords).flip();
        glBindBuffer(GL_ARRAY_BUFFER, lVboCoordTextureId);
        glBufferData(GL_ARRAY_BUFFER, textCoordsBuffer, GL_STATIC_DRAW);
        glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);

        //VBO of mNormals layout 2 in vertShader.vs
        FloatBuffer normBuffer = BufferUtils.createFloatBuffer(mNormals.length);
        normBuffer.put(mNormals).flip();
        int lVboNormId = glGenBuffers();
        vboIdList.add(lVboNormId);
        glBindBuffer(GL_ARRAY_BUFFER, lVboNormId);
        glBufferData(GL_ARRAY_BUFFER, normBuffer, GL_STATIC_DRAW);

        //We explain to OpenGL how to read our Buffers.
        glVertexAttribPointer(2, 3, GL_FLOAT, false, 0, 0);

        //VBO of mIndices
        IntBuffer indicesBuffer = BufferUtils.createIntBuffer(mIndices.length);
        indicesBuffer.put(mIndices).flip();
        int lVboIndicesId = glGenBuffers();
        vboIdList.add(lVboIndicesId);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lVboIndicesId);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }

    /**
     * Render the specified items
     *
     * @param pItems         item
     * @param pShaderProgram shaderProgram
     * @param pViewMatrix    viewMatrix
     */
    public void render(List<ItemObject> pItems, ShaderProgram pShaderProgram, Matrix4f pViewMatrix) {

        //initRender
        initRender();


        pShaderProgram.setUniform("material", mMaterial);
        for (ItemObject lItem : pItems) {
            //can be moved to Item class
            Matrix4f modelViewMat = new Matrix4f(pViewMatrix).mul(lItem.getWorldMatrix());
            pShaderProgram.setUniform("modelViewMatrix", modelViewMat);
            if (mDrawingStrategy != null) {
                //delegate the drawing
                mDrawingStrategy.drawMesh(this);
            } else {
                LoggerYAW.getLogger().severe("No drawing strategy has been set for the mesh");
            }

        }
        //end render

        endRender();
    }

    public void cleanUp() {
        //de-allocation of VAO and VBO
        glDisableVertexAttribArray(0);

        // Delete the VBO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        for (int vboId : vboIdList) {
            glDeleteBuffers(vboId);
        }
        Texture texture = mMaterial.getTexture();
        if (texture != null) {
            texture.cleanup();
        }
        // Delete the VAO
        glBindVertexArray(0);
        glDeleteVertexArrays(mVaoId);


    }

    /**
     * Returns the value to which the specified @attributeName is mapped,
     * or null if this map contains no mapping for the key.
     *
     * @param pAttributeName name of the attribute (most of the time it will be clojure keywords)
     * @return the corresponding value if exist null otherwise
     */
    public Object getAttribute(String pAttributeName) {
        return this.mOptionalAttributes.get(pAttributeName);
    }

    /**
     * Copies all of the mappings from the specified map to this map (optional operation).
     * The effect of this call is equivalent to that of calling put(k, v) on this map once for each mapping
     * from key k to value v in the specified map.
     * The behavior of this operation is undefined if the specified map is modified while the operation is in progress.
     *
     * @param pOptionalAttributes mappings to be stored in this map
     */
    public void putOptionalAttributes(Map<String, String> pOptionalAttributes) {
        this.mOptionalAttributes.putAll(pOptionalAttributes);
    }

    public void initRender() {

        Texture texture = mMaterial != null ? mMaterial.getTexture() : null;
        if (texture != null) {
            //load the texture if needed
            if (!texture.isActivated()) {
                texture.init();
            }
            // Activate first texture bank
            glActiveTexture(GL_TEXTURE0);


            // Bind the texture
            texture.bind();
        }

        // Draw the mesh
        glBindVertexArray(mVaoId);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glEnableVertexAttribArray(2);

    }

    protected void endRender() {
        // Restore state
        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        glDisableVertexAttribArray(2);
        glBindVertexArray(0);

        glBindTexture(GL_TEXTURE_2D, 0);
    }

    public float[] getVertices() {
        return mVertices;
    }

    public Material getMaterial() {
        return mMaterial;
    }

    public void setMaterial(Material pMaterial) {
        this.mMaterial = pMaterial;
    }

    public float[] getNormals() {
        return mNormals;
    }

    public int[] getIndices() {
        return mIndices;
    }

    public int getWeight() {
        return mWeight;
    }

    public void setDrawingStrategy(MeshDrawingStrategy pDrawingStrategy) {
        mDrawingStrategy = pDrawingStrategy;
    }

    public void setTextCoords(float[] pTextCoord) {
        mTextCoords = pTextCoord;
    }
} public class MeshBuilder {
    private static final String BLOCK_MESH = "block";
    private static final String GROUND_MESH = "ground";
    private static final String HALF_BLOCK_MESH = "half-block";
    private static final String PYRAMID_MESH = "pyramid";
    private static final String OCTAHEDRON_MESH = "octahedron";
    private static final String TETRAHEDRON_MESH = "tetrahedron";
    //

    //TODO remove the text coord array, only use it for test

    /**
     * Generate a mesh with the specified material (cx, cy, cz, reflectance), width, length and height
     *
     * @param meshType meshType
     * @param xLength  width
     * @param yLength  length
     * @param zLength  height
     * @param cx       Red value of the material
     * @param cy       Green  value of the material
     * @param cz       Blue  value of the material
     * @return Mesh
     */
    @Deprecated
    public static Mesh generate(String meshType, float xLength, float yLength, float zLength, float cx, float cy, float cz) {
        Mesh mesh = null;
        /*Warning default reflectance*/
        Material lMaterial = null;
        try {
            lMaterial = new Material(new Vector3f(cx, cy, cz));
        } catch (Exception pE) {
            pE.printStackTrace();
        }
        //System.out.println(meshType);
        switch (meshType) {
            case BLOCK_MESH:
                mesh = generateBlock(xLength, yLength, zLength);
                break;
            case GROUND_MESH:
                mesh = generateGround(xLength, yLength, zLength);
                break;
            case HALF_BLOCK_MESH:
                mesh = generateHalfBlock(xLength, yLength, zLength);
                break;
            case PYRAMID_MESH:
                mesh = generatePyramid(xLength, yLength, zLength);
                break;
            case OCTAHEDRON_MESH:
                mesh = generateOctahedron();
                break;
            case TETRAHEDRON_MESH:
                mesh = generateTetrahedron();
                break;
            default:
                System.out.println("error");
        }
        if (mesh != null) mesh.setMaterial(lMaterial);
        return mesh;
        //return generate(xLength, yLength, zLength, new Material(new Vector3f(cx, cy, cz), r));
    }

    /**
     * Generate a BlockMesh with the specified material , width, length and height.
     * Create vertices based on the specified width, length and height.
     * Normals and indices are hard coded
     *
     * @param xLength xLength
     * @param yLength yLength
     * @param zLength zLength
     * @return BlockMesh
     */
    @Deprecated
    public static Mesh generateBlock(float xLength, float yLength, float zLength) {
        float x = xLength / 2f;
        float y = yLength / 2f;
        float z = zLength / 2f;
        float[] vertices = new float[]{
                //Front face
                x, y, z, -x, y, z, -x, -y, z, x, -y, z,
                //Top face
                x, y, z, -x, y, z, x, y, -z, -x, y, -z,
                //Back face
                x, y, -z, -x, y, -z, -x, -y, -z, x, -y, -z,
                //Bottom face
                x, -y, z, -x, -y, z, x, -y, -z, -x, -y, -z,
                //Left face
                -x, y, z, -x, y, -z, -x, -y, z, -x, -y, -z,
                //Right face
                x, y, z, x, y, -z, x, -y, z, x, -y, -z};
        //for light
        float[] normals = {
                //Front face
                0, 0, 1f, 0, 0, 1f, 0, 0, 1f, 0, 0, 1f,
                //Top face
                0, 1f, 0, 0, 1f, 0, 0, 1f, 0, 0, 1f, 0,
                //Back face
                0, 0, -1f, 0, 0, -1f, 0, 0, -1f, 0, 0, -1f,
                //Bottom face
                0, -1f, 0, 0, -1f, 0, 0, -1f, 0, 0, -1f, 0,
                //Left face
                -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0,
                //Right face
                1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0};
        //mapping for the texture
        float[] textCoord = new float[]{
                0.0f, 0.0f,
                0.0f, 0.5f,
                0.5f, 0.5f,
                0.5f, 0.0f,

                0.0f, 0.0f,
                0.5f, 0.0f,
                0.0f, 0.5f,
                0.5f, 0.5f,

                // For text coords in top face
                0.0f, 0.5f,
                0.5f, 0.5f,
                0.0f, 1.0f,
                0.5f, 1.0f,

                // For text coords in right face
                0.0f, 0.0f,
                0.0f, 0.5f,

                // For text coords in left face
                0.5f, 0.0f,
                0.5f, 0.5f,

                // For text coords in bottom face
                0.5f, 0.0f,
                1.0f, 0.0f,
                0.5f, 0.5f,
                1.0f, 0.5f,
        };
        //mapping the triangle for each face
        int[] indices = new int[]{
                //Front face
                0, 1, 2, 0, 2, 3,
                //Top face
                4, 6, 5, 6, 7, 5,
                //Back face
                8, 11, 10, 8, 10, 9,
                //Bottom Face
                14, 12, 13, 14, 13, 15,
                //Left face
                16, 19, 18, 16, 17, 19,
                //Right face
                20, 22, 21, 22, 23, 21};
        Mesh lMesh = new Mesh(vertices, textCoord, normals, indices, 8);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        Map<String, String> lOptionalAttributes = MeshBuilder.getPositionAttributesMap(xLength, yLength, zLength);
        lMesh.putOptionalAttributes(lOptionalAttributes);
        return lMesh;

    }

    /**
     * Create a map and store each axis individually (3D position)
     *
     * @param xLength width
     * @param yLength length
     * @param zLength height
     * @return the map
     */
    public static Map<String, String> getPositionAttributesMap(float xLength, float yLength, float zLength) {
        Map<String, String> lOptionalAttributes = new HashMap<>();
        lOptionalAttributes.put("xLength", String.valueOf(xLength));
        lOptionalAttributes.put("yLength", String.valueOf(yLength));
        lOptionalAttributes.put("zLength", String.valueOf(zLength));
        return lOptionalAttributes;
    }

    /**
     * Generate a ground Mesh with the specified material , width, length and height.
     * Create vertices based on the specified width, length and height.
     * Normals and indices are hard coded
     *
     * @param width  width
     * @param length length
     * @param height height
     * @return Mesh
     */
    @Deprecated
    public static Mesh generateGround(float width, float length, float height) {
        float halfW = width / 2;
        float halfL = length / 2;
        float[] vertices = new float[]{-halfW, height, -halfL, -halfW, height, halfL, halfW, height, -halfL, halfW, height, halfL};
        float[] normals = new float[]{0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0};
        int[] indices = new int[]{0, 1, 2, 1, 3, 2};
        Mesh lMesh = new Mesh(vertices, null, normals, indices);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        Map<String, String> lOptionalAttributes = MeshBuilder.getPositionAttributesMap(width, length, height);
        lMesh.putOptionalAttributes(lOptionalAttributes);
        return lMesh;

    }

    /**
     * Generate a half block Mesh with the specified  width, length and height.
     * Create vertices and normals based on the specified width, length and height.
     *
     * @param xLength width
     * @param yLength length
     * @param zLength height
     * @return Mesh
     */
    @Deprecated
    public static Mesh generateHalfBlock(float xLength, float yLength, float zLength) {
        float x = xLength / 2f;
        float y = yLength / 2f;
        float z = zLength / 2f;
        float[] vertices = new float[]{
                //Front face
                -x, -y, z, x, -y, z, x, y, -z, -x, y, -z,
                //Back face
                x, y, -z, -x, y, -z, -x, -y, -z, x, -y, -z,
                //Bottom face
                x, -y, z, -x, -y, z, x, -y, -z, -x, -y, -z,
                //Left face
                -x, y, -z, -x, -y, z, -x, -y, -z,
                //Right face
                x, y, -z, x, -y, z, x, -y, -z};
        float frontNormal = (float) Math.sqrt(yLength * yLength + zLength * zLength);
        if (frontNormal == 0) frontNormal = 0.00000000000001f;
        float yNormal = z / frontNormal;
        float zNormal = y / frontNormal;
        float[] normals = {
                //Front face
                0, yNormal, zNormal, 0, yNormal, zNormal, 0, yNormal, zNormal, 0, yNormal, zNormal,
                //Back face
                0, 0, -1f, 0, 0, -1f, 0, 0, -1f, 0, 0, -1f,
                //Bottom face
                0, -1f, 0, 0, -1f, 0, 0, -1f, 0, 0, -1f, 0,
                //Left face
                -1, 0, 0, -1, 0, 0, -1, 0, 0,
                //Right face
                1, 0, 0, 1, 0, 0, 1, 0, 0};
        int[] indices = new int[]{
                //Front face
                0, 1, 2, 0, 2, 3,
                //Back face
                4, 7, 6, 4, 6, 5,
                //Bottom Face
                10, 8, 9, 10, 9, 11,
                //Left face
                12, 14, 13,
                //Right face
                15, 16, 17};
        Mesh lMesh = new Mesh(vertices, null, normals, indices, 6);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        Map<String, String> lOptionalAttributes = MeshBuilder.getPositionAttributesMap(xLength, yLength, zLength);
        lMesh.putOptionalAttributes(lOptionalAttributes);
        return lMesh;
    }

    /**
     * Generate a ground Mesh with the specified width, length and height.
     * Create vertices and normals based on the specified width, length and height.
     *
     * @param xLength width
     * @param yLength length
     * @param zLength height
     * @return Mesh
     */
    @Deprecated
    public static Mesh generatePyramid(float xLength, float yLength, float zLength) {
        float x = xLength / 2f;
        float y = yLength / 2f;
        float z = zLength / 2f;
        float[] vertices = new float[]{
                //Front face
                0, y, 0, x, -y, z, -x, -y, z,
                //Back face
                0, y, 0, -x, -y, -z, x, -y, -z,
                //Left face
                0, y, 0, -x, -y, z, -x, -y, -z,
                //Right face
                0, y, 0, x, -y, -z, x, -y, z,
                //Bottom face
                x, -y, z, -x, -y, z, x, -y, -z, -x, -y, -z};
        float frontNormal = (float) Math.sqrt(yLength * yLength + zLength * zLength);
        if (frontNormal == 0) frontNormal = 0.00000000000001f;
        float yFNormal = z / frontNormal;
        float zNormal = y / frontNormal;
        float lateralNormal = (float) Math.sqrt(yLength * yLength + xLength * xLength);
        if (lateralNormal == 0) lateralNormal = 0.00000000000001f;
        float xNormal = y / lateralNormal;
        float yLNormal = z / lateralNormal;
        float[] normals = {
                //Front face
                0, yFNormal, zNormal, 0, yFNormal, zNormal, 0, yFNormal, zNormal,
                //Back face
                0, yFNormal, -zNormal, 0, yFNormal, -zNormal, 0, yFNormal, -zNormal,
                //Left face
                -xNormal, yLNormal, 0, -xNormal, yLNormal, 0, -xNormal, yLNormal, 0,
                //Right face
                xNormal, yLNormal, 0, xNormal, yLNormal, 0, xNormal, yLNormal, 0,
                //Bottom face
                0, -1f, 0, 0, -1f, 0, 0, -1f, 0, 0, -1f, 0};
        int[] indices = new int[]{
                //Front face
                0, 2, 1,
                //Back face
                3, 5, 4,
                //Left face
                6, 8, 7,
                //Right face
                9, 11, 10,
                //Bottom Face
                12, 13, 15, 12, 15, 14};
        Mesh lMesh = new Mesh(vertices, null, normals, indices, 5);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        Map<String, String> lOptionalAttributes = MeshBuilder.getPositionAttributesMap(xLength, yLength, zLength);
        lMesh.putOptionalAttributes(lOptionalAttributes);
        return lMesh;
    }

    /**
     * Generate a regular octohedron mesh
     * default size 1*1*1 for each triangle
     * Warning: vertices are hard coded
     *
     * @return Mesh
     */
    @Deprecated
    public static Mesh generateOctahedron() {
        float[] vertices = new float[]{
                //LTF
                -0.5f, 0f, 0f, 0f, 0.5f, 0f, 0f, 0f, 0.5f,
                //RTF
                0.5f, 0f, 0f, 0f, 0.5f, 0f, 0f, 0f, 0.5f,
                //LBF
                -0.5f, 0f, 0f, 0f, -0.5f, 0f, 0f, 0f, 0.5f,
                //RBF
                0.5f, 0f, 0f, 0f, -0.5f, 0f, 0f, 0f, 0.5f,
                //LTB
                -0.5f, 0f, 0f, 0f, 0.5f, 0f, 0f, 0f, -0.5f,
                //RTB
                0.5f, 0f, 0f, 0f, 0.5f, 0f, 0f, 0f, -0.5f,
                //LBB
                -0.5f, 0f, 0f, 0f, -0.5f, 0f, 0f, 0f, -0.5f,
                //RBB
                0.5f, 0f, 0f, 0f, -0.5f, 0f, 0f, 0f, -0.5f};
        float pos = (float) (1 / Math.sqrt(3));
        float neg = (float) (-1 / Math.sqrt(3));
        //for(int i=0; i< vertices.length; i++)
        //System.out.println(vertices[i]+ " ");
        float[] normals = new float[]{
                //LTF
                neg, pos, pos, neg, pos, pos, neg, pos, pos,
                //RTF
                pos, pos, pos, pos, pos, pos, pos, pos, pos,
                //LBF
                neg, neg, pos, neg, neg, pos, neg, neg, pos,
                //RBF
                pos, neg, pos, pos, neg, pos, pos, neg, pos,
                //LTB
                neg, pos, neg, neg, pos, neg, neg, pos, neg,
                //RTB
                pos, pos, neg, pos, pos, neg, pos, pos, neg,
                //LBB
                neg, neg, neg, neg, neg, neg, neg, neg, neg,
                //RBB
                pos, neg, neg, pos, neg, neg, pos, neg, neg};
        int[] indices = new int[]{
                //LTF
                0, 2, 1,
                //RTF
                3, 4, 5,
                //LBF
                6, 7, 8,
                //RBF
                9, 11, 10,
                //LTB
                12, 13, 14,
                //RTB
                15, 17, 16,
                //LBB
                18, 20, 19,
                //RBB
                21, 22, 23};
        Mesh lMesh = new Mesh(vertices, null, normals, indices, 6);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        return lMesh;
    }

    /**
     * Generate a regular Tetrahedron mesh
     * default size 1*1*1 for each triangle
     * Warning: vertices are hard coded
     *
     * @return Mesh
     */
    @Deprecated
    public static Mesh generateTetrahedron() {
        float[] vertices = new float[]{
                //First Face
                -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f,
                //Snd Face
                -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f,
                //Third Face
                -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f,
                //Last Face
                0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f};
        float pos = (float) (1 / Math.sqrt(3));
        float neg = (float) (-1 / Math.sqrt(3));
        float[] normals = new float[]{
                //First Face
                pos, pos, pos, pos, pos, pos, pos, pos, pos,
                //Snd Face
                neg, pos, neg, neg, pos, neg, neg, pos, neg,
                //Third Face
                neg, neg, pos, neg, neg, pos, neg, neg, pos,
                //Last Face
                pos, neg, neg, pos, neg, neg, pos, neg, neg};
        int[] indices = new int[]{
                //First Face
                0, 2, 1,
                //Snd Face
                3, 4, 5,
                //Third Face
                6, 7, 8,
                //Last Face
                9, 10, 11};

        Mesh lMesh = new Mesh(vertices, null, normals, indices, 8);
        lMesh.setDrawingStrategy(new DefaultDrawingStrategy());
        return lMesh;

    }

    /**
     * Generate a BoundingBoxMesh with the specified material , width, length and height.
     * Create vertices based on the specified width, length and height.
     * Normals and indices are hard coded
     *
     * @param xLength xLength
     * @param yLength yLength
     * @param zLength zLength
     * @return BoundingBoxMesh
     */
    public static Mesh generateBoundingBox(float xLength, float yLength, float zLength) {
        float x = xLength / 2f;
        float y = yLength / 2f;
        float z = zLength / 2f;
        float[] vertices = new float[]{
                //Front face
                x, y, z, -x, y, z, -x, -y, z, x, -y, z,
                //Top face
                x, y, z, -x, y, z, x, y, -z, -x, y, -z,
                //Back face
                x, y, -z, -x, y, -z, -x, -y, -z, x, -y, -z,
                //Bottom face
                x, -y, z, -x, -y, z, x, -y, -z, -x, -y, -z,
                //Left face
                -x, y, z, -x, y, -z, -x, -y, z, -x, -y, -z,
                //Right face
                x, y, z, x, y, -z, x, -y, z, x, -y, -z};

        float[] normals = {0};
        float[] textCoord = {0};

        int[] indices = new int[]{
                //Front face
                0, 1, 1, 2, 2, 3, 3, 0,
                //Top face
                0, 8, 9, 1, 2, 10, 3, 11,
                //Back face
                8, 9, 9, 10, 10, 11, 11, 8};

        Mesh lMesh = new Mesh(vertices, textCoord, normals, indices);
        //we set the new strategy
        lMesh.setDrawingStrategy(new BoundingBoxDrawingStrategy());
        Map<String, String> lOptionalAttributes = MeshBuilder.getPositionAttributesMap(xLength, yLength, zLength);
        lMesh.putOptionalAttributes(lOptionalAttributes);
        return lMesh;
    }
} public class Skybox {
    public final float width, length, height;
    public Vector3f color;
    public boolean init = false;
    private ShaderProgram shaderProg;
    private int vaoId, vboVertex, vboIndices;

    /**
     * Construct a skybox with the specified width length height and color(vector)
     *
     * @param width  width
     * @param length length
     * @param height height
     * @param color  color
     */
    public Skybox(float width, float length, float height, Vector3f color) {
        this.width = width;
        this.height = height;
        this.length = length;
        this.color = color;
    }

    /**
     * Construct a skybox with the specified width length height and color(r,g,b)
     *
     * @param width  width
     * @param length length
     * @param height height
     * @param r      red value of the color
     * @param g      greeb value of the color
     * @param b      blue value of the color
     */
    public Skybox(float width, float length, float height, float r, float g, float b) {
        this.width = width;
        this.height = height;
        this.length = length;
        this.color = new Vector3f(r, g, b);
    }

    /**
     * initialize the skybox shadders and vbo
     *
     * @throws Exception if the last compile operation on shader was unsuccessful or an
     *                   error occured during the shadder creation
     */
    public void init() throws Exception {
        //if the last compile operation on shader was unsuccessful then an exception is launched
        shaderProg = new ShaderProgram();

        shaderProg.createVertexShader(skyboxVertexShader.SHADER_STRING);
        shaderProg.createFragmentShader(skyboxFragmentShader.SHADER_STRING);

        shaderProg.link();
        //Initialization of the uniforms
        shaderProg.createUniform("projectionMatrix");
        shaderProg.createUniform("viewMatrix");
        shaderProg.createUniform("color");


        float lWidth = width / 2, lHeight = height / 2, lLength = length / 2;

        float[] vertices = new float[]{
                lWidth, lHeight, lLength,
                -lWidth, lHeight, lLength,
                lWidth, -lHeight, lLength,
                -lWidth, -lHeight, lLength,
                lWidth, lHeight, -lLength,
                -lWidth, lHeight, -lLength,
                lWidth, -lHeight, -lLength,
                -lWidth, -lHeight, -lLength
        };
        //Box mapping
        int[] indices = new int[]{
                //Front
                0, 2, 3,
                0, 3, 1,
                //Back
                5, 7, 4,
                4, 7, 6,
                //top
                0, 5, 4,
                0, 1, 5,
                //Bottom
                2, 6, 7,
                2, 7, 3,
                //left
                3, 7, 1,
                7, 5, 1,
                //right
                0, 4, 2,
                4, 6, 2
        };

        vaoId = glGenVertexArrays();
        glBindVertexArray(vaoId);

        //Initialization of VBO
        //VBO of vertex
        FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
        verticesBuffer.put(vertices).flip();
        vboVertex = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vboVertex);
        glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);

        //We explain to OpenGL how to read our Buffers.
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

        //VBO of indices
        IntBuffer indicesBuffer = BufferUtils.createIntBuffer(indices.length);
        indicesBuffer.put(indices).flip();
        vboIndices = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndices);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        init = true;
    }

    public void draw(Camera cam) {
        shaderProg.bind();
        // Bind to the VAO
        glBindVertexArray(vaoId);
        glEnableVertexAttribArray(0);

        shaderProg.setUniform("projectionMatrix", cam.getCameraMat());

        Matrix4f mat = new Matrix4f(cam.setupViewMatrix());
        // we do not want translation to be applied to the sky box. so we fixed the value to 0
        mat.m30(0);
        mat.m31(0);
        mat.m32(0);

        shaderProg.setUniform("viewMatrix", mat);
        shaderProg.setUniform("color", color);

        glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

        glDisableVertexAttribArray(1);
        glBindVertexArray(0);
        shaderProg.unbind();
    }

    /**
     * Deallocation and deletion of VAO and VBO
     */
    public void cleanUp() {
        // Deallocation of VAO and VBO
        glDisableVertexAttribArray(0);

        // Delete the VBO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glDeleteBuffers(vboVertex);
        glDeleteBuffers(vboIndices);

        // Delete the VAO
        glBindVertexArray(0);
        glDeleteVertexArrays(vaoId);
    }
}
import org.joml.Vector3f;
import yaw.engine.UpdateCallback;
import yaw.engine.World;
import yaw.engine.items.HitBox;
import yaw.engine.items.ItemGroup;
import yaw.engine.items.ItemObject;
import yaw.engine.light.AmbientLight;
import yaw.engine.light.DirectionalLight;
import yaw.engine.meshs.Mesh;
import yaw.engine.meshs.MeshBuilder;
import yaw.engine.skybox.Skybox;
package yaw;


/**
 * Test class for Hit boxes: rotations, colllision, multi-bounding-box hitboxes
 */
public class HitboxRotate implements UpdateCallback {
    private int nbUpdates = 0;
    private double totalDeltaTime = 0.0;
    private static long deltaRefreshMillis = 1000;
    private long prevDeltaRefreshMillis = 0;
    private ItemGroup cube_1_hitbox ;