added some really bad raycasting

This commit is contained in:
JISAUAY 2025-11-13 15:22:23 -06:00
parent 46be697e2f
commit ae5b2a0185
8 changed files with 268 additions and 31 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

6
Notes.md Normal file
View File

@ -0,0 +1,6 @@
These are just some notes on the blogpost I was following. While it was great, I thought the author might want to know where some areas were lacking (i would).
- in the definition of raycast_voxel `step` is never defined, nor explained. I'm pretty sure it's just supposed to be `step_dir` though.
- `get_voxel` returns the type `Voxel?` but `Chunk.voxels` doesn't hold a type named `Voxel` only a HashMap<ivec3, VoxelKind>

View File

@ -29,3 +29,9 @@ Chunks now generate around the player in a square based on a render distance. Th
Different voxel types are now supported in the data structures and passed to the shaders.
![a world of dirt with some stone blocks](.attachments/voxel-types.png)
## 0.6.0 : Raycasting Placing - 11/13/25
When placing a block, the program now raycasts to place where the camera is looking.
![a place of brown dirt with word Hi built out of stone.](.attachments/raycasting_test.png)

View File

@ -4,7 +4,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
const float CAMERA_HEIGHT = 1.7f;
const float CAMERA_HEIGHT = 1.8f;
const float GRAVITY = 18.6f; // Should be in m/s^2 but it doesn't really work out with 9.8 so we double it
class Camera
@ -62,11 +62,12 @@ class Camera
glm::mat4 getView()
{
glm::mat4 v = glm::lookAt(this->position,
this->target + this->position,
this->worldUp);
glm::vec3 real_camera_pos = this->position;
real_camera_pos.y += CAMERA_HEIGHT;
v = glm::translate(v, glm::vec3(0.0f, -CAMERA_HEIGHT, 0.0f));
glm::mat4 v = glm::lookAt(real_camera_pos,
real_camera_pos + this->target,
this->worldUp);
return v;
}
@ -92,6 +93,9 @@ class Camera
}
glm::vec3 getEyePosition() const {
return position + glm::vec3(0.0f, CAMERA_HEIGHT, 0.0f);
}
};
#endif

View File

