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