October 26, 2024
Chicago 12, Melborne City, USA
java

Error when applying physics in LWJGL 2.9.1


I am practicing doing a project on a voxel engine, and I have a bug when running the program, I simulated physics to the camera and generated a chunk in the position of the camera, but from time to time the camera falls into the void, I have a check to activate the movement to the camera only when the chunk under it has been generated, but it still works wrong. I am using lwjgl 2.9.1.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Vector3f;

import chunks.Chunk;
import chunks.ChunkMesh;
import entities.Camera;
import entities.Entity;
import entities.Pointer;
import fisics.CollisionHandler;
import models.CubeModel;
import models.RawModel;
import models.TexturedModel;
import render_engine.DisplayManager;
import render_engine.Loader;
import render_engine.MasterRenderer;
import shaders.StaticShader;
import textures.Modeltexture;

/**
 * The MainGameLoop class is the entry point of the JuanCraft game application.
 * It initializes the display, prepares the rendering engine, and enters the
 * main game loop where rendering occurs until the display is closed.
 */
public class MainGameLoop {

    // Static references for loader and shader for use throughout the application.
    public static Loader loader1 = null; // Loader instance for model loading
    public static StaticShader shader1 = null; // Shader instance for rendering
    
    // List of chunks to be rendered in the game world.
    public static List<ChunkMesh> chunks = new CopyOnWriteArrayList<>();
    
    // Vector representing the position of the camera.
    static Vector3f camPos = new Vector3f(0, 0, 0);
       
    // List of positions that have been used for placing entities to avoid duplication.
    static List<Vector3f> usedPos = new ArrayList<Vector3f>();
    
    static List<Entity> entities = new ArrayList<Entity>();
    
    // Defines the size of the world (distance from the camera in each direction).
    static final int WORLD_SIZE = 9 * 32; // World size is 288 units (9 chunks of 32 units each).
    
    /**
     * The main method that starts the game. It initializes the display, creates a
     * MasterRenderer for rendering, and enters the game loop.
     * 
     * @param args Command line arguments (not used in this application).
     */
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        // Create and initialize the display window for the game.
        DisplayManager.createDisplay();

        // Create a Loader instance for loading models and shaders.
        Loader loader = new Loader();
        loader1 = loader; // Store the loader instance for potential future use.
        StaticShader shader = new StaticShader();
        shader1 = shader; // Store the shader instance for potential future use.
        
        // Set properties for the pointer entity.
        Pointer.setSize(20.0f);
        Pointer.setColor(1.0f, 0.0f, 0.0f); // Set pointer color to red.

        // Instantiate the MasterRenderer to handle rendering operations.
        MasterRenderer renderer = new MasterRenderer();

        // Load the vertices, indices, and UV coordinates into a RawModel for a cube.
        RawModel model = loader.loadToVao(CubeModel.vertices, CubeModel.indices, CubeModel.uv);

        // Load a texture from the specified file and create a Modeltexture object.
        Modeltexture texture = new Modeltexture(loader.loadTexture("DefaultPack"));

        // Create a TexturedModel object using the loaded texture and the 3D model.
        TexturedModel texturedModel = new TexturedModel(model, texture);

        // Create a Camera object positioned at the origin with no rotation.
        Camera camera = new Camera(new Vector3f(0, 2, 0), 0, 0, 0);

        // Create a new thread to manage entity creation in the positive X and Z quadrant.
        new Thread(new Runnable() {
            @Override
            public void run() {
                // Generate the chunk where the camera is located.
                Chunk.generateChunkAtCameraPosition(camPos, usedPos);
                
                // Generate nearby chunks in a separate thread.
                while (!Display.isCloseRequested()) {
                    // Loop to generate chunks around the camera in a 32x32 area.
                    for (int x = (int) (camPos.x - WORLD_SIZE) / 32; x < (camPos.x + WORLD_SIZE) / 32; x++) {
                        for (int z = (int) (camPos.z - WORLD_SIZE) / 32; z < (camPos.z + WORLD_SIZE) / 32; z++) {
                            // Avoid generating already used chunks.
                            if (!usedPos.contains(new Vector3f(x * 32, 0, z * 32))) {
                                Chunk.generateChunkAt(x, z, usedPos); // Generate the chunk.
                            }
                        }
                    }

                    try {
                        Thread.sleep(1); // Sleep to prevent overloading the loop.
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt(); // Restore interrupted state.
                    }
                }
            }
        }).start();
        
