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. 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) ![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/glm.hpp>
#include <glm/gtc/matrix_transform.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 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 class Camera
@ -62,11 +62,12 @@ class Camera
glm::mat4 getView() glm::mat4 getView()
{ {
glm::mat4 v = glm::lookAt(this->position, glm::vec3 real_camera_pos = this->position;
this->target + this->position, real_camera_pos.y += CAMERA_HEIGHT;
this->worldUp);
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; return v;
} }
@ -92,6 +93,9 @@ class Camera
} }
glm::vec3 getEyePosition() const {
return position + glm::vec3(0.0f, CAMERA_HEIGHT, 0.0f);
}
}; };
#endif #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; unsigned int vIndex = vbo.size() / VERTEX_SIZE;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
const float* v = &faceVertices[i * 6]; // Face data is still 6 floats per vertex const float* v = &faceVertices[i * 6]; // Face data is still 6 floats per vertex
vbo.push_back(v[0] + pos.x); float world_x = v[0] + pos.x;
vbo.push_back(v[1] + pos.y); float world_y = v[1] + pos.y;
vbo.push_back(v[2] + pos.z); 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[3]);
vbo.push_back(v[4]); vbo.push_back(v[4]);
vbo.push_back(v[5]); vbo.push_back(v[5]);
@ -117,5 +127,10 @@ void Chunk::generateMesh() {
} }
void Chunk::regenerateMesh() { 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(); this->generateMesh();
} }

View File

@ -1,10 +1,34 @@
#include "World.h" #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() {} World::World() {}
void World::set_voxel(glm::ivec3 position, std::optional<VoxelKind> kind) { void World::set_voxel(glm::ivec3 position, std::optional<VoxelKind> kind) {
glm::ivec3 chunk_position = position / CHUNK_SIZE; glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
glm::ivec3 local_position = 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); Chunk * chunk = get_or_create_chunk(chunk_position);
if (!kind.has_value()) { 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 // Fill in new chunk with a flat plane of voxels at y=0
std::unordered_map<glm::ivec3, VoxelKind> voxels; std::unordered_map<glm::ivec3, VoxelKind> voxels;
if (chunk_position.y == 0) { if (chunk_position.y == 0) {
for (int z = 0; z <= CHUNK_SIZE; z++) { for (int z = 0; z < CHUNK_SIZE; z++) {
for (int y = 0; y <= CHUNK_SIZE; y++) { for (int y = 0; y < CHUNK_SIZE; y++) {
for (int x = 0; x <= CHUNK_SIZE; x++) { for (int x = 0; x < CHUNK_SIZE; x++) {
glm::ivec3 offset = glm::ivec3(x, y, z); 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) { if (world_voxel_position.y == 0) {
voxels[offset] = VoxelKind::Dirt; voxels[offset] = VoxelKind::Dirt;
@ -42,3 +66,86 @@ Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) {
} }
return &this->chunks[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); 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); 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; bool W_pressed, S_pressed, A_pressed, D_pressed = false;
glm::mat4 projection = glm::mat4(1.0f); 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 // Callback function for when the window is resized
// glfw: whenever the window size changed (by OS or user resize) this callback function executes // 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.y = -sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
camera.target = glm::normalize(direction); 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 // 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; camera.speed = CAMERA_SPEED * 7.5;
if(glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_RELEASE) if(glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_RELEASE)
camera.speed = CAMERA_SPEED * 20; 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() { int main() {
@ -189,7 +236,7 @@ int main() {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 2. --- Create a Window --- // 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) { if (window == NULL) {
std::cerr << "Failed to create GLFW window" << std::endl; std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate(); glfwTerminate();
@ -227,6 +274,30 @@ int main() {
// Setup projection matrix // 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 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; glm::mat4 view;
double start_time = glfwGetTime(); double start_time = glfwGetTime();
double move_time = glfwGetTime(); double move_time = glfwGetTime();
@ -246,15 +317,19 @@ int main() {
shader.use(); shader.use();
if (!debug_printed) { // if (!debug_printed) {
std::cout << "\n=== RENDER DEBUG ===" << std::endl; // 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 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 << "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; // // std::cout << "Number of chunks to draw: " << world.chunks.size() << std::endl;
} // }
// Generate chunks around the camera // 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 ---- // ---- PASS 1: Draw solid grey cubes ----
glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_POLYGON_OFFSET_FILL);
@ -276,16 +351,26 @@ int main() {
Chunk* chunk = world.get_or_create_chunk(chunk_pos); Chunk* chunk = world.get_or_create_chunk(chunk_pos);
Mesh* chunk_mesh = chunk->getMesh(); 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(); chunk_mesh->draw();
} }
} }
} }
if (!debug_printed) {
debug_printed = true;
} }
// if (!debug_printed) {
// debug_printed = true;
// }
glDisable(GL_POLYGON_OFFSET_FILL); glDisable(GL_POLYGON_OFFSET_FILL);
// ---- PASS 2: Draw wireframe on top ---- // ---- PASS 2: Draw wireframe on top ----
@ -315,6 +400,16 @@ int main() {
// --- Reset polygon mode for next frame (good practice) --- // --- Reset polygon mode for next frame (good practice) ---
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 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(); double end_time = glfwGetTime();
float time_since_last_move = end_time - move_time; float time_since_last_move = end_time - move_time;
if (time_since_last_move >= 0.017) if (time_since_last_move >= 0.017)