@ -46,9 +46,19 @@ void addFace(glm::ivec3 pos, const float *faceVertices, float voxelKind, std::ve
unsigned int vIndex = vbo.size() / VERTEX_SIZE;
for (int i = 0; i < 4; i++) {
const float* v = &faceVertices[i * 6]; // Face data is still 6 floats per vertex
vbo.push_back(v[0] + pos.x);
vbo.push_back(v[1] + pos.y);
vbo.push_back(v[2] + pos.z);
float world_x = v[0] + pos.x;
float world_y = v[1] + pos.y;
float world_z = v[2] + pos.z;
// Debug: Print first vertex of first face
if (vbo.empty() && i == 0) {
std::cout << "[MESH] First vertex - Local pos: (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
std::cout << "[MESH] First vertex - World: (" << world_x << ", " << world_y << ", " << world_z << ")" << std::endl;
}
vbo.push_back(world_x);
vbo.push_back(world_y);
vbo.push_back(world_z);
vbo.push_back(v[3]);
vbo.push_back(v[4]);
vbo.push_back(v[5]);
@ -117,5 +127,10 @@ void Chunk::generateMesh() {
}
void Chunk::regenerateMesh() {
std::cout << "[REGENERATE] Regenerating mesh with " << voxels.size() << " voxels" << std::endl;
if (!voxels.empty()) {
auto first = voxels.begin();
std::cout << "[REGENERATE] First voxel at local: (" << first->first.x << ", " << first->first.y << ", " << first->first.z << ")" << std::endl;
}
this->generateMesh();
}

View File

@ -1,10 +1,34 @@
#include "World.h"
#include <iostream>
// Helper function to handle negative modulo correctly
glm::ivec3 positive_modulo(glm::ivec3 value, int divisor) {
glm::ivec3 result;
result.x = ((value.x % divisor) + divisor) % divisor;
result.y = ((value.y % divisor) + divisor) % divisor;
result.z = ((value.z % divisor) + divisor) % divisor;
return result;
}
// Helper function to correctly floor divide for chunk positions
glm::ivec3 floor_divide(glm::ivec3 value, int divisor) {
glm::ivec3 result;
result.x = (int)std::floor((float)value.x / divisor);
result.y = (int)std::floor((float)value.y / divisor);
result.z = (int)std::floor((float)value.z / divisor);
return result;
}
World::World() {}
void World::set_voxel(glm::ivec3 position, std::optional<VoxelKind> kind) {
glm::ivec3 chunk_position = position / CHUNK_SIZE;
glm::ivec3 local_position = position % CHUNK_SIZE;
glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
glm::ivec3 local_position = positive_modulo(position, CHUNK_SIZE);
std::cout << "[SET_VOXEL] World pos: (" << position.x << ", " << position.y << ", " << position.z << ")" << std::endl;
std::cout << "[SET_VOXEL] Chunk pos: (" << chunk_position.x << ", " << chunk_position.y << ", " << chunk_position.z << ")" << std::endl;
std::cout << "[SET_VOXEL] Local pos: (" << local_position.x << ", " << local_position.y << ", " << local_position.z << ")" << std::endl;
Chunk * chunk = get_or_create_chunk(chunk_position);
if (!kind.has_value()) {
@ -22,11 +46,11 @@ Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) {
// Fill in new chunk with a flat plane of voxels at y=0
std::unordered_map<glm::ivec3, VoxelKind> voxels;
if (chunk_position.y == 0) {
for (int z = 0; z <= CHUNK_SIZE; z++) {
for (int y = 0; y <= CHUNK_SIZE; y++) {
for (int x = 0; x <= CHUNK_SIZE; x++) {
for (int z = 0; z < CHUNK_SIZE; z++) {
for (int y = 0; y < CHUNK_SIZE; y++) {
for (int x = 0; x < CHUNK_SIZE; x++) {
glm::ivec3 offset = glm::ivec3(x, y, z);
glm::ivec3 world_voxel_position = chunk_position * 32 + offset;
glm::ivec3 world_voxel_position = chunk_position * CHUNK_SIZE + offset;
if (world_voxel_position.y == 0) {
voxels[offset] = VoxelKind::Dirt;
@ -42,3 +66,86 @@ Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) {
}
return &this->chunks[chunk_position];
}
std::optional<std::pair<glm::ivec3, glm::ivec3>> World::raycast_voxel(glm::vec3 start, glm::vec3 direction, float max_dist) {
// Normalize direction to ensure consistent behavior
direction = glm::normalize(direction);
std::cout << "[RAYCAST] Start: (" << start.x << ", " << start.y << ", " << start.z << ")" << std::endl;
std::cout << "[RAYCAST] Direction: (" << direction.x << ", " << direction.y << ", " << direction.z << ")" << std::endl;
glm::ivec3 pos = glm::ivec3(std::floor(start.x), std::floor(start.y), std::floor(start.z));
std::cout << "[RAYCAST] Starting voxel: (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
// (+1 or -1 for each axis)
glm::ivec3 step_dir = glm::sign(direction);
std::cout << "[RAYCAST] Step direction: (" << step_dir.x << ", " << step_dir.y << ", " << step_dir.z << ")" << std::endl;
// How far to step in each axis
const float epsilon = 0.0001f;
glm::vec3 safe_dir = glm::vec3(
abs(direction.x) < epsilon ? epsilon : direction.x,
abs(direction.y) < epsilon ? epsilon : direction.y,
abs(direction.z) < epsilon ? epsilon : direction.z
);
glm::vec3 delta = glm::abs(1.0f / safe_dir);
glm::vec3 fract = start - glm::vec3(pos);
glm::vec3 t_max = glm::vec3(
(step_dir.x > 0 ? (1.0f - fract.x) : fract.x) * delta.x,
(step_dir.y > 0 ? (1.0f - fract.y) : fract.y) * delta.y,
(step_dir.z > 0 ? (1.0f - fract.z) : fract.z) * delta.z
);
float dist = 0.0f;
glm::ivec3 last_move = glm::ivec3(0, 0, 0);
int iterations = 0;
const int max_iterations = 100; // Safety limit
while (dist < max_dist && iterations < max_iterations) {
iterations++;
std::cout << "[RAYCAST] Iteration " << iterations << " at voxel (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
if (this->get_voxel(pos).has_value()) {
std::cout << "[RAYCAST] Hit voxel at (" << pos.x << ", " << pos.y << ", " << pos.z << ") after " << iterations << " iterations" << std::endl;
std::cout << "[RAYCAST] Returning normal: (" << -last_move.x << ", " << -last_move.y << ", " << -last_move.z << ")" << std::endl;
return std::make_pair(pos, -last_move);
}
// step in the axis with the smallest t_max - that's the next voxel boundary
if (t_max.x < t_max.y && t_max.x < t_max.z) {
pos.x += step_dir.x;
last_move = glm::ivec3(step_dir.x, 0, 0);
dist = t_max.x;
t_max.x += delta.x;
} else if (t_max.y < t_max.z) {
pos.y += step_dir.y;
last_move = glm::ivec3(0, step_dir.y, 0);
dist = t_max.y;
t_max.y += delta.y;
} else {
pos.z += step_dir.z;
last_move = glm::ivec3(0, 0, step_dir.z);
dist = t_max.z;
t_max.z += delta.z;
}
}
std::cout << "[RAYCAST] No voxel hit after " << iterations << " iterations, dist=" << dist << std::endl;
return std::nullopt;
}
std::optional<VoxelKind> World::get_voxel(glm::ivec3 position) {
glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
glm::ivec3 local_position = positive_modulo(position, CHUNK_SIZE);
Chunk* chunk = this->get_or_create_chunk(chunk_position);
auto it = chunk->voxels.find(local_position);
if (it != chunk->voxels.end()) {
return it->second;
}
return std::nullopt;
}

View File

@ -17,6 +17,10 @@ class World
void set_voxel(glm::ivec3 position, std::optional<VoxelKind> kind);
std::optional<std::pair<glm::ivec3, glm::ivec3>> raycast_voxel(glm::vec3 start, glm::vec3 direction, float max_dist);
std::optional<VoxelKind> get_voxel(glm::ivec3 position);
Chunk* get_or_create_chunk(glm::ivec3 chunk_position);
};

View File

@ -33,7 +33,7 @@ const int VIEW_DISTANCE = 4; // In Chunks
bool W_pressed, S_pressed, A_pressed, D_pressed = false;
glm::mat4 projection = glm::mat4(1.0f);
Camera camera = Camera(CAMERA_SPEED, glm::vec3(0.0f, 1.0f, 3.0f));
Camera camera = Camera(CAMERA_SPEED, glm::vec3(0.0f, 0.0f, 0.0f));
// Callback function for when the window is resized
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
@ -86,10 +86,6 @@ void mouse_callback(GLFWwindow * window, double xpos, double ypos)
direction.y = -sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
camera.target = glm::normalize(direction);
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS) {
world.set_voxel(glm::ivec3(camera.position), VoxelKind::Stone);
}
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
@ -176,6 +172,57 @@ void processInput(GLFWwindow *window)
camera.speed = CAMERA_SPEED * 7.5;
if(glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_RELEASE)
camera.speed = CAMERA_SPEED * 20;
static bool left_mouse_pressed = false;
static bool right_mouse_pressed = false;
// Left click - place block
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS && !left_mouse_pressed) {
left_mouse_pressed = true;
std::cout << "\n=== LEFT CLICK (PLACE) ===" << std::endl;
std::cout << "Camera eye pos: (" << camera.getEyePosition().x << ", " << camera.getEyePosition().y << ", " << camera.getEyePosition().z << ")" << std::endl;
std::cout << "Camera target: (" << camera.target.x << ", " << camera.target.y << ", " << camera.target.z << ")" << std::endl;
std::optional<std::pair<glm::ivec3, glm::ivec3>> raycast_result =
world.raycast_voxel(camera.getEyePosition(), camera.target, 3);
if (raycast_result.has_value()) {
auto [target_block, normal] = raycast_result.value();
std::cout << "Hit block at: (" << target_block.x << ", " << target_block.y << ", " << target_block.z << ")" << std::endl;
std::cout << "Normal: (" << normal.x << ", " << normal.y << ", " << normal.z << ")" << std::endl;
glm::ivec3 place_pos = target_block + normal;
std::cout << "Placing at: (" << place_pos.x << ", " << place_pos.y << ", " << place_pos.z << ")" << std::endl;
world.set_voxel(place_pos, VoxelKind::Stone);
} else {
std::cout << "No block hit within range" << std::endl;
}
}
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_RELEASE) {
left_mouse_pressed = false;
}
// Right click - remove block
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS && !right_mouse_pressed) {
right_mouse_pressed = true;
std::cout << "\n=== RIGHT CLICK (REMOVE) ===" << std::endl;
std::cout << "Camera eye pos: (" << camera.getEyePosition().x << ", " << camera.getEyePosition().y << ", " << camera.getEyePosition().z << ")" << std::endl;
std::cout << "Camera target: (" << camera.target.x << ", " << camera.target.y << ", " << camera.target.z << ")" << std::endl;
std::optional<std::pair<glm::ivec3, glm::ivec3>> raycast_result =
world.raycast_voxel(camera.getEyePosition(), camera.target, 3);
if (raycast_result.has_value()) {
auto [target_block, normal] = raycast_result.value();
std::cout << "Hit block at: (" << target_block.x << ", " << target_block.y << ", " << target_block.z << ")" << std::endl;
std::cout << "Removing block at: (" << target_block.x << ", " << target_block.y << ", " << target_block.z << ")" << std::endl;
world.set_voxel(target_block, std::nullopt);
} else {
std::cout << "No block hit within range" << std::endl;
}
}
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_RELEASE) {
right_mouse_pressed = false;
}
}
int main() {
@ -189,7 +236,7 @@ int main() {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 2. --- Create a Window ---
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Voxel Engine 0.5.0", NULL, NULL);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Voxel Engine 0.6.0", NULL, NULL);
if (window == NULL) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
@ -227,6 +274,30 @@ int main() {
// Setup projection matrix
projection = glm::perspective(glm::radians(FOV), (float)SCR_WIDTH / (float)SCR_HEIGHT, Z_NEAR, Z_FAR); // This doesn't need to be recalculated every frame
// Setup crosshair
unsigned int crosshairVAO, crosshairVBO;
glGenVertexArrays(1, &crosshairVAO);
glGenBuffers(1, &crosshairVBO);
glBindVertexArray(crosshairVAO);
glBindBuffer(GL_ARRAY_BUFFER, crosshairVBO);
// Crosshair vertices in NDC (Normalized Device Coordinates: -1 to 1)
float crosshairSize = 0.02f; // Size in NDC
float crosshairVertices[] = {
// Horizontal line
-crosshairSize, 0.0f, 0.0f,
crosshairSize, 0.0f, 0.0f,
// Vertical line
0.0f, -crosshairSize, 0.0f,
0.0f, crosshairSize, 0.0f
};
glBufferData(GL_ARRAY_BUFFER, sizeof(crosshairVertices), crosshairVertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
glm::mat4 view;
double start_time = glfwGetTime();
double move_time = glfwGetTime();
@ -246,15 +317,19 @@ int main() {
shader.use();
if (!debug_printed) {
std::cout << "\n=== RENDER DEBUG ===" << std::endl;
std::cout << "Camera pos: (" << camera.position.x << ", " << camera.position.y << ", " << camera.position.z << ")" << std::endl;
std::cout << "Camera target: (" << camera.target.x << ", " << camera.target.y << ", " << camera.target.z << ")" << std::endl;
// std::cout << "Number of chunks to draw: " << world.chunks.size() << std::endl;
}
// if (!debug_printed) {
// std::cout << "\n=== RENDER DEBUG ===" << std::endl;
// std::cout << "Camera pos: (" << camera.position.x << ", " << camera.position.y << ", " << camera.position.z << ")" << std::endl;
// std::cout << "Camera target: (" << camera.target.x << ", " << camera.target.y << ", " << camera.target.z << ")" << std::endl;
// // std::cout << "Number of chunks to draw: " << world.chunks.size() << std::endl;
// }
// Generate chunks around the camera
glm::ivec3 camera_chunk = glm::ivec3(camera.position) / CHUNK_SIZE;
glm::ivec3 camera_chunk = glm::ivec3(
std::floor(camera.position.x / CHUNK_SIZE),
std::floor(camera.position.y / CHUNK_SIZE),
std::floor(camera.position.z / CHUNK_SIZE)
);
// ---- PASS 1: Draw solid grey cubes ----
glEnable(GL_POLYGON_OFFSET_FILL);
@ -276,16 +351,26 @@ int main() {
Chunk* chunk = world.get_or_create_chunk(chunk_pos);
Mesh* chunk_mesh = chunk->getMesh();
if (chunk_mesh)
if (chunk_mesh) {
// Debug: Log when rendering chunks with your placed block
static bool logged_placed_block = false;
if (!logged_placed_block && chunk->voxels.count(glm::ivec3(0, 1, 31)) > 0) {
std::cout << "\n[RENDER] ===== FOUND PLACED BLOCK =====" << std::endl;
std::cout << "[RENDER] Drawing chunk at pos (" << chunk_pos.x << ", " << chunk_pos.y << ", " << chunk_pos.z << ")" << std::endl;
std::cout << "[RENDER] Translation: (" << (chunk_pos.x * CHUNK_SIZE) << ", " << (chunk_pos.y * CHUNK_SIZE) << ", " << (chunk_pos.z * CHUNK_SIZE) << ")" << std::endl;
std::cout << "[RENDER] Final world position should be: (" << (chunk_pos.x * CHUNK_SIZE + 0) << ", " << (chunk_pos.y * CHUNK_SIZE + 1) << ", " << (chunk_pos.z * CHUNK_SIZE + 31) << ")" << std::endl;
logged_placed_block = true;
}
chunk_mesh->draw();
}
}
}
if (!debug_printed) {
debug_printed = true;
}
// if (!debug_printed) {
// debug_printed = true;
// }
glDisable(GL_POLYGON_OFFSET_FILL);
// ---- PASS 2: Draw wireframe on top ----
@ -315,6 +400,16 @@ int main() {
// --- Reset polygon mode for next frame (good practice) ---
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// ---- Draw Crosshair ----
glDisable(GL_DEPTH_TEST); // Draw on top of everything
shader.use();
shader.setMat4("u_mvp", glm::mat4(1.0f)); // Identity matrix (NDC coordinates)
shader.setVec3("u_Color", glm::vec3(1.0f, 1.0f, 1.0f)); // White
glBindVertexArray(crosshairVAO);
glDrawArrays(GL_LINES, 0, 4); // 4 vertices = 2 lines
glBindVertexArray(0);
glEnable(GL_DEPTH_TEST);
double end_time = glfwGetTime();
float time_since_last_move = end_time - move_time;
if (time_since_last_move >= 0.017)