        // Main game loop, which runs continuously until the display requests to close.
        int index = 0; // Index for tracking chunks to be loaded.
        boolean showDebugInfo = false; // Toggle for debug information display.
        while (!Display.isCloseRequested()) {

            // Check if the chunk below the camera is ready before applying gravity
            if (Chunk.isChunkAtCameraReady) {
                if (CollisionHandler.isGroundBelowCamera(camera)) {
                    camera.applyGravity(); // Apply gravity only when ground exists below the camera
                }
                camera.move();
            }
                    
            // Get the current camera position for entity placement logic.
            camPos = camera.getPosition();
            
            // Apply gravity if there is no ground below the camera.
            if (!CollisionHandler.isGroundBelowCamera(camera)) {
                camera.applyGravity();
            }
            
            // Load chunks into the world until we reach the limit.
            if (index < chunks.size()) {
                // Load the chunk's mesh data into a RawModel.
                RawModel model123 = loader.loadToVao(chunks.get(index).positions, chunks.get(index).uvs);
                
                // Create a textured model for the chunk using the loaded texture.
                TexturedModel texModel = new TexturedModel(model123, texture);
                
                // Create an entity for the chunk, placed at the chunk's origin.
                Entity entity = new Entity(texModel, chunks.get(index).chunck.getOrigin(), 0, 0, 0, 1);
                entities.add(entity); // Add the new entity to the list of entities.
                
                // Clean up chunk data to free memory.
                chunks.get(index).positions = null;
                chunks.get(index).uvs = null;
                chunks.get(index).normals = null;
                
                index++; // Move to the next chunk.
            }
            
            // Render each chunk entity that is within the specified world size.
            for (int i = 0; i < entities.size(); i++) {
                Vector3f origin = entities.get(i).getPosition(); // Get the position of the entity.
                
                // Calculate the distance from the camera to the chunk along the X and Z axes.
                int distX = Math.abs((int) (camPos.x - origin.x));
                int distZ = Math.abs((int) (camPos.z - origin.z));

                // If the chunk is within the world size range, render its blocks.
                if (distX <= WORLD_SIZE && distZ <= WORLD_SIZE) {
                    renderer.addEntity(entities.get(i)); // Add entity for rendering.
                }
            }
            
            // Clear the frame buffer before rendering the new frame.
            GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

            // Render the scene with the camera's current view.
            renderer.render(camera);
            
            // Render the pointer.
            Pointer.renderPointer();
            
            // Update the display (sync frame rate and render new frame).
            DisplayManager.updateDisplay();
        }
        
        // Clean up pointer resources.
        Pointer.cleanup();

        // Close the display and clean up resources when the loop exits.
        DisplayManager.closeDisplay();
    }
}

package chunks;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.lwjgl.util.vector.Vector3f;
import cube.Block;
import juancraft.MainGameLoop;
import toolbox.PerlinNoiseGenerator;

/**
 * The Chunk class represents a section of the game world containing a collection of blocks.
 * Each chunk is defined by its list of blocks and an origin point that indicates its position in the 3D space.
 */
public class Chunk {

    // List of blocks that make up this chunk.
    private List<Block> blocks;

    // The origin position of the chunk in 3D space, represented as a Vector3f (x, y, z).
    private Vector3f origin;
    
    // Random instance for generating random values.
    static Random random = new Random();
    
    // Perlin noise generator for generating heights based on noise.
    static PerlinNoiseGenerator generator = new PerlinNoiseGenerator(random.nextInt(10), random.nextInt(10), random.nextInt(100), random.nextInt(50));

    // Flag to indicate if the chunk at the camera position is ready.
    public static boolean isChunkAtCameraReady = false;

    /**
     * Constructs a Chunk with a specified list of blocks and an origin position.
     *
     * @param blocks List of Block objects representing the blocks in the chunk.
     * @param origin The origin position of the chunk in the game world.
     */
    public Chunk(List<Block> blocks, Vector3f origin) {
        this.blocks = blocks; // Initialize the list of blocks in this chunk.
        this.origin = origin; // Set the origin position of the chunk in the world.
    }

