"import org.lwjgl.*;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;
import org.lwjgl.system.*;

import java.nio.*;
import java.nio.file.*;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*; // For GL_CLAMP_TO_EDGE
import static org.lwjgl.stb.STBImage.*;
import static org.lwjgl.system.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*;

public class Main {

    private long window;
    private int width = 800;
    private int height = 600;
    private int textureID;

    // Camera parameters
    private float orbitAngle = 0.0f; // Horizontal rotation angle
    private float pitch = 20.0f;     // Vertical rotation angle (tilt)
    private float radius = 50.0f;    // Distance from the block

    // Mouse settings
    private double lastMouseX = width / 2.0;
    private double lastMouseY = height / 2.0;
    private boolean firstMouse = true;
    private boolean dragging = false;

    public static void main(String[] args) {
        new Main().run();
    }

    public void run() {
        init();
        loop();
        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);
        glfwTerminate();
        glfwSetErrorCallback(null).free();
    }

    private void init() {
        GLFWErrorCallback.createPrint(System.err).set();
        if (!glfwInit())
            throw new IllegalStateException("Unable to initialize GLFW");

        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);

        window = glfwCreateWindow(width, height, "Orbiting Camera", NULL, NULL);
        if (window == NULL)
            throw new RuntimeException("Failed to create the GLFW window");

        glfwSetFramebufferSizeCallback(window, (window, newWidth, newHeight) -> {
            this.width = newWidth;
            this.height = newHeight;
            glViewport(0, 0, width, height);
            updateProjection();
        });

        glfwSetCursorPosCallback(window, (window, xpos, ypos) -> {
            if (dragging) {
                if (firstMouse) {
                    lastMouseX = xpos;
                    lastMouseY = ypos;
                    firstMouse = false;
                }

                double xOffset = xpos - lastMouseX;
                double yOffset = ypos - lastMouseY; // Calculate yOffset normally
                lastMouseX = xpos;
                lastMouseY = ypos;

                float sensitivity = 0.1f; // Sensitivity of mouse movement
                orbitAngle += xOffset * sensitivity; // Inverted horizontal control
                pitch -= yOffset * sensitivity; // Invert the effect of yOffset on pitch

                // Constrain the pitch
                if (pitch > 89.0f) pitch = 89.0f;
                if (pitch < -89.0f) pitch = -89.0f;
            }
        });

        // Enable and disable dragging with mouse buttons
        glfwSetMouseButtonCallback(window, (window, button, action, mods) -> {
            if (button == GLFW_MOUSE_BUTTON_LEFT) {
                if (action == GLFW_PRESS) {
                    dragging = true;
                    firstMouse = true;
                } else if (action == GLFW_RELEASE) {
                    dragging = false;
                }
            }
        });

        glfwMakeContextCurrent(window);
        glfwSwapInterval(1);
        glfwShowWindow(window);

        GL.createCapabilities();
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_TEXTURE_2D);

        // Load texture
        textureID = loadTexture("src/minecraft_block.png"); // Ensure this path is correct for your texture

        updateProjection(); // Setup initial projection
    }

    private int loadTexture(String filePath) {
        if (!Files.exists(Paths.get(filePath))) {
            throw new RuntimeException("Texture file not found: " + filePath);
        }

        try (MemoryStack stack = stackPush()) {
            IntBuffer w = stack.mallocInt(1);
            IntBuffer h = stack.mallocInt(1);
            IntBuffer comp = stack.mallocInt(1);

            stbi_set_flip_vertically_on_load(true); // Flip the image vertically
            ByteBuffer image = stbi_load(filePath, w, h, comp, 4);
            if (image == null) {
                throw new RuntimeException("Failed to load texture file: " + stbi_failure_reason());
            }

            int textureID = glGenTextures();
            glBindTexture(GL_TEXTURE_2D, textureID);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w.get(), h.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
            stbi_image_free(image);

            return textureID;
        }
    }

    private void updateProjection() {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        float aspectRatio = (float) width / height;
        float fov = 45.0f;
        float near = 0.1f;
        float far = 500.0f;
        float top = (float) Math.tan(Math.toRadians(fov / 2)) * near;
        float bottom = -top;
        float left = bottom * aspectRatio;
        float right = top * aspectRatio;
        glFrustum(left, right, bottom, top, near, far);
        glMatrixMode(GL_MODELVIEW);
    }

    private void loop() {
        while (!glfwWindowShouldClose(window)) {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glLoadIdentity();

            // Calculate the camera position using spherical coordinates
            float cosPitch = (float) Math.cos(Math.toRadians(pitch));
            float sinPitch = (float) Math.sin(Math.toRadians(pitch));
            float cosYaw = (float) Math.cos(Math.toRadians(orbitAngle));
            float sinYaw = (float) Math.sin(Math.toRadians(orbitAngle));

            float cameraX = radius * cosYaw * cosPitch;
            float cameraY = radius * sinPitch;
            float cameraZ = radius * sinYaw * cosPitch;

            gluLookAt(cameraX, cameraY, cameraZ, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

            glBindTexture(GL_TEXTURE_2D, textureID);

            // Render the block
            renderBlock();

            glfwSwapBuffers(window);
            glfwPollEvents();
        }
    }

    private void renderBlock() {
        glPushMatrix();
        glTranslatef(-8.0f, -8.0f, -8.0f);

        float blockSize = 16.0f; // Full size of the block

        glBegin(GL_QUADS);

        // Front Face
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(blockSize, 0.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(blockSize, blockSize, 0.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(0.0f, blockSize, 0.0f);

        // Back Face
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, blockSize);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(blockSize, 0.0f, blockSize);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(blockSize, blockSize, blockSize);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(0.0f, blockSize, blockSize);

        // Top Face
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(0.0f, blockSize, 0.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(blockSize, blockSize, 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(blockSize, blockSize, blockSize);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(0.0f, blockSize, blockSize);

        // Bottom Face
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(0.0f, 0.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(blockSize, 0.0f, 0.0f);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(blockSize, 0.0f, blockSize);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, blockSize);

        // Right Face
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(blockSize, 0.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(blockSize, blockSize, 0.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(blockSize, blockSize, blockSize);
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(blockSize, 0.0f, blockSize);

        // Left Face
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(0.0f, blockSize, 0.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(0.0f, blockSize, blockSize);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(0.0f, 0.0f, blockSize);

        glEnd();

        glPopMatrix();
    }

    private void gluLookAt(float eyeX, float eyeY, float eyeZ,
                           float centerX, float centerY, float centerZ,
                           float upX, float upY, float upZ) {
        FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
        float[] f = new float[3];
        float[] up = new float[3];
        float[] s = new float[3];
        float[] u = new float[3];

        f[0] = centerX - eyeX;
        f[1] = centerY - eyeY;
        f[2] = centerZ - eyeZ;
        normalize(f);

        up[0] = upX;
        up[1] = upY;
        up[2] = upZ;
        normalize(up);

        s[0] = f[1] * up[2] - f[2] * up[1];
        s[1] = f[2] * up[0] - f[0] * up[2];
        s[2] = f[0] * up[1] - f[1] * up[0];
        normalize(s);

        u[0] = s[1] * f[2] - s[2] * f[1];
        u[1] = s[2] * f[0] - s[0] * f[2];
        u[2] = s[0] * f[1] - s[1] * f[0];

        buffer.put(new float[]{
            s[0], u[0], -f[0], 0,
            s[1], u[1], -f[1], 0,
            s[2], u[2], -f[2], 0,
            0, 0, 0, 1
        });

        buffer.flip();
        glMultMatrixf(buffer);
        glTranslatef(-eyeX, -eyeY, -eyeZ);
    }

    private void normalize(float[] vector) {
        float length = (float) Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
        if (length != 0) {
            vector[0] /= length;
            vector[1] /= length;
            vector[2] /= length;
        }
    }
}", add a 40x40x40 cube grid and place the camera at the center.