Compare commits
10
Commits
2d30ce1cb9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b19aefb34 | ||
|
|
243f69291e | ||
|
|
561df24953 | ||
|
|
15aba4f901 | ||
|
|
5920376262 | ||
|
|
de39d10776 | ||
|
|
57d749f09b | ||
|
|
55d01e86c1 | ||
|
|
4925849747 | ||
|
|
fc4960f25d |
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -25,6 +25,8 @@ add_executable(VoxelEngine
|
|||||||
src/Mesh.cpp
|
src/Mesh.cpp
|
||||||
src/Chunk.h
|
src/Chunk.h
|
||||||
src/Chunk.cpp
|
src/Chunk.cpp
|
||||||
|
src/Player.h
|
||||||
|
src/Perlin.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Configure GLAD (from your src/ and include/ folders) ---
|
# --- Configure GLAD (from your src/ and include/ folders) ---
|
||||||
|
|||||||
@@ -41,3 +41,21 @@ When placing a block, the program now raycasts to place where the camera is look
|
|||||||
It took me a couple of days to fix the ray casting. I forgot that my mesh is offset by 0.5, so 3/4 raycasts looked at least a block off of what the camera was looking at.
|
It took me a couple of days to fix the ray casting. I forgot that my mesh is offset by 0.5, so 3/4 raycasts looked at least a block off of what the camera was looking at.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 0.7.0 : Physics - 11/18/25
|
||||||
|
|
||||||
|
Woah! The camera doesn't just phase through placed blocks anymore! The camera can also fall into the void for eternity! So cool!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 0.8.0 : Data Structure Redo - 11/19/25
|
||||||
|
|
||||||
|
Final part of the blog post tutorial. Changing the data structure of the Chunk's to store voxels in a 1d array instead of a HashMap.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 0.9.0 : Perlin Noise - 11/24/25
|
||||||
|
|
||||||
|
Woah! Look at that, not a flat plane of dirt. This update adds variation to terrian height with a height map generated by perlin noise. Unfortunately, this tanks FPS. So I need to make the engine _faster_.
|
||||||
|
|
||||||
|

|
||||||
@@ -13,10 +13,12 @@ void main()
|
|||||||
// You can now use v_VoxelKind to modify the color
|
// You can now use v_VoxelKind to modify the color
|
||||||
// Example: Different colors for different voxel types
|
// Example: Different colors for different voxel types
|
||||||
vec3 color = u_Color;
|
vec3 color = u_Color;
|
||||||
if (v_VoxelKind == 0.0) {
|
if (v_VoxelKind == 1.0) {
|
||||||
// Dirt - brownish
|
// Dirt - brownish
|
||||||
color *= vec3(0.6, 0.4, 0.2);
|
color *= vec3(0.6, 0.4, 0.2);
|
||||||
} else if (v_VoxelKind == 1.0) {
|
} else if (v_VoxelKind == 2.0) {
|
||||||
|
color *= vec3(0.2, 0.8, 0.2);
|
||||||
|
} else if (v_VoxelKind == 3.0) {
|
||||||
// Stone - grayish (keep as is)
|
// Stone - grayish (keep as is)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+81
-121
@@ -1,77 +1,39 @@
|
|||||||
#ifndef CAMERA_H
|
#ifndef CAMERA_H
|
||||||
#define CAMERA_H
|
#define CAMERA_H
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include "World.h"
|
#include "Player.h"
|
||||||
|
#include "Frustum.h"
|
||||||
|
|
||||||
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
|
class Camera
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
glm::vec3 position;
|
Player* player;
|
||||||
glm::vec3 target;
|
glm::vec3 target;
|
||||||
glm::vec3 direction;
|
glm::vec3 direction;
|
||||||
glm::vec3 up;
|
glm::vec3 up;
|
||||||
|
|
||||||
// Movement
|
|
||||||
glm::vec3 horizontal_velocity;
|
|
||||||
glm::vec3 forward_velocity;
|
|
||||||
glm::vec3 vertical_velocity;
|
|
||||||
|
|
||||||
bool grounded;
|
|
||||||
|
|
||||||
float speed;
|
|
||||||
|
|
||||||
glm::vec3 worldUp;
|
glm::vec3 worldUp;
|
||||||
|
|
||||||
Camera(float speed, glm::vec3 pos)
|
Camera(Player* player)
|
||||||
{
|
{
|
||||||
this->speed = speed;
|
this->player = player;
|
||||||
this->vertical_velocity = glm::vec3(0.0f);
|
|
||||||
|
|
||||||
this->horizontal_velocity = glm::vec3(0.0f);
|
|
||||||
this->forward_velocity = glm::vec3(0.0f);
|
|
||||||
|
|
||||||
this->position = pos;
|
|
||||||
this->target = glm::vec3(0.0f, 0.0f, -1.0f);
|
this->target = glm::vec3(0.0f, 0.0f, -1.0f);
|
||||||
|
|
||||||
this->direction = glm::normalize(this->position);
|
this->direction = glm::normalize(this->player->position);
|
||||||
this->worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
|
this->worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
glm::vec3 right = glm::normalize(glm::cross(up, this->direction));
|
glm::vec3 right = glm::normalize(glm::cross(up, this->direction));
|
||||||
this->up = glm::cross(this->direction, right);
|
this->up = glm::cross(this->direction, right);
|
||||||
|
|
||||||
this->grounded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Camera(glm::vec3 pos, glm::vec3 target, float speed)
|
|
||||||
{
|
|
||||||
this->speed = speed;
|
|
||||||
this->vertical_velocity = glm::vec3(0.0f);
|
|
||||||
|
|
||||||
this->horizontal_velocity = glm::vec3(0.0f);
|
|
||||||
this->forward_velocity = glm::vec3(0.0f);
|
|
||||||
|
|
||||||
this->position = pos;
|
|
||||||
this->target = target;
|
|
||||||
|
|
||||||
this->direction = glm::normalize(this->position - this->target);
|
|
||||||
this->worldUp = glm::vec3(0.0f, 1.0f, 0.0f);
|
|
||||||
|
|
||||||
glm::vec3 right = glm::normalize(glm::cross(up, this->direction));
|
|
||||||
this->up = glm::cross(this->direction, right);
|
|
||||||
|
|
||||||
this->grounded = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 getView()
|
glm::mat4 getView()
|
||||||
{
|
{
|
||||||
glm::vec3 real_camera_pos = this->position;
|
glm::vec3 real_camera_pos = this->player->getEyePosition();
|
||||||
real_camera_pos.y += CAMERA_HEIGHT;
|
|
||||||
|
|
||||||
glm::mat4 v = glm::lookAt(real_camera_pos,
|
glm::mat4 v = glm::lookAt(real_camera_pos,
|
||||||
real_camera_pos + this->target,
|
real_camera_pos + this->target,
|
||||||
@@ -80,82 +42,80 @@ class Camera
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
void move(World* world, float time) // time passed in seconds since last move
|
|
||||||
{
|
|
||||||
// bool in_air = this->position.y > 1;
|
|
||||||
|
|
||||||
// if (this->position.y > 1)
|
|
||||||
// this->vertical_velocity.y -= (GRAVITY * time); // no way! physics reference?
|
|
||||||
|
|
||||||
glm::vec3 velocity = this->forward_velocity + this->horizontal_velocity + this->vertical_velocity;
|
|
||||||
|
|
||||||
glm::vec3 position_delta = (velocity * time);
|
|
||||||
|
|
||||||
// glm::vec3 new_position = this->position + position_delta;
|
|
||||||
|
|
||||||
// AABB Algorithm
|
|
||||||
glm::vec3 aabb_dims = glm::vec3(0.8f, 1.8f, 0.8f);
|
|
||||||
glm::vec3 aabb_half_dims = aabb_dims / 2.0f;
|
|
||||||
|
|
||||||
glm::vec3 aabb_center_offset = glm::vec3(0.0f, aabb_dims.y / 2.0f, 0.0f);
|
|
||||||
|
|
||||||
bool y_axis_collided = false;
|
|
||||||
|
|
||||||
for (int axis = 0; axis < 3; axis++) {
|
|
||||||
glm::vec3 target_position = this->position;
|
|
||||||
target_position[axis] += position_delta[axis];
|
|
||||||
|
|
||||||
glm::vec3 aabb_center = target_position + aabb_center_offset;
|
|
||||||
glm::vec3 adjusted_center = aabb_center + glm::vec3(0.5f, 0.5f, 0.5f);
|
|
||||||
glm::vec3 min = glm::floor(adjusted_center - aabb_half_dims);
|
|
||||||
glm::vec3 max = glm::ceil(adjusted_center + aabb_half_dims);
|
|
||||||
|
|
||||||
bool collided = false;
|
|
||||||
for (int x = min.x; x < max.x && !collided; x++) {
|
|
||||||
for (int y = min.y; y < max.y && !collided; y++) {
|
|
||||||
for (int z = min.z; z < max.z && !collided; z++) {
|
|
||||||
glm::ivec3 calculated_position = glm::ivec3(x, y, z);
|
|
||||||
if (world->get_voxel(calculated_position).has_value()) {
|
|
||||||
collided = true;
|
|
||||||
// std::cout << "Collided!!" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!collided) {
|
|
||||||
this->position[axis] = target_position[axis];
|
|
||||||
} else if (axis == 1) {
|
|
||||||
y_axis_collided = true;
|
|
||||||
grounded = true;
|
|
||||||
this->vertical_velocity.y = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!y_axis_collided) {
|
|
||||||
this->vertical_velocity.y -= GRAVITY * time;
|
|
||||||
grounded = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// this->position += movement;
|
|
||||||
|
|
||||||
// Don't want to clip into the ground right
|
|
||||||
// if (this->position.y <= 1 && in_air)
|
|
||||||
// {
|
|
||||||
// this->position.y = 0.0f;
|
|
||||||
// this->vertical_velocity.y = 0.0f;
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 getEyePosition() const {
|
glm::vec3 getEyePosition() const {
|
||||||
return position + glm::vec3(0.0f, CAMERA_HEIGHT, 0.0f);
|
return player->getEyePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build frustum from view-projection matrix (more reliable method)
|
||||||
|
Frustum createFrustumFromMatrix(const glm::mat4& viewProj) {
|
||||||
|
Frustum frustum;
|
||||||
|
|
||||||
|
// Extract planes from view-projection matrix
|
||||||
|
// Left plane
|
||||||
|
frustum.leftFace.normal.x = viewProj[0][3] + viewProj[0][0];
|
||||||
|
frustum.leftFace.normal.y = viewProj[1][3] + viewProj[1][0];
|
||||||
|
frustum.leftFace.normal.z = viewProj[2][3] + viewProj[2][0];
|
||||||
|
frustum.leftFace.distance = viewProj[3][3] + viewProj[3][0];
|
||||||
|
|
||||||
|
// Right plane
|
||||||
|
frustum.rightFace.normal.x = viewProj[0][3] - viewProj[0][0];
|
||||||
|
frustum.rightFace.normal.y = viewProj[1][3] - viewProj[1][0];
|
||||||
|
frustum.rightFace.normal.z = viewProj[2][3] - viewProj[2][0];
|
||||||
|
frustum.rightFace.distance = viewProj[3][3] - viewProj[3][0];
|
||||||
|
|
||||||
|
// Bottom plane
|
||||||
|
frustum.bottomFace.normal.x = viewProj[0][3] + viewProj[0][1];
|
||||||
|
frustum.bottomFace.normal.y = viewProj[1][3] + viewProj[1][1];
|
||||||
|
frustum.bottomFace.normal.z = viewProj[2][3] + viewProj[2][1];
|
||||||
|
frustum.bottomFace.distance = viewProj[3][3] + viewProj[3][1];
|
||||||
|
|
||||||
|
// Top plane
|
||||||
|
frustum.topFace.normal.x = viewProj[0][3] - viewProj[0][1];
|
||||||
|
frustum.topFace.normal.y = viewProj[1][3] - viewProj[1][1];
|
||||||
|
frustum.topFace.normal.z = viewProj[2][3] - viewProj[2][1];
|
||||||
|
frustum.topFace.distance = viewProj[3][3] - viewProj[3][1];
|
||||||
|
|
||||||
|
// Near plane
|
||||||
|
frustum.nearFace.normal.x = viewProj[0][3] + viewProj[0][2];
|
||||||
|
frustum.nearFace.normal.y = viewProj[1][3] + viewProj[1][2];
|
||||||
|
frustum.nearFace.normal.z = viewProj[2][3] + viewProj[2][2];
|
||||||
|
frustum.nearFace.distance = viewProj[3][3] + viewProj[3][2];
|
||||||
|
|
||||||
|
// Far plane
|
||||||
|
frustum.farFace.normal.x = viewProj[0][3] - viewProj[0][2];
|
||||||
|
frustum.farFace.normal.y = viewProj[1][3] - viewProj[1][2];
|
||||||
|
frustum.farFace.normal.z = viewProj[2][3] - viewProj[2][2];
|
||||||
|
frustum.farFace.distance = viewProj[3][3] - viewProj[3][2];
|
||||||
|
|
||||||
|
// Normalize all planes
|
||||||
|
float length;
|
||||||
|
|
||||||
|
length = glm::length(frustum.leftFace.normal);
|
||||||
|
frustum.leftFace.normal /= length;
|
||||||
|
frustum.leftFace.distance /= length;
|
||||||
|
|
||||||
|
length = glm::length(frustum.rightFace.normal);
|
||||||
|
frustum.rightFace.normal /= length;
|
||||||
|
frustum.rightFace.distance /= length;
|
||||||
|
|
||||||
|
length = glm::length(frustum.bottomFace.normal);
|
||||||
|
frustum.bottomFace.normal /= length;
|
||||||
|
frustum.bottomFace.distance /= length;
|
||||||
|
|
||||||
|
length = glm::length(frustum.topFace.normal);
|
||||||
|
frustum.topFace.normal /= length;
|
||||||
|
frustum.topFace.distance /= length;
|
||||||
|
|
||||||
|
length = glm::length(frustum.nearFace.normal);
|
||||||
|
frustum.nearFace.normal /= length;
|
||||||
|
frustum.nearFace.distance /= length;
|
||||||
|
|
||||||
|
length = glm::length(frustum.farFace.normal);
|
||||||
|
frustum.farFace.normal /= length;
|
||||||
|
frustum.farFace.distance /= length;
|
||||||
|
|
||||||
|
return frustum;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+34
-25
@@ -51,10 +51,10 @@ void addFace(glm::ivec3 pos, const float *faceVertices, float voxelKind, std::ve
|
|||||||
float world_z = v[2] + pos.z;
|
float world_z = v[2] + pos.z;
|
||||||
|
|
||||||
// Debug: Print first vertex of first face
|
// Debug: Print first vertex of first face
|
||||||
if (vbo.empty() && i == 0) {
|
// 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 - Local pos: (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
|
||||||
std::cout << "[MESH] First vertex - World: (" << world_x << ", " << world_y << ", " << world_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_x);
|
||||||
vbo.push_back(world_y);
|
vbo.push_back(world_y);
|
||||||
@@ -73,17 +73,14 @@ void addFace(glm::ivec3 pos, const float *faceVertices, float voxelKind, std::ve
|
|||||||
}
|
}
|
||||||
|
|
||||||
Chunk::Chunk() {
|
Chunk::Chunk() {
|
||||||
// mesh is initially null (nullptr)
|
this->data.fill(VoxelKind::Air);
|
||||||
}
|
|
||||||
|
|
||||||
Chunk::Chunk(std::unordered_map<glm::ivec3, VoxelKind> voxels) {
|
|
||||||
this->voxels = voxels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh* Chunk::getMesh() {
|
Mesh* Chunk::getMesh() {
|
||||||
// Generate mesh on first access if not already generated
|
// Generate mesh on first access if not already generated, or if dirty
|
||||||
if (!mesh && !voxels.empty()) {
|
if ((!mesh || mesh_dirty) && !this->isEmpty()) {
|
||||||
generateMesh();
|
generateMesh();
|
||||||
|
mesh_dirty = false;
|
||||||
}
|
}
|
||||||
return mesh.get();
|
return mesh.get();
|
||||||
}
|
}
|
||||||
@@ -103,14 +100,35 @@ void Chunk::generateMesh() {
|
|||||||
std::pair(glm::ivec3(0, 0, -1), backFace) // back
|
std::pair(glm::ivec3(0, 0, -1), backFace) // back
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const std::pair<const glm::ivec3, VoxelKind>& pair : voxels) {
|
for (int i = 0; i < TOTAL; ++i) {
|
||||||
const glm::ivec3& pos = pair.first;
|
VoxelKind kind = this->data[i];
|
||||||
const VoxelKind& kind = pair.second;
|
if (kind == VoxelKind::Air) continue;
|
||||||
|
|
||||||
for (const std::pair<glm::ivec3, const float*>& neighborPair : neighbors) {
|
int x = i % SIZE;
|
||||||
|
int y = (i / SIZE) % SIZE;
|
||||||
|
int z = i / (SIZE * SIZE);
|
||||||
|
|
||||||
|
glm::ivec3 pos(x, y, z);
|
||||||
|
|
||||||
|
for (const auto& neighborPair : neighbors) {
|
||||||
glm::ivec3 dir = neighborPair.first;
|
glm::ivec3 dir = neighborPair.first;
|
||||||
const float* face = neighborPair.second;
|
const float* face = neighborPair.second;
|
||||||
if (voxels.count(pos + dir) == 0) {
|
int nx = x + dir.x;
|
||||||
|
int ny = y + dir.y;
|
||||||
|
int nz = z + dir.z;
|
||||||
|
|
||||||
|
bool neighbour_is_solid = false;
|
||||||
|
// in-bounds neighbor -> check data
|
||||||
|
if (nx >= 0 && nx < SIZE && ny >= 0 && ny < SIZE && nz >= 0 && nz < SIZE) {
|
||||||
|
// std::cout << "Accessing " << nx << ", " << ny << ", " << nz << std::endl;
|
||||||
|
neighbour_is_solid = (data[index(nx, ny, nz)] != VoxelKind::Air);
|
||||||
|
} else {
|
||||||
|
// neighbour out of this chunk: treat as air (exposed face)
|
||||||
|
// or call into World to query adjacent chunk here
|
||||||
|
neighbour_is_solid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!neighbour_is_solid) {
|
||||||
addFace(pos, face, static_cast<float>(kind), vboData, eboData);
|
addFace(pos, face, static_cast<float>(kind), vboData, eboData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,12 +143,3 @@ void Chunk::generateMesh() {
|
|||||||
// Now, upload this data to our mesh object
|
// Now, upload this data to our mesh object
|
||||||
mesh->uploadData(vboData, eboData);
|
mesh->uploadData(vboData, eboData);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
+53
-5
@@ -1,21 +1,28 @@
|
|||||||
#ifndef CHUNK_H
|
#ifndef CHUNK_H
|
||||||
#define CHUNK_H
|
#define CHUNK_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#include "Mesh.h"
|
#include "Mesh.h"
|
||||||
|
|
||||||
enum VoxelKind {
|
enum class VoxelKind : uint8_t {
|
||||||
|
Air,
|
||||||
Dirt,
|
Dirt,
|
||||||
|
Grass,
|
||||||
Stone
|
Stone
|
||||||
};
|
};
|
||||||
|
|
||||||
const int CHUNK_SIZE = 32;
|
const int CHUNK_SIZE = 32;
|
||||||
|
|
||||||
// Hash function for glm::ivec3 to use with unordered_set
|
// Hash function for glm::ivec3 to use with unordered_set
|
||||||
|
// Still used in World.h
|
||||||
namespace std {
|
namespace std {
|
||||||
template <>
|
template <>
|
||||||
struct hash<glm::ivec3> {
|
struct hash<glm::ivec3> {
|
||||||
@@ -33,18 +40,59 @@ namespace std {
|
|||||||
|
|
||||||
class Chunk {
|
class Chunk {
|
||||||
public:
|
public:
|
||||||
std::unordered_map<glm::ivec3, VoxelKind> voxels;
|
|
||||||
|
|
||||||
Chunk();
|
Chunk();
|
||||||
Chunk(std::unordered_map<glm::ivec3, VoxelKind> voxels);
|
|
||||||
|
|
||||||
Mesh* getMesh(); // Returns pointer, can be null if not generated yet
|
Mesh* getMesh(); // Returns pointer, can be null if not generated yet
|
||||||
|
|
||||||
void generateMesh();
|
void generateMesh();
|
||||||
void regenerateMesh();
|
|
||||||
|
// Accessors
|
||||||
|
inline VoxelKind get(int x, int y, int z) const {
|
||||||
|
return data[index(x, y, z)];
|
||||||
|
}
|
||||||
|
inline VoxelKind get(glm::ivec3 pos) const {
|
||||||
|
// std::cout << "Getting Pos (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
|
||||||
|
return data[index(pos.x, pos.y, pos.z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutators
|
||||||
|
inline void set(int x, int y, int z, VoxelKind v) {
|
||||||
|
data[index(x, y, z)] = v;
|
||||||
|
empty_checked = false; // Invalidate cache
|
||||||
|
mesh_dirty = true; // Mark mesh as needing regeneration
|
||||||
|
}
|
||||||
|
inline void set(glm::ivec3 pos, VoxelKind v) {
|
||||||
|
// std::cout << "Setting Pos (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
|
||||||
|
data[index(pos.x, pos.y, pos.z)] = v;
|
||||||
|
empty_checked = false; // Invalidate cache
|
||||||
|
mesh_dirty = true; // Mark mesh as needing regeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other
|
||||||
|
inline bool isEmpty() const {
|
||||||
|
if (!empty_checked) {
|
||||||
|
is_empty = std::all_of(data.begin(), data.end(), [](VoxelKind k){
|
||||||
|
return k == VoxelKind::Air;
|
||||||
|
});
|
||||||
|
empty_checked = true;
|
||||||
|
}
|
||||||
|
return is_empty;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr int SIZE = CHUNK_SIZE;
|
||||||
|
static constexpr int TOTAL = SIZE * SIZE * SIZE;
|
||||||
|
std::array<VoxelKind, TOTAL> data;
|
||||||
|
|
||||||
std::unique_ptr<Mesh> mesh; // Nullable mesh
|
std::unique_ptr<Mesh> mesh; // Nullable mesh
|
||||||
|
mutable bool is_empty = true; // Cache empty state
|
||||||
|
mutable bool empty_checked = false; // Track if we've checked
|
||||||
|
bool mesh_dirty = true; // Track if mesh needs regeneration
|
||||||
|
|
||||||
|
static inline constexpr int index(int x, int y, int z) noexcept {
|
||||||
|
return x + (y * SIZE) + (z * SIZE * SIZE);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif CHUNK_H
|
#endif // CHUNK_H
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#ifndef FRUSTUM_H
|
||||||
|
#define FRUSTUM_H
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
struct Plane {
|
||||||
|
glm::vec3 normal = { 0.0f, 1.0f, 0.0f };
|
||||||
|
float distance = 0.0f;
|
||||||
|
|
||||||
|
// Distance from point to plane (positive = in front, negative = behind)
|
||||||
|
float distanceToPoint(const glm::vec3& point) const {
|
||||||
|
return glm::dot(normal, point) + distance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AABB {
|
||||||
|
glm::vec3 min;
|
||||||
|
glm::vec3 max;
|
||||||
|
|
||||||
|
// Get center point
|
||||||
|
glm::vec3 getCenter() const {
|
||||||
|
return (min + max) * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get positive extents (half-size)
|
||||||
|
glm::vec3 getExtents() const {
|
||||||
|
return (max - min) * 0.5f;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Frustum {
|
||||||
|
Plane topFace;
|
||||||
|
Plane bottomFace;
|
||||||
|
Plane rightFace;
|
||||||
|
Plane leftFace;
|
||||||
|
Plane farFace;
|
||||||
|
Plane nearFace;
|
||||||
|
|
||||||
|
// Test if AABB is in frustum (returns true if visible)
|
||||||
|
bool isAABBVisible(const AABB& aabb) const {
|
||||||
|
// For each plane, check if the AABB is completely behind it
|
||||||
|
// If it's behind any plane, it's outside the frustum
|
||||||
|
return isOnOrForwardPlane(topFace, aabb) &&
|
||||||
|
isOnOrForwardPlane(bottomFace, aabb) &&
|
||||||
|
isOnOrForwardPlane(rightFace, aabb) &&
|
||||||
|
isOnOrForwardPlane(leftFace, aabb) &&
|
||||||
|
isOnOrForwardPlane(farFace, aabb) &&
|
||||||
|
isOnOrForwardPlane(nearFace, aabb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Check if AABB is on or in front of plane
|
||||||
|
bool isOnOrForwardPlane(const Plane& plane, const AABB& aabb) const {
|
||||||
|
// Get the positive vertex (furthest point in direction of normal)
|
||||||
|
const glm::vec3 center = aabb.getCenter();
|
||||||
|
const glm::vec3 extents = aabb.getExtents();
|
||||||
|
|
||||||
|
// Project extents onto plane normal to get the effective radius
|
||||||
|
const float radius = extents.x * std::abs(plane.normal.x) +
|
||||||
|
extents.y * std::abs(plane.normal.y) +
|
||||||
|
extents.z * std::abs(plane.normal.z);
|
||||||
|
|
||||||
|
// Distance from center to plane
|
||||||
|
const float dist = plane.distanceToPoint(center);
|
||||||
|
|
||||||
|
// AABB is visible if center is within radius distance from plane
|
||||||
|
return dist >= -radius;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#ifndef PERLIN_H
|
||||||
|
#define PERLIN_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
// Doesn't need to be super fast because assumedly I'm not making Perlin noise _that_ often
|
||||||
|
std::vector<int> makePermutation() {
|
||||||
|
std::vector<int> permutation;
|
||||||
|
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
permutation.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 g(rd());
|
||||||
|
|
||||||
|
std::shuffle(permutation.begin(), permutation.end(), g);
|
||||||
|
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
permutation.push_back(permutation[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permutation;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 getConstantVector(int v) {
|
||||||
|
int h = v & 3;
|
||||||
|
|
||||||
|
if (h == 0) {
|
||||||
|
return glm::vec2(1.0f, 1.0f);
|
||||||
|
} else if (h == 1) {
|
||||||
|
return glm::vec2(-1.0f, 1.0f);
|
||||||
|
} else if (h == 2) {
|
||||||
|
return glm::vec2(-1.0f, -1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return glm::vec2(1.0f, -1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fade(float t) {
|
||||||
|
return ((6 * t - 15) * t + 10) * t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// linear interpolation
|
||||||
|
float lerp(float t, float a1, float a2) {
|
||||||
|
return a1 + t * (a2 - a1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float perlinNoise2D(const float x, const float y, const std::vector<int>& permutation) {
|
||||||
|
const int X = (int)floorf(x) & 255;
|
||||||
|
const int Y = (int)floorf(y) & 255;
|
||||||
|
|
||||||
|
const float xf = x - floorf(x);
|
||||||
|
const float yf = y - floorf(y);
|
||||||
|
|
||||||
|
const glm::vec2 topRight = glm::vec2(xf - 1.0f, yf - 1.0f);
|
||||||
|
const glm::vec2 topLeft = glm::vec2(xf, yf - 1.0f);
|
||||||
|
const glm::vec2 bottomRight = glm::vec2(xf - 1.0f, yf);
|
||||||
|
const glm::vec2 bottomLeft = glm::vec2(xf, yf);
|
||||||
|
|
||||||
|
const int valueTopRight = permutation[permutation[X+1]+Y+1];
|
||||||
|
const int valueTopLeft = permutation[permutation[X]+Y+1];
|
||||||
|
const int valueBottomRight = permutation[permutation[X+1]+Y];
|
||||||
|
const int valueBottomLeft = permutation[permutation[X]+Y];
|
||||||
|
|
||||||
|
const auto dotTopRight = glm::dot(topRight, getConstantVector(valueTopRight));
|
||||||
|
const auto dotTopLeft = glm::dot(topLeft, getConstantVector(valueTopLeft));
|
||||||
|
const auto dotBottomRight = glm::dot(bottomRight, getConstantVector(valueBottomRight));
|
||||||
|
const auto dotBottomLeft = glm::dot(bottomLeft, getConstantVector(valueBottomLeft));
|
||||||
|
|
||||||
|
const float u = fade(xf);
|
||||||
|
const float v = fade(yf);
|
||||||
|
|
||||||
|
return lerp(
|
||||||
|
u,
|
||||||
|
lerp(v, dotBottomLeft, dotTopLeft),
|
||||||
|
lerp(v, dotBottomRight, dotTopRight)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef PLAYER_H
|
||||||
|
#define PLAYER_H
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include "World.h"
|
||||||
|
|
||||||
|
const float PLAYER_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 Player
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
glm::vec3 position;
|
||||||
|
|
||||||
|
// Movement velocities
|
||||||
|
glm::vec3 horizontal_velocity;
|
||||||
|
glm::vec3 forward_velocity;
|
||||||
|
glm::vec3 vertical_velocity;
|
||||||
|
|
||||||
|
bool grounded;
|
||||||
|
float speed;
|
||||||
|
|
||||||
|
Player(float speed, glm::vec3 pos)
|
||||||
|
{
|
||||||
|
this->speed = speed;
|
||||||
|
this->position = pos;
|
||||||
|
|
||||||
|
this->horizontal_velocity = glm::vec3(0.0f);
|
||||||
|
this->forward_velocity = glm::vec3(0.0f);
|
||||||
|
this->vertical_velocity = glm::vec3(0.0f);
|
||||||
|
|
||||||
|
this->grounded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void move(World* world, float time) // time passed in seconds since last move
|
||||||
|
{
|
||||||
|
glm::vec3 velocity = this->forward_velocity + this->horizontal_velocity + this->vertical_velocity;
|
||||||
|
glm::vec3 position_delta = (velocity * time);
|
||||||
|
|
||||||
|
// AABB Algorithm
|
||||||
|
glm::vec3 aabb_dims = glm::vec3(0.8f, 1.8f, 0.8f);
|
||||||
|
glm::vec3 aabb_half_dims = aabb_dims / 2.0f;
|
||||||
|
glm::vec3 aabb_center_offset = glm::vec3(0.0f, aabb_dims.y / 2.0f, 0.0f);
|
||||||
|
|
||||||
|
bool y_axis_collided = false;
|
||||||
|
|
||||||
|
for (int axis = 0; axis < 3; axis++) {
|
||||||
|
glm::vec3 target_position = this->position;
|
||||||
|
target_position[axis] += position_delta[axis];
|
||||||
|
|
||||||
|
glm::vec3 aabb_center = target_position + aabb_center_offset;
|
||||||
|
glm::vec3 adjusted_center = aabb_center + glm::vec3(0.5f, 0.5f, 0.5f);
|
||||||
|
glm::vec3 min = glm::floor(adjusted_center - aabb_half_dims);
|
||||||
|
glm::vec3 max = glm::ceil(adjusted_center + aabb_half_dims);
|
||||||
|
|
||||||
|
bool collided = false;
|
||||||
|
for (int x = min.x; x < max.x && !collided; x++) {
|
||||||
|
for (int y = min.y; y < max.y && !collided; y++) {
|
||||||
|
for (int z = min.z; z < max.z && !collided; z++) {
|
||||||
|
glm::ivec3 calculated_position = glm::ivec3(x, y, z);
|
||||||
|
if (world->get_voxel(calculated_position) != VoxelKind::Air) {
|
||||||
|
collided = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collided) {
|
||||||
|
this->position[axis] = target_position[axis];
|
||||||
|
} else if (axis == 1) {
|
||||||
|
y_axis_collided = true;
|
||||||
|
grounded = true;
|
||||||
|
this->vertical_velocity.y = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!y_axis_collided) {
|
||||||
|
this->vertical_velocity.y -= GRAVITY * time;
|
||||||
|
grounded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 getEyePosition() const {
|
||||||
|
return position + glm::vec3(0.0f, PLAYER_HEIGHT, 0.0f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
+43
-29
@@ -1,4 +1,5 @@
|
|||||||
#include "World.h"
|
#include "World.h"
|
||||||
|
#include "Perlin.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@@ -20,51 +21,67 @@ glm::ivec3 floor_divide(glm::ivec3 value, int divisor) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
World::World() {}
|
World::World() {
|
||||||
|
this->noise_permutation = makePermutation();
|
||||||
|
}
|
||||||
|
|
||||||
void World::set_voxel(glm::ivec3 position, std::optional<VoxelKind> kind) {
|
void World::set_voxel(glm::ivec3 position, VoxelKind kind) {
|
||||||
glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
|
glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
|
||||||
glm::ivec3 local_position = positive_modulo(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] 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] 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;
|
// 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()) {
|
chunk->set(local_position, kind);
|
||||||
chunk->voxels.erase(chunk->voxels.find(local_position));
|
|
||||||
} else {
|
|
||||||
chunk->voxels[local_position] = kind.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk->regenerateMesh();
|
// No need to call regenerateMesh() - the dirty flag will trigger regeneration on next getMesh()
|
||||||
}
|
}
|
||||||
|
|
||||||
Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) {
|
Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) {
|
||||||
if (this->chunks.count(chunk_position) == 0) {
|
|
||||||
|
|
||||||
// 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;
|
auto [it, inserted] = this->chunks.try_emplace(chunk_position);
|
||||||
if (chunk_position.y == 0) {
|
Chunk& chunk = it->second;
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
|
||||||
|
const float scale = 0.05f; // smaller = smoother, larger = more variation
|
||||||
|
const float heightMultiplier = 20.0f; // Maximum terrian height variation
|
||||||
|
const int baseHeight = 0; // Sea Level
|
||||||
|
|
||||||
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 x = 0; x < CHUNK_SIZE; x++) {
|
for (int x = 0; x < CHUNK_SIZE; x++) {
|
||||||
|
glm::ivec3 world_pos = chunk_position * CHUNK_SIZE + glm::ivec3(x, 0, z);
|
||||||
|
|
||||||
|
float noise = perlinNoise2D(world_pos.x * scale, world_pos.z * scale, this->noise_permutation);
|
||||||
|
|
||||||
|
float normalizedNoise = (noise + 1.0f) * 0.5f;
|
||||||
|
int terrainHeight = baseHeight + (int)(normalizedNoise * heightMultiplier);
|
||||||
|
|
||||||
|
for (int y = 0; y < CHUNK_SIZE; y++) {
|
||||||
glm::ivec3 offset = glm::ivec3(x, y, z);
|
glm::ivec3 offset = glm::ivec3(x, y, z);
|
||||||
glm::ivec3 world_voxel_position = chunk_position * CHUNK_SIZE + offset;
|
glm::ivec3 world_voxel_position = chunk_position * CHUNK_SIZE + offset;
|
||||||
|
|
||||||
if (world_voxel_position.y == 0) {
|
VoxelKind kind;
|
||||||
voxels[offset] = VoxelKind::Dirt;
|
if (world_voxel_position.y < terrainHeight - 3) {
|
||||||
} else if (world_voxel_position.y < 0) {
|
kind = VoxelKind::Stone;
|
||||||
voxels[offset] = VoxelKind::Stone;
|
} else if (world_voxel_position.y < terrainHeight) {
|
||||||
|
kind = VoxelKind::Dirt; // Near surface
|
||||||
|
} else if (world_voxel_position.y == terrainHeight) {
|
||||||
|
kind = VoxelKind::Grass; // Surface block (could be grass)
|
||||||
|
} else {
|
||||||
|
kind = VoxelKind::Air; // Above terrain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chunk.set(offset, kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->chunks[chunk_position] = Chunk(voxels);
|
return &chunk;
|
||||||
}
|
|
||||||
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) {
|
std::optional<std::pair<glm::ivec3, glm::ivec3>> World::raycast_voxel(glm::vec3 start, glm::vec3 direction, float max_dist) {
|
||||||
@@ -133,7 +150,7 @@ std::optional<std::pair<glm::ivec3, glm::ivec3>> World::raycast_voxel(glm::vec3
|
|||||||
|
|
||||||
// std::cout << "[RAYCAST] Iteration " << iterations << " at voxel (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
|
// std::cout << "[RAYCAST] Iteration " << iterations << " at voxel (" << pos.x << ", " << pos.y << ", " << pos.z << ")" << std::endl;
|
||||||
|
|
||||||
if (this->get_voxel(pos).has_value()) {
|
if (this->get_voxel(pos) != VoxelKind::Air) {
|
||||||
// std::cout << "[RAYCAST] Hit voxel at (" << pos.x << ", " << pos.y << ", " << pos.z << ") after " << iterations << " iterations" << std::endl;
|
// 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;
|
// std::cout << "[RAYCAST] Returning normal: (" << -last_move.x << ", " << -last_move.y << ", " << -last_move.z << ")" << std::endl;
|
||||||
return std::make_pair(pos, -last_move);
|
return std::make_pair(pos, -last_move);
|
||||||
@@ -162,14 +179,11 @@ std::optional<std::pair<glm::ivec3, glm::ivec3>> World::raycast_voxel(glm::vec3
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<VoxelKind> World::get_voxel(glm::ivec3 position) {
|
VoxelKind World::get_voxel(glm::ivec3 position) {
|
||||||
glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
|
glm::ivec3 chunk_position = floor_divide(position, CHUNK_SIZE);
|
||||||
glm::ivec3 local_position = positive_modulo(position, CHUNK_SIZE);
|
glm::ivec3 local_position = positive_modulo(position, CHUNK_SIZE);
|
||||||
|
|
||||||
Chunk* chunk = this->get_or_create_chunk(chunk_position);
|
Chunk* chunk = this->get_or_create_chunk(chunk_position);
|
||||||
auto it = chunk->voxels.find(local_position);
|
|
||||||
if (it != chunk->voxels.end()) {
|
return chunk->get(local_position);
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
+6
-2
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
@@ -15,13 +16,16 @@ class World
|
|||||||
|
|
||||||
World();
|
World();
|
||||||
|
|
||||||
void set_voxel(glm::ivec3 position, std::optional<VoxelKind> kind);
|
void set_voxel(glm::ivec3 position, VoxelKind kind);
|
||||||
|
|
||||||
std::optional<std::pair<glm::ivec3, glm::ivec3>> raycast_voxel(glm::vec3 start, glm::vec3 direction, float max_dist);
|
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);
|
VoxelKind get_voxel(glm::ivec3 position);
|
||||||
|
|
||||||
Chunk* get_or_create_chunk(glm::ivec3 chunk_position);
|
Chunk* get_or_create_chunk(glm::ivec3 chunk_position);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<int> noise_permutation;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif WORLD_H
|
#endif WORLD_H
|
||||||
+71
-42
@@ -5,6 +5,7 @@
|
|||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#include "Shader.h"
|
#include "Shader.h"
|
||||||
|
#include "Player.h"
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
#include "Object3D.h"
|
#include "Object3D.h"
|
||||||
#include "World.h"
|
#include "World.h"
|
||||||
@@ -30,12 +31,15 @@ const float Z_NEAR = 0.1f;
|
|||||||
bool WIREFRAME_MODE = !true;
|
bool WIREFRAME_MODE = !true;
|
||||||
const float FOV = 110.0f;
|
const float FOV = 110.0f;
|
||||||
|
|
||||||
const int VIEW_DISTANCE = 4; // In Chunks
|
const std::string VERSION = "0.9.1";
|
||||||
|
|
||||||
|
const int VIEW_DISTANCE = 4;
|
||||||
|
|
||||||
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.85f, 0.0f));
|
Player player = Player(CAMERA_SPEED, glm::vec3(0.0f, 20.0f, 0.0f));
|
||||||
|
Camera camera = Camera(&player);
|
||||||
|
|
||||||
// Targeted block for highlighting
|
// Targeted block for highlighting
|
||||||
std::optional<glm::ivec3> targeted_block = std::nullopt;
|
std::optional<glm::ivec3> targeted_block = std::nullopt;
|
||||||
@@ -141,25 +145,25 @@ void processInput(GLFWwindow *window)
|
|||||||
// Forward / Backward
|
// Forward / Backward
|
||||||
if(glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
|
if(glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
|
||||||
{
|
{
|
||||||
camera.forward_velocity = camera.speed * glm::vec3(camera.target.x, 0.0f, camera.target.z);
|
player.forward_velocity = player.speed * glm::vec3(camera.target.x, 0.0f, camera.target.z);
|
||||||
W_pressed = true;
|
W_pressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
|
if(glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
|
||||||
{
|
{
|
||||||
camera.forward_velocity = -camera.speed * glm::vec3(camera.target.x, 0.0f, camera.target.z);
|
player.forward_velocity = -player.speed * glm::vec3(camera.target.x, 0.0f, camera.target.z);
|
||||||
S_pressed = true;
|
S_pressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(glfwGetKey(window, GLFW_KEY_S) == GLFW_RELEASE && S_pressed)
|
if(glfwGetKey(window, GLFW_KEY_S) == GLFW_RELEASE && S_pressed)
|
||||||
{
|
{
|
||||||
camera.forward_velocity = glm::vec3(0.0f);
|
player.forward_velocity = glm::vec3(0.0f);
|
||||||
S_pressed = false;
|
S_pressed = false;
|
||||||
}
|
}
|
||||||
if(glfwGetKey(window, GLFW_KEY_W) == GLFW_RELEASE && W_pressed)
|
if(glfwGetKey(window, GLFW_KEY_W) == GLFW_RELEASE && W_pressed)
|
||||||
{
|
{
|
||||||
camera.forward_velocity = glm::vec3(0.0f);
|
player.forward_velocity = glm::vec3(0.0f);
|
||||||
W_pressed = false;
|
W_pressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,44 +171,44 @@ void processInput(GLFWwindow *window)
|
|||||||
// Horizontal
|
// Horizontal
|
||||||
if(glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
|
if(glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
|
||||||
{
|
{
|
||||||
camera.horizontal_velocity = -glm::normalize(glm::cross(glm::vec3(camera.target.x, 0.0f, camera.target.z), camera.worldUp)) * camera.speed;
|
player.horizontal_velocity = -glm::normalize(glm::cross(glm::vec3(camera.target.x, 0.0f, camera.target.z), camera.worldUp)) * player.speed;
|
||||||
A_pressed = true;
|
A_pressed = true;
|
||||||
}
|
}
|
||||||
if(glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
|
if(glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
|
||||||
{
|
{
|
||||||
camera.horizontal_velocity = glm::normalize(glm::cross(glm::vec3(camera.target.x, 0.0f, camera.target.z), camera.worldUp)) * camera.speed;
|
player.horizontal_velocity = glm::normalize(glm::cross(glm::vec3(camera.target.x, 0.0f, camera.target.z), camera.worldUp)) * player.speed;
|
||||||
D_pressed = true;
|
D_pressed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(glfwGetKey(window, GLFW_KEY_A) == GLFW_RELEASE && A_pressed)
|
if(glfwGetKey(window, GLFW_KEY_A) == GLFW_RELEASE && A_pressed)
|
||||||
{
|
{
|
||||||
camera.horizontal_velocity = glm::vec3(0.0f);
|
player.horizontal_velocity = glm::vec3(0.0f);
|
||||||
A_pressed = false;
|
A_pressed = false;
|
||||||
}
|
}
|
||||||
if(glfwGetKey(window, GLFW_KEY_D) == GLFW_RELEASE && D_pressed)
|
if(glfwGetKey(window, GLFW_KEY_D) == GLFW_RELEASE && D_pressed)
|
||||||
{
|
{
|
||||||
camera.horizontal_velocity = glm::vec3(0.0f);
|
player.horizontal_velocity = glm::vec3(0.0f);
|
||||||
D_pressed = false;
|
D_pressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Jump
|
// Jump
|
||||||
if(glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
|
if(glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
|
||||||
// camera.velocity -= glm::normalize(glm::cross(camera.target, glm::normalize(glm::cross(camera.target, camera.worldUp)))) * camera.speed * 0.5f;
|
// player.velocity -= glm::normalize(glm::cross(camera.target, glm::normalize(glm::cross(camera.target, camera.worldUp)))) * player.speed * 0.5f;
|
||||||
{
|
{
|
||||||
if (current_time - last_jump > JUMP_COOLDOWN && camera.grounded)
|
if (current_time - last_jump > JUMP_COOLDOWN && player.grounded)
|
||||||
{
|
{
|
||||||
camera.vertical_velocity.y = JUMP_POWER;
|
player.vertical_velocity.y = JUMP_POWER;
|
||||||
last_jump = current_time;
|
last_jump = current_time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sprint
|
// Sprint
|
||||||
if(glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS)
|
if(glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS)
|
||||||
camera.speed = CAMERA_SPEED * 7.5;
|
player.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;
|
player.speed = CAMERA_SPEED * 20;
|
||||||
|
|
||||||
static bool left_mouse_pressed = false;
|
static bool left_mouse_pressed = false;
|
||||||
static bool right_mouse_pressed = false;
|
static bool right_mouse_pressed = false;
|
||||||
@@ -248,7 +252,7 @@ void processInput(GLFWwindow *window)
|
|||||||
auto [target_block, normal] = raycast_result.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 << "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;
|
std::cout << "Removing block at: (" << target_block.x << ", " << target_block.y << ", " << target_block.z << ")" << std::endl;
|
||||||
world.set_voxel(target_block, std::nullopt);
|
world.set_voxel(target_block, VoxelKind::Air);
|
||||||
} else {
|
} else {
|
||||||
std::cout << "No block hit within range" << std::endl;
|
std::cout << "No block hit within range" << std::endl;
|
||||||
}
|
}
|
||||||
@@ -269,7 +273,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.6.1", NULL, NULL);
|
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, ("Voxel Engine " + VERSION).c_str(), 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();
|
||||||
@@ -391,8 +395,24 @@ int main() {
|
|||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
bool debug_printed = false;
|
bool debug_printed = false;
|
||||||
|
|
||||||
|
// FPS counter variables
|
||||||
|
double last_fps_update = glfwGetTime();
|
||||||
|
int frame_count = 0;
|
||||||
|
double fps = 0.0;
|
||||||
|
|
||||||
// 6. --- The Render Loop ---
|
// 6. --- The Render Loop ---
|
||||||
while (!glfwWindowShouldClose(window)) {
|
while (!glfwWindowShouldClose(window)) {
|
||||||
|
// Update FPS counter
|
||||||
|
double current_time = glfwGetTime();
|
||||||
|
frame_count++;
|
||||||
|
if (current_time - last_fps_update >= 1.0) {
|
||||||
|
fps = frame_count / (current_time - last_fps_update);
|
||||||
|
std::string title = "Voxel Engine " + VERSION + " | FPS: " + std::to_string((int)fps);
|
||||||
|
glfwSetWindowTitle(window, title.c_str());
|
||||||
|
frame_count = 0;
|
||||||
|
last_fps_update = current_time;
|
||||||
|
}
|
||||||
|
|
||||||
// Input (e.g., close window on ESC)
|
// Input (e.g., close window on ESC)
|
||||||
processInput(window);
|
processInput(window);
|
||||||
|
|
||||||
@@ -403,32 +423,40 @@ int main() {
|
|||||||
|
|
||||||
shader.use();
|
shader.use();
|
||||||
|
|
||||||
// if (!debug_printed) {
|
// Generate chunks around the player
|
||||||
// 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(
|
glm::ivec3 camera_chunk = glm::ivec3(
|
||||||
std::floor(camera.position.x / CHUNK_SIZE),
|
std::floor(player.position.x / CHUNK_SIZE),
|
||||||
std::floor(camera.position.y / CHUNK_SIZE),
|
std::floor(player.position.y / CHUNK_SIZE),
|
||||||
std::floor(camera.position.z / CHUNK_SIZE)
|
std::floor(player.position.z / CHUNK_SIZE)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create frustum for culling from view-projection matrix
|
||||||
|
glm::mat4 viewProj = projection * view;
|
||||||
|
Frustum frustum = camera.createFrustumFromMatrix(viewProj);
|
||||||
|
|
||||||
// ---- PASS 1: Draw solid grey cubes ----
|
// ---- PASS 1: Draw solid grey cubes ----
|
||||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||||
glPolygonOffset(1.0, 1.0); // Push solid faces "back"
|
glPolygonOffset(1.0, 1.0); // Push solid faces "back"
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||||
shader.setVec3("u_Color", glm::vec3(0.5, 0.5, 0.5));
|
shader.setVec3("u_Color", glm::vec3(0.5, 0.5, 0.5));
|
||||||
|
|
||||||
for (int y = -VIEW_DISTANCE; y < VIEW_DISTANCE; y++) {
|
for (int y = -2; y < 2; y++) { // Reduced from -VIEW_DISTANCE to VIEW_DISTANCE
|
||||||
for (int z = -VIEW_DISTANCE; z < VIEW_DISTANCE; z++) {
|
for (int z = -VIEW_DISTANCE; z < VIEW_DISTANCE; z++) {
|
||||||
for (int x = -VIEW_DISTANCE; x < VIEW_DISTANCE; x++) {
|
for (int x = -VIEW_DISTANCE; x < VIEW_DISTANCE; x++) {
|
||||||
glm::ivec3 chunk_offset = glm::ivec3(x, y, z);
|
glm::ivec3 chunk_offset = glm::ivec3(x, y, z);
|
||||||
glm::ivec3 chunk_pos = camera_chunk + chunk_offset;
|
glm::ivec3 chunk_pos = camera_chunk + chunk_offset;
|
||||||
|
|
||||||
|
// Create AABB for this chunk
|
||||||
|
glm::vec3 chunkWorldPos = glm::vec3(chunk_pos * CHUNK_SIZE);
|
||||||
|
AABB chunkAABB;
|
||||||
|
chunkAABB.min = chunkWorldPos;
|
||||||
|
chunkAABB.max = chunkWorldPos + glm::vec3(CHUNK_SIZE);
|
||||||
|
|
||||||
|
// Frustum culling - skip if chunk is not visible
|
||||||
|
if (!frustum.isAABBVisible(chunkAABB)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
glm::mat4 model = glm::mat4(1.0f);
|
glm::mat4 model = glm::mat4(1.0f);
|
||||||
model = glm::translate(model, glm::vec3(chunk_pos * CHUNK_SIZE));
|
model = glm::translate(model, glm::vec3(chunk_pos * CHUNK_SIZE));
|
||||||
glm::mat4 mvp = projection * view * model;
|
glm::mat4 mvp = projection * view * model;
|
||||||
@@ -437,21 +465,11 @@ 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) {
|
// if (!debug_printed) {
|
||||||
// debug_printed = true;
|
// debug_printed = true;
|
||||||
@@ -463,12 +481,23 @@ int main() {
|
|||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Just the lines
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Just the lines
|
||||||
shader.setVec3("u_Color", glm::vec3(1.0, 1.0, 1.0));
|
shader.setVec3("u_Color", glm::vec3(1.0, 1.0, 1.0));
|
||||||
|
|
||||||
for (int y = -VIEW_DISTANCE; y < VIEW_DISTANCE; y++) {
|
for (int y = -2; y < 2; y++) { // Reduced from -VIEW_DISTANCE to VIEW_DISTANCE
|
||||||
for (int z = -VIEW_DISTANCE; z < VIEW_DISTANCE; z++) {
|
for (int z = -VIEW_DISTANCE; z < VIEW_DISTANCE; z++) {
|
||||||
for (int x = -VIEW_DISTANCE; x < VIEW_DISTANCE; x++) {
|
for (int x = -VIEW_DISTANCE; x < VIEW_DISTANCE; x++) {
|
||||||
glm::ivec3 chunk_offset = glm::ivec3(x, y, z);
|
glm::ivec3 chunk_offset = glm::ivec3(x, y, z);
|
||||||
glm::ivec3 chunk_pos = camera_chunk + chunk_offset;
|
glm::ivec3 chunk_pos = camera_chunk + chunk_offset;
|
||||||
|
|
||||||
|
// Create AABB for this chunk
|
||||||
|
glm::vec3 chunkWorldPos = glm::vec3(chunk_pos * CHUNK_SIZE);
|
||||||
|
AABB chunkAABB;
|
||||||
|
chunkAABB.min = chunkWorldPos;
|
||||||
|
chunkAABB.max = chunkWorldPos + glm::vec3(CHUNK_SIZE);
|
||||||
|
|
||||||
|
// Frustum culling - skip if chunk is not visible
|
||||||
|
if (!frustum.isAABBVisible(chunkAABB)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
glm::mat4 model = glm::mat4(1.0f);
|
glm::mat4 model = glm::mat4(1.0f);
|
||||||
model = glm::translate(model, glm::vec3(chunk_pos * CHUNK_SIZE));
|
model = glm::translate(model, glm::vec3(chunk_pos * CHUNK_SIZE));
|
||||||
glm::mat4 mvp = projection * view * model;
|
glm::mat4 mvp = projection * view * model;
|
||||||
@@ -516,7 +545,7 @@ int main() {
|
|||||||
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)
|
||||||
{
|
{
|
||||||
camera.move(&world, time_since_last_move);
|
player.move(&world, time_since_last_move);
|
||||||
move_time = end_time;
|
move_time = end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user