    /**
     * Returns the list of blocks that make up this chunk.
     *
     * @return List of Block objects.
     */
    public List<Block> getBlocks() {
        return blocks;
    }

    /**
     * Returns the origin position of this chunk.
     *
     * @return The origin as a Vector3f.
     */
    public Vector3f getOrigin() {
        return origin;
    }
    
    /**
     * Retrieves the block at the given local coordinates within the chunk.
     *
     * @param x Local X coordinate (0 to 31).
     * @param y Local Y coordinate (depends on the height).
     * @param z Local Z coordinate (0 to 31).
     * @return The block at the specified local coordinates, or null if no block exists there.
     */
    public Block getBlockAt(int x, int y, int z) {
        // Iterate through the list of blocks and find the block at the given coordinates.
        for (Block block : blocks) {
            if (block.getX() == x && block.getY() == y && block.getZ() == z) {
                return block; // Return the found block.
            }
        }
        return null; // Return null if no block exists at those coordinates.
    }
    
    /**
     * Generates a chunk at the camera's current position.
     *
     * @param camPos The current position of the camera in the world.
     * @param usedPos List of positions that have already been used to prevent duplicate generation.
     */
    public static void generateChunkAtCameraPosition(Vector3f camPos, List<Vector3f> usedPos) {
        int camChunkX = (int) camPos.x / 32;
        int camChunkZ = (int) camPos.z / 32;

        float chunkCenterX = (float) (camChunkX * 32) + 16;
        float chunkCenterZ = (float) (camChunkZ * 32) + 16;

        camPos.x = chunkCenterX;
        camPos.z = chunkCenterZ;

        // Ensure camera starts above the ground level
        camPos.y = Math.max(camPos.y, 1.0f);

        // Check if chunk is partially generated or contains blocks below the camera's y-position
        Block blockBelow = getBlockAtPosition(camPos.x, camPos.y - 1, camPos.z);
        if (blockBelow != null) {
            camPos.y = (float) blockBelow.getY() + 1; // Set camera above the ground
        }

        // Generate or continue generating the chunk at the camera's position
        generateChunkAt(camChunkX, camChunkZ, usedPos);
        isChunkAtCameraReady = true; // Flag chunk readiness for the game loop
    }
    
    /**
     * Checks if a chunk exists at the specified coordinates.
     *
     * @param chunkX The X coordinate of the chunk.
     * @param chunkZ The Z coordinate of the chunk.
     * @return true if the chunk exists, false otherwise.
     */
    private static boolean isChunkAtPosition(int chunkX, int chunkZ) {
        // Iterate through the list of chunks.
        for (ChunkMesh chunkMesh : MainGameLoop.chunks) {
            if (chunkMesh != null) {
                // Get the origin of the chunk.
                Vector3f chunkOrigin = chunkMesh.chunck.getOrigin();
                // Check if the coordinates match.
                if ((int)(chunkOrigin.x / 32) == chunkX && (int)(chunkOrigin.z / 32) == chunkZ) {
                    return true; // The chunk exists.
                }
            }
        }
        return false; // No matching chunk found.
    }

    /**
     * Retrieves a block at the specified world position.
     *
     * @param x World X coordinate.
     * @param y World Y coordinate.
     * @param z World Z coordinate.
     * @return The block at the specified position, or null if no block exists there.
     */
    private static Block getBlockAtPosition(float x, float y, float z) {
        // Calculate chunk coordinates based on the world position.
        int chunkX = (int) (x / 32); // Assuming each chunk is 32x32.
        int chunkZ = (int) (z / 32);

        // Retrieve the corresponding chunk using the calculated coordinates.
        Chunk chunk = getChunkAt(chunkX, chunkZ);
        if (chunk != null) {
            // Calculate the local block position within the chunk.
            int localX = (int) (x % 32); // Local position in X.
            int localY = (int) y;        // Height, which is an integer value.
            int localZ = (int) (z % 32); // Local position in Z.

            // Return the block at the local position.
            return chunk.getBlockAt(localX, localY, localZ); // Method to get the block within the chunk.
        }
        return null; // Return null if no matching chunk is found.
    }
    
