[Add] Initial terrain generation.
This commit is contained in:
parent
506356458a
commit
7ba0b348bc
18
libbettola/include/bettola/game/chunk.h
Normal file
18
libbettola/include/bettola/game/chunk.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace BettolaLib {
|
||||
namespace Game {
|
||||
|
||||
const int CHUNK_WIDTH = 32;
|
||||
const int CHUNK_HEIGHT = 32;
|
||||
|
||||
struct Chunk {
|
||||
std::vector<float> heightmap;
|
||||
|
||||
Chunk(void) : heightmap(CHUNK_WIDTH * CHUNK_HEIGHT) {}
|
||||
};
|
||||
|
||||
} /* namespace Game. */
|
||||
} /* namespace BettolaLib. */
|
||||
14
libbettola/include/bettola/network/chunk_message.h
Normal file
14
libbettola/include/bettola/network/chunk_message.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "bettola/game/chunk.h"
|
||||
|
||||
namespace BettolaLib {
|
||||
namespace Network {
|
||||
|
||||
struct ChunkMessage {
|
||||
int chunk_x;
|
||||
int chunk_z;
|
||||
float heightmap[BettolaLib::Game::CHUNK_WIDTH * BettolaLib::Game::CHUNK_HEIGHT];
|
||||
};
|
||||
|
||||
} /* namespace Network. */
|
||||
} /* namespace BettolaLib. */
|
||||
@ -8,6 +8,7 @@ enum class MessageType : unsigned char {
|
||||
PlayerState,
|
||||
GameState,
|
||||
PlayerId,
|
||||
ChunkData,
|
||||
};
|
||||
|
||||
struct MessageHeader {
|
||||
|
||||
@ -126,7 +126,7 @@ void Bettola::update(double dt) {
|
||||
|
||||
void Bettola::render(void) {
|
||||
_renderer.render(_camera, _game_client.get_player(),
|
||||
_game_client.get_remote_players());
|
||||
_game_client.get_remote_players(), _game_client.get_world());
|
||||
SDL_GL_SwapWindow(_window);
|
||||
}
|
||||
|
||||
|
||||
23
src/game/world.cpp
Normal file
23
src/game/world.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "world.h"
|
||||
#include <cstring>
|
||||
#include "bettola/game/chunk.h"
|
||||
#include "graphics/chunk_mesh.h"
|
||||
|
||||
World::~World(void) {
|
||||
for(auto const& [pos, mesh] : _chunk_meshes) {
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
|
||||
void World::add_chunk(int chunk_x, int chunk_z, const float* heightmap) {
|
||||
ChunkPos pos = { chunk_x, chunk_z };
|
||||
if(_chunk_meshes.count(pos)) {
|
||||
/* TODO: Chunk already exists, perhaps update it later.. */
|
||||
return;
|
||||
}
|
||||
|
||||
BettolaLib::Game::Chunk chunk;
|
||||
memcpy(chunk.heightmap.data(), heightmap, chunk.heightmap.size()*sizeof(float));
|
||||
|
||||
_chunk_meshes[pos] = new ChunkMesh(chunk_x, chunk_z, chunk);
|
||||
}
|
||||
28
src/game/world.h
Normal file
28
src/game/world.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "graphics/chunk_mesh.h"
|
||||
|
||||
/* Allow the use of a pair of ints as a map key. */
|
||||
struct ChunkPos {
|
||||
int x, z;
|
||||
bool operator<(const ChunkPos& other) const {
|
||||
if(x < other.x) return true;
|
||||
if(x > other.x) return false;
|
||||
return z < other.z;
|
||||
}
|
||||
};
|
||||
|
||||
class World {
|
||||
public:
|
||||
~World(void);
|
||||
|
||||
void add_chunk(int chunk_x, int chunk_z, const float* heightmap);
|
||||
const std::map<ChunkPos, ChunkMesh*>& get_chunk_meshes(void) const {
|
||||
return _chunk_meshes;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<ChunkPos, ChunkMesh*> _chunk_meshes;
|
||||
};
|
||||
@ -9,6 +9,7 @@
|
||||
#include "bettola/game/player_base.h"
|
||||
#include "game/remote_player.h"
|
||||
#include "bettola/math/vec3.h"
|
||||
#include "bettola/network/chunk_message.h"
|
||||
#include "bettola/network/game_state_message.h"
|
||||
#include "bettola/network/message.h"
|
||||
|
||||
@ -58,12 +59,35 @@ void GameClient::process_network_messages(void) {
|
||||
struct timeval tv = {0,0}; /* Non-blocking. */
|
||||
|
||||
if(select(max_fd+1, &read_fds, nullptr, nullptr, &tv) > 0) {
|
||||
if(FD_ISSET(tcp_fd, &read_fds) && _our_player_id == 0) {
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
if(_tcp_socket.recv(&header, sizeof(header)) > 0 && header.type ==
|
||||
BettolaLib::Network::MessageType::PlayerId) {
|
||||
_tcp_socket.recv(&_our_player_id, sizeof(_our_player_id));
|
||||
printf("BetollaClient: Assigned player ID: %u\n", _our_player_id);
|
||||
if(FD_ISSET(tcp_fd, &read_fds)) {
|
||||
/* Drain all data from the TCP socket. */
|
||||
while(true) {
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
ssize_t header_bytes = _tcp_socket.recv(&header, sizeof(header));
|
||||
|
||||
if(header_bytes <= 0) {
|
||||
/* No more data, or an error? */
|
||||
break;
|
||||
}
|
||||
|
||||
if(header.type == BettolaLib::Network::MessageType::PlayerId) {
|
||||
if(header.size == sizeof(unsigned int)) {
|
||||
_tcp_socket.recv(&_our_player_id, sizeof(_our_player_id));
|
||||
printf("BettolaClient: Assigned player ID %u\n", _our_player_id);
|
||||
}
|
||||
} else if(header.type == BettolaLib::Network::MessageType::ChunkData) {
|
||||
if(header.size == sizeof(BettolaLib::Network::ChunkMessage)) {
|
||||
BettolaLib::Network::ChunkMessage msg;
|
||||
_tcp_socket.recv(&msg, sizeof(msg));
|
||||
_world.add_chunk(msg.chunk_x, msg.chunk_z, msg.heightmap);
|
||||
}
|
||||
} else {
|
||||
/* Discard message payloads we don't understand to prevent de-sync. */
|
||||
char discard_buffer[4096];
|
||||
if(header.size > 0 && header.size < sizeof(discard_buffer)) {
|
||||
_tcp_socket.recv(discard_buffer, header.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(FD_ISSET(udp_fd, &read_fds)) {
|
||||
@ -178,3 +202,7 @@ void GameClient::update_players(float dt) {
|
||||
}
|
||||
}
|
||||
|
||||
const World& GameClient::get_world(void) const {
|
||||
return _world;
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include "game/player.h"
|
||||
#include "game/remote_player.h"
|
||||
#include "game/world.h"
|
||||
#include "graphics/camera.h"
|
||||
#include "bettola/network/tcpsocket.h"
|
||||
#include "bettola/network/udpsocket.h"
|
||||
@ -25,6 +26,8 @@ public:
|
||||
const std::vector<RemotePlayer>& get_remote_players(void) const { return _remote_players; }
|
||||
void update_players(float dt);
|
||||
|
||||
const World& get_world(void) const;
|
||||
|
||||
private:
|
||||
void _process_udp_messages(void);
|
||||
void _process_game_state(const BettolaLib::Network::GameStateMessage& msg);
|
||||
@ -39,4 +42,6 @@ private:
|
||||
|
||||
unsigned int _input_sequence_number;
|
||||
std::deque<BettolaLib::Network::PlayerInputMessage> _pending_inputs;
|
||||
|
||||
World _world;
|
||||
};
|
||||
|
||||
69
src/graphics/chunk_mesh.cpp
Normal file
69
src/graphics/chunk_mesh.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include <GL/glew.h>
|
||||
#include "bettola/game/chunk.h"
|
||||
#include "chunk_mesh.h"
|
||||
|
||||
ChunkMesh::ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& chunk) {
|
||||
/* Generate vertices from the heightmap. */
|
||||
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT; ++z) {
|
||||
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH; ++x) {
|
||||
/* Vertex position. */
|
||||
_vertices.push_back((float)(chunk_x * (BettolaLib::Game::CHUNK_WIDTH-1) + x));
|
||||
_vertices.push_back(chunk.heightmap[z * BettolaLib::Game::CHUNK_WIDTH+x] * 5.0f); /* 5x scale height */
|
||||
_vertices.push_back((float)(chunk_z * (BettolaLib::Game::CHUNK_HEIGHT-1) + z));
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate indices for the triangles. */
|
||||
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT-1; ++z) {
|
||||
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH-1; ++x) {
|
||||
int top_left = z * BettolaLib::Game::CHUNK_WIDTH + x;
|
||||
int top_right = top_left + 1;
|
||||
int bottom_left = (z+1) * BettolaLib::Game::CHUNK_WIDTH + x;
|
||||
int bottom_right = bottom_left + 1;
|
||||
|
||||
/* First triangle of the quad. */
|
||||
_indices.push_back(top_left);
|
||||
_indices.push_back(bottom_left);
|
||||
_indices.push_back(top_right);
|
||||
|
||||
/* Second triangle of the quad. */
|
||||
_indices.push_back(top_right);
|
||||
_indices.push_back(bottom_left);
|
||||
_indices.push_back(bottom_right);
|
||||
}
|
||||
}
|
||||
|
||||
_setup_mesh();
|
||||
}
|
||||
|
||||
ChunkMesh::~ChunkMesh(void) {
|
||||
glDeleteVertexArrays(1, &_vao);
|
||||
glDeleteBuffers(1, &_vbo);
|
||||
glDeleteBuffers(1, &_ebo);
|
||||
}
|
||||
|
||||
void ChunkMesh::_setup_mesh(void) {
|
||||
glGenVertexArrays(1, &_vao);
|
||||
glGenBuffers(1, &_vbo);
|
||||
glGenBuffers(1, &_ebo);
|
||||
|
||||
glBindVertexArray(_vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, _vertices.size()*sizeof(float), _vertices.data(), GL_STATIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size()*sizeof(unsigned int), _indices.data(), GL_STATIC_DRAW);
|
||||
|
||||
/* Position attrib. */
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void ChunkMesh::draw(void) {
|
||||
glBindVertexArray(_vao);
|
||||
glDrawElements(GL_TRIANGLES, _indices.size(), GL_UNSIGNED_INT, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
20
src/graphics/chunk_mesh.h
Normal file
20
src/graphics/chunk_mesh.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "bettola//game/chunk.h"
|
||||
|
||||
class ChunkMesh {
|
||||
public:
|
||||
ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& chunk);
|
||||
~ChunkMesh(void);
|
||||
|
||||
void draw(void);
|
||||
|
||||
private:
|
||||
void _setup_mesh(void);
|
||||
|
||||
unsigned int _vao, _vbo, _ebo;
|
||||
std::vector<float> _vertices;
|
||||
std::vector<unsigned int> _indices;
|
||||
};
|
||||
@ -2,6 +2,7 @@
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include "game/player.h"
|
||||
#include "game/world.h"
|
||||
#include "graphics/camera.h"
|
||||
#include <SDL3/SDL_timer.h>
|
||||
|
||||
@ -136,7 +137,8 @@ bool Renderer::init(int screen_width, int screen_height) {
|
||||
}
|
||||
|
||||
void Renderer::render(const Camera& camera, const Player& player,
|
||||
const std::vector<RemotePlayer>& remote_players) {
|
||||
const std::vector<RemotePlayer>& remote_players,
|
||||
const World& world) {
|
||||
glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Need to clear depth buffer too. */
|
||||
GL_CHECK_ERROR();
|
||||
@ -168,6 +170,15 @@ void Renderer::render(const Camera& camera, const Player& player,
|
||||
/* Set players colour back. */
|
||||
glUniform3f(color_loc, 0.2f, 0.5f, 0.8f);
|
||||
|
||||
/* Render world. */
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); /* Wireframe to see geometry. */
|
||||
for(auto const& [pos, mesh] : world.get_chunk_meshes()) {
|
||||
BettolaMath::Mat4 model;
|
||||
_shader.set_mat4("model", model);
|
||||
mesh->draw();
|
||||
}
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); /* Reset to default. */
|
||||
|
||||
/* Draw the local player's cube. */
|
||||
const auto& player_pos = player.get_position();
|
||||
BettolaMath::Mat4 trans_matrix = BettolaMath::Mat4::translation(player_pos.x,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "game/world.h"
|
||||
#include "graphics/camera.h"
|
||||
#include "shader.h"
|
||||
#include "game/player.h"
|
||||
@ -14,7 +15,8 @@ public:
|
||||
|
||||
bool init(int screen_width, int screen_height);
|
||||
void render(const Camera& camera, const Player& player,
|
||||
const std::vector<RemotePlayer>& remote_players);
|
||||
const std::vector<RemotePlayer>& remote_players,
|
||||
const World& world);
|
||||
|
||||
private:
|
||||
bool _init_shaders();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "bettola/math/mat4.h"
|
||||
|
||||
class Shader {
|
||||
public:
|
||||
@ -9,6 +10,8 @@ public:
|
||||
|
||||
bool load_from_files(const std::string& vert_path, const std::string& frag_path);
|
||||
void use(void);
|
||||
|
||||
void set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const;
|
||||
unsigned int get_id(void) const { return _program_id; }
|
||||
private:
|
||||
bool compile_shader(unsigned int& shader_id, const char* shader_source, int shader_type);
|
||||
|
||||
7
src/graphics/shader_uniforms.cpp
Normal file
7
src/graphics/shader_uniforms.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
#include <GL/glew.h>
|
||||
#include "bettola/math/mat4.h"
|
||||
#include "shader.h"
|
||||
|
||||
void Shader::set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const {
|
||||
glUniformMatrix4fv(glGetUniformLocation(_program_id, name.c_str()), 1, GL_FALSE, matrix.get_ptr());
|
||||
}
|
||||
@ -5,12 +5,15 @@
|
||||
#include "bettola/network/tcpsocket.h"
|
||||
#include "bettola/network/udpsocket.h"
|
||||
#include "player.h"
|
||||
#include "world.h"
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Player* add_player(BettolaLib::Network::TCPSocket* socket);
|
||||
void remove_player(unsigned int player_id);
|
||||
|
||||
void send_initial_chunks(Player* player);
|
||||
|
||||
void process_udp_message(const char* buffer, size_t size, const sockaddr_in& from_addr);
|
||||
void broadcast_game_state(BettolaLib::Network::UDPSocket& udp_socket);
|
||||
|
||||
@ -19,4 +22,5 @@ public:
|
||||
|
||||
private:
|
||||
std::vector<Player*> _players;
|
||||
World _world;
|
||||
};
|
||||
|
||||
27
srv/game/game_send_chunks.cpp
Normal file
27
srv/game/game_send_chunks.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <cstring>
|
||||
#include "bettola/game/chunk.h"
|
||||
#include "game.h"
|
||||
#include "bettola/network/chunk_message.h"
|
||||
#include "bettola/network/message.h"
|
||||
#include "player.h"
|
||||
|
||||
void Game::send_initial_chunks(Player* player) {
|
||||
BettolaLib::Network::MessageHeader header;
|
||||
header.type = BettolaLib::Network::MessageType::ChunkData;
|
||||
header.size = sizeof(BettolaLib::Network::ChunkMessage);
|
||||
|
||||
/* Send a 3x3 grid of chunks around the origin. */
|
||||
for(int x = -1; x <=1; ++x) {
|
||||
for(int z = -1; z <= 1; ++z) {
|
||||
BettolaLib::Game::Chunk& chunk = _world.get_chunk(x, z);
|
||||
|
||||
BettolaLib::Network::ChunkMessage msg;
|
||||
msg.chunk_x = x;
|
||||
msg.chunk_z = z;
|
||||
memcpy(msg.heightmap, chunk.heightmap.data(), chunk.heightmap.size() * sizeof(float));
|
||||
|
||||
player->get_socket().send(&header, sizeof(header));
|
||||
player->get_socket().send(&msg, sizeof(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
srv/game/world.cpp
Normal file
34
srv/game/world.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "world.h"
|
||||
#include "bettola/game/chunk.h"
|
||||
#include "bettola/noise/fast_noise_lite.h"
|
||||
|
||||
World::World(void) {
|
||||
m_noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin);
|
||||
m_noise.SetFrequency(0.02f);
|
||||
}
|
||||
|
||||
BettolaLib::Game::Chunk& World::get_chunk(int x, int z) {
|
||||
ChunkPos pos = {x, z};
|
||||
auto it = m_chunks.find(pos);
|
||||
if(it != m_chunks.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
/* If chunk doesn't exist, generate it. */
|
||||
BettolaLib::Game::Chunk& new_chunk = m_chunks[pos];
|
||||
_generate_chunk(new_chunk, x, z);
|
||||
return new_chunk;
|
||||
}
|
||||
|
||||
void World::_generate_chunk(BettolaLib::Game::Chunk& chunk, int chunk_x, int chunk_z) {
|
||||
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT; ++z) {
|
||||
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH; ++x) {
|
||||
/* Calculate world coordinates. */
|
||||
float world_x = (float)(chunk_x * BettolaLib::Game::CHUNK_WIDTH + x);
|
||||
float world_z = (float)(chunk_z * BettolaLib::Game::CHUNK_HEIGHT + z);
|
||||
|
||||
/* generate noise value. */
|
||||
chunk.heightmap[z * BettolaLib::Game::CHUNK_WIDTH + x] = m_noise.GetNoise(world_x, world_z);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
srv/game/world.h
Normal file
28
srv/game/world.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "bettola/noise/fast_noise_lite.h"
|
||||
#include "bettola/game/chunk.h"
|
||||
|
||||
struct ChunkPos {
|
||||
int x, z;
|
||||
bool operator<(const ChunkPos& other) const {
|
||||
if(x < other.x) return true;
|
||||
if(x > other.x) return false;
|
||||
return z < other.z;
|
||||
}
|
||||
};
|
||||
|
||||
class World {
|
||||
public:
|
||||
World(void);
|
||||
|
||||
BettolaLib::Game::Chunk& get_chunk(int x, int z);
|
||||
|
||||
private:
|
||||
void _generate_chunk(BettolaLib::Game::Chunk& chunk, int chunk_x, int chunk_z);
|
||||
|
||||
FastNoiseLite m_noise;
|
||||
std::map<ChunkPos, BettolaLib::Game::Chunk> m_chunks;
|
||||
};
|
||||
|
||||
@ -112,6 +112,8 @@ int main(void) {
|
||||
unsigned int id = new_player->get_id();
|
||||
client_socket->send(&id, sizeof(id));
|
||||
|
||||
game.send_initial_chunks(new_player);
|
||||
|
||||
int client_flags = fcntl(client_socket->get_sockfd(), F_GETFL, 0);
|
||||
fcntl(client_socket->get_sockfd(), F_SETFL, client_flags | O_NONBLOCK);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user