    /**
     * Retrieves a chunk at the specified chunk coordinates.
     *
     * @param chunkX The X coordinate of the chunk.
     * @param chunkZ The Z coordinate of the chunk.
     * @return The chunk at the specified coordinates, or null if no chunk is found.
     */
    public static Chunk getChunkAt(int chunkX, int chunkZ) {
        // Iterate through the list of chunks.
        for (ChunkMesh chunkMesh : MainGameLoop.chunks) {
            if (chunkMesh != null) {
                Vector3f chunkOrigin = chunkMesh.chunck.getOrigin();
                // Check if the chunk coordinates match the requested coordinates.
                if ((int)(chunkOrigin.x / 32) == chunkX && (int)(chunkOrigin.z / 32) == chunkZ) {
                    return chunkMesh.chunck; // Return the matching chunk.
                }
            }
        }
        return null; // Return null if no matching chunk is found.
    }

    /**
     * Generates a chunk at the specified coordinates and populates it with blocks.
     *
     * @param x The X coordinate of the chunk.
     * @param z The Z coordinate of the chunk.
     * @param usedPos List of positions that have already been used to avoid duplicate generation.
     */
    public static void generateChunkAt(int x, int z, List<Vector3f> usedPos) {
        // Create a list of blocks for the current chunk.
        List<Block> blocks = new ArrayList<>();
        
        // Create a grid of 32x32 blocks for the chunk.
        for (int i = 0; i < 32; i++) {
            for (int j = 0; j < 32; j++) {
                // Generate height using Perlin noise or a similar generator.
                blocks.add(new Block(i, (int) generator.generateHeight(i + (x * 32), j + (z * 32)), j, Block.GRASS));
            }
        }
        
        // Create the chunk at the calculated position.
        Chunk chunk = new Chunk(blocks, new Vector3f((x * 32), 0, (z * 32)));
        // Create the mesh for the chunk from the blocks.
        ChunkMesh mesh = new ChunkMesh(chunk);
        
        // Add the generated chunk to the list of chunks.
        MainGameLoop.chunks.add(mesh);
        
        // Mark the position as used to prevent generating the same chunk twice.
        usedPos.add(new Vector3f((x * 32), 0, (z * 32)));
    }
}

package entities;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.util.vector.Vector3f;

import fisics.CollisionHandler;
import juancraft.MainGameLoop;

/**
 * The Camera class represents a camera in a 3D space, allowing for movement and rotation based on user input.
 * It uses keyboard input for forward and backward movement and mouse input for rotation.
 */
public class Camera {
    
    // Variables relacionadas con la física y el vuelo
    private float velocityY = 0;       // Velocidad vertical
    @SuppressWarnings("unused")
    private boolean isFlying = false;  // Si la cámara está en modo de vuelo
    private boolean onGround = true;   // Si la cámara está en el suelo
    private boolean doubleJumpAvailable = true; // Si el doble salto está disponible

    private final float gravity = -0.01f;     // Fuerza de gravedad
    private final float jumpStrength = 0.3f; // Fuerza del salto

    // The position of the camera in 3D space.
    private Vector3f position;
    
    // Rotation angles for the camera around the X, Y, and Z axes.
    private float rotX, rotY, rotZ;
    
    // Speed at which the camera moves.
    private float speed = 0.5f;
    
    // Speed at which the camera rotates.
    private float turnSpeed = 0.1f;
    
    @SuppressWarnings("unused")
    private long lastSpacePressTime = 0;

    /**
     * Constructor to create a Camera instance.
     * 
     * @param position The initial position of the camera in 3D space.
     * @param rotX The initial rotation angle around the X axis.
     * @param rotY The initial rotation angle around the Y axis.
     * @param rotZ The initial rotation angle around the Z axis (not used in this implementation).
     */
    public Camera(Vector3f position, float rotX, float rotY, float rotZ) {
        this.position = position;
        this.rotX = rotX;
        this.rotY = rotY;
        this.rotZ = rotZ;
    }
    
    // Método para aplicar gravedad
    public void applyGravity() {
        if (!onGround) {
            velocityY += gravity; // Aplicar aceleración de la gravedad
            position.y += velocityY; // Actualizar la posición Y según la velocidad
        }
    }

    /**
     * Updates the camera's position and rotation based on user input.
     * The W and S keys (or UP and DOWN arrows) move the camera forward and backward.
     * The A and D keys move the camera left and right.
     * The SPACE key moves the camera upward.
     * The LEFT SHIFT key moves the camera downward.
     * The mouse movement updates the camera's rotation.
     */
    public void move() {
        // Save old position
        Vector3f oldPosition = new Vector3f(position);

        // Variables to control movement direction
        float moveAt = 0;
        float strafeAt = 0;

        // Detect if SPACE is pressed for jumping or flying
        if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
            if (onGround) {
                velocityY = jumpStrength; // Normal jump when on the ground
                onGround = false;          // Now we are in the air
            } else if (doubleJumpAvailable) {
                velocityY = jumpStrength;  // Double jump if available
                doubleJumpAvailable = false; // Disable double jump
            }
        }

        if (!onGround) {
            velocityY += gravity; // Apply gravity only if not on the ground
        } else {
            velocityY = 0; // Reset vertical velocity when on the ground
        }

        // Movement detection (forward/backward and left/right)
        if (Keyboard.isKeyDown(Keyboard.KEY_W) || Keyboard.isKeyDown(Keyboard.KEY_UP)) {
            moveAt = -speed; // Move forward
        } else if (Keyboard.isKeyDown(Keyboard.KEY_S) || Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
            moveAt = speed; // Move backward
        }
        
        if (Keyboard.isKeyDown(Keyboard.KEY_A) || Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
            strafeAt = -speed; // Move left
        } else if (Keyboard.isKeyDown(Keyboard.KEY_D) || Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
            strafeAt = speed; // Move right
        }

        // Update rotation based on mouse movement
        rotX += -Mouse.getDY() * turnSpeed;
        rotY += Mouse.getDX() * turnSpeed;

        // Calculate movement based on rotation
        float dx = (float) -(moveAt * Math.sin(Math.toRadians(rotY))) + (float) (strafeAt * Math.cos(Math.toRadians(rotY)));
        float dz = (float) (moveAt * Math.cos(Math.toRadians(rotY))) + (float) (strafeAt * Math.sin(Math.toRadians(rotY)));

        // Update camera position
        position.x += dx;
        position.y += velocityY; // Apply vertical velocity (gravity or jump)
        position.z += dz;

        // Check for collisions with blocks
        boolean collided = CollisionHandler.checkCollision(this, MainGameLoop.chunks);

        if (collided) {
            // The camera collides with a block
            if (velocityY < 0) {
                // Adjust the position to land on the block
                position.y = (float) Math.ceil(position.y); // Set camera to the top of the block
                onGround = true; // Camera is now on the ground
                doubleJumpAvailable = true; // Restore double jump ability
                velocityY = 0; // Stop vertical movement
            }
        } else {
            // If not on the ground, check for other conditions
            onGround = false; // Camera is not touching the ground
        }

        // Handle collision response
        CollisionHandler.handleCollision(this, oldPosition, MainGameLoop.chunks);
    }



    /**
     * Gets the current position of the camera.
     * 
     * @return The position of the camera as a Vector3f.
     */
    public Vector3f getPosition() {
        return position;
    }

    public void setPosition(Vector3f position) {
        this.position = position;
    }

    /**
     * Gets the current rotation around the X axis.
     * 
     * @return The rotation angle around the X axis.
     */
    public float getRotX() {
        return rotX;
    }

    /**
     * Gets the current rotation around the Y axis.
     * 
     * @return The rotation angle around the Y axis.
     */
    public float getRotY() {
        return rotY;
    }

    /**
     * Gets the current rotation around the Z axis.
     * 
     * @return The rotation angle around the Z axis (not used in this implementation).
     */
    public float getRotZ() {
        return rotZ;
    }

    /**
     * Gets a direction vector indicating where the camera is looking.
     * 
     * @return A Vector3f representing the forward direction the camera is facing.
     */
    public Vector3f getDirection() {
        float dirX = (float) -(Math.sin(Math.toRadians(rotY)));
        float dirY = (float) -(Math.sin(Math.toRadians(rotX)));
        float dirZ = (float) -(Math.cos(Math.toRadians(rotY)));
        return new Vector3f(dirX, dirY, dirZ);
    }
}

I want that when I run the program, the camera never falls into the void, but rather that it works as Minecraft does, or at least simulate this operation. I would appreciate if you help me.



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video