return movement authority to server
This commit is contained in:
parent
663444157e
commit
dd682b8b67
BIN
assets/heightmap_large.bin
Normal file
BIN
assets/heightmap_large.bin
Normal file
Binary file not shown.
BIN
assets/heightmap_small.bin
Normal file
BIN
assets/heightmap_small.bin
Normal file
Binary file not shown.
64
client/config.hpp
Normal file
64
client/config.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
struct GameConfig {
|
||||
// Terrain configuration
|
||||
std::string heightmapFile = "../assets/heightmap.bin";
|
||||
float unitsPerSample = 1.0f; // Meters per heightmap sample
|
||||
float heightScale = 1.0f; // Height multiplier
|
||||
|
||||
// Window configuration
|
||||
int windowWidth = 1280;
|
||||
int windowHeight = 720;
|
||||
std::string windowTitle = "Game";
|
||||
int targetFPS = 60;
|
||||
|
||||
// Movement
|
||||
float moveSpeed = 15.0f;
|
||||
|
||||
// Network
|
||||
float heartbeatInterval = 5.0f;
|
||||
|
||||
// Debug options
|
||||
bool showDebugInfo = false;
|
||||
|
||||
// Load configuration from command line args or config file
|
||||
static GameConfig fromArgs(int argc, char* argv[]) {
|
||||
GameConfig config;
|
||||
|
||||
// Parse command line arguments
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "--heightmap" && i + 1 < argc) {
|
||||
config.heightmapFile = argv[++i];
|
||||
} else if (arg == "--scale" && i + 1 < argc) {
|
||||
config.unitsPerSample = std::stof(argv[++i]);
|
||||
} else if (arg == "--height-scale" && i + 1 < argc) {
|
||||
config.heightScale = std::stof(argv[++i]);
|
||||
} else if (arg == "--debug") {
|
||||
config.showDebugInfo = true;
|
||||
} else if (arg == "--help") {
|
||||
printHelp();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private:
|
||||
static void printHelp() {
|
||||
std::cout << "Game Options:\n";
|
||||
std::cout << " --heightmap <file> Load specific heightmap file\n";
|
||||
std::cout << " --scale <value> Units per heightmap sample (default: 1.0)\n";
|
||||
std::cout << " --height-scale <val> Height multiplier (default: 1.0)\n";
|
||||
std::cout << " --debug Show debug information\n";
|
||||
std::cout << " --help Show this help\n";
|
||||
std::cout << "\nExamples:\n";
|
||||
std::cout << " ./game --heightmap ../assets/heightmap_small.bin\n";
|
||||
std::cout << " ./game --heightmap ../assets/heightmap_large.bin --scale 2.0\n";
|
||||
}
|
||||
};
|
||||
206
client/terrain/Heightmap.hpp
Normal file
206
client/terrain/Heightmap.hpp
Normal file
@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
|
||||
#include <raylib.h>
|
||||
#include <raymath.h>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <cstdint>
|
||||
|
||||
class Heightmap {
|
||||
public:
|
||||
struct Config {
|
||||
float unitsPerSample = 1.0f; // How many world units (meters) per heightmap sample
|
||||
float heightScale = 1.0f; // Scale factor for height values
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<float> data;
|
||||
int samplesPerSide{0}; // Number of samples along one edge
|
||||
float worldWidth{0.0f}; // Total world width in units
|
||||
float worldHeight{0.0f}; // Total world height in units
|
||||
Config config;
|
||||
|
||||
public:
|
||||
Heightmap() : config{} {}
|
||||
Heightmap(const Config& cfg) : config(cfg) {}
|
||||
|
||||
// Load heightmap from binary file
|
||||
bool load(const std::string& filename) {
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
if (!file) return false;
|
||||
|
||||
int32_t fileSize;
|
||||
file.read(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
|
||||
samplesPerSide = fileSize;
|
||||
|
||||
data.resize(samplesPerSide * samplesPerSide);
|
||||
file.read(reinterpret_cast<char*>(data.data()), data.size() * sizeof(float));
|
||||
|
||||
// Calculate world dimensions based on samples and units per sample
|
||||
worldWidth = worldHeight = (samplesPerSide - 1) * config.unitsPerSample;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get interpolated height at world position
|
||||
float getHeightAtPosition(float worldX, float worldZ) const {
|
||||
// Convert world coordinates to sample coordinates
|
||||
float sampleX = (worldX + worldWidth * 0.5f) / config.unitsPerSample;
|
||||
float sampleZ = (worldZ + worldHeight * 0.5f) / config.unitsPerSample;
|
||||
|
||||
// Clamp to valid range
|
||||
sampleX = std::max(0.0f, std::min(sampleX, float(samplesPerSide - 1)));
|
||||
sampleZ = std::max(0.0f, std::min(sampleZ, float(samplesPerSide - 1)));
|
||||
|
||||
// Get integer sample indices
|
||||
int x0 = static_cast<int>(sampleX);
|
||||
int z0 = static_cast<int>(sampleZ);
|
||||
int x1 = std::min(x0 + 1, samplesPerSide - 1);
|
||||
int z1 = std::min(z0 + 1, samplesPerSide - 1);
|
||||
|
||||
// Get fractional parts for interpolation
|
||||
float fx = sampleX - x0;
|
||||
float fz = sampleZ - z0;
|
||||
|
||||
// Get heights at four corners
|
||||
float h00 = data[z0 * samplesPerSide + x0] * config.heightScale;
|
||||
float h10 = data[z0 * samplesPerSide + x1] * config.heightScale;
|
||||
float h01 = data[z1 * samplesPerSide + x0] * config.heightScale;
|
||||
float h11 = data[z1 * samplesPerSide + x1] * config.heightScale;
|
||||
|
||||
// Bilinear interpolation
|
||||
float h0 = h00 * (1.0f - fx) + h10 * fx;
|
||||
float h1 = h01 * (1.0f - fx) + h11 * fx;
|
||||
return h0 * (1.0f - fz) + h1 * fz;
|
||||
}
|
||||
|
||||
// Generate mesh from heightmap
|
||||
Mesh generateMesh() const {
|
||||
auto mesh = Mesh{};
|
||||
int vertexCount = samplesPerSide * samplesPerSide;
|
||||
int triangleCount = (samplesPerSide - 1) * (samplesPerSide - 1) * 2;
|
||||
|
||||
mesh.vertexCount = vertexCount;
|
||||
mesh.triangleCount = triangleCount;
|
||||
mesh.vertices = (float*)MemAlloc(vertexCount * 3 * sizeof(float));
|
||||
mesh.texcoords = (float*)MemAlloc(vertexCount * 2 * sizeof(float));
|
||||
mesh.normals = (float*)MemAlloc(vertexCount * 3 * sizeof(float));
|
||||
mesh.indices = (unsigned short*)MemAlloc(triangleCount * 3 * sizeof(unsigned short));
|
||||
|
||||
// Generate vertices
|
||||
for (int z = 0; z < samplesPerSide; z++) {
|
||||
for (int x = 0; x < samplesPerSide; x++) {
|
||||
int idx = z * samplesPerSide + x;
|
||||
|
||||
// World position in units (meters)
|
||||
// Center the terrain around origin
|
||||
float worldX = (x * config.unitsPerSample) - worldWidth * 0.5f;
|
||||
float worldZ = (z * config.unitsPerSample) - worldHeight * 0.5f;
|
||||
float worldY = data[idx] * config.heightScale;
|
||||
|
||||
mesh.vertices[idx * 3] = worldX;
|
||||
mesh.vertices[idx * 3 + 1] = worldY;
|
||||
mesh.vertices[idx * 3 + 2] = worldZ;
|
||||
|
||||
// Texture coordinates: 1 unit = 1 texture tile
|
||||
// Map from world coordinates to texture coordinates
|
||||
float texU = worldX + worldWidth * 0.5f;
|
||||
float texV = worldZ + worldHeight * 0.5f;
|
||||
|
||||
mesh.texcoords[idx * 2] = texU;
|
||||
mesh.texcoords[idx * 2 + 1] = texV;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate indices
|
||||
int triIdx = 0;
|
||||
for (int z = 0; z < samplesPerSide - 1; z++) {
|
||||
for (int x = 0; x < samplesPerSide - 1; x++) {
|
||||
int topLeft = z * samplesPerSide + x;
|
||||
int topRight = topLeft + 1;
|
||||
int bottomLeft = (z + 1) * samplesPerSide + x;
|
||||
int bottomRight = bottomLeft + 1;
|
||||
|
||||
mesh.indices[triIdx++] = topLeft;
|
||||
mesh.indices[triIdx++] = bottomLeft;
|
||||
mesh.indices[triIdx++] = topRight;
|
||||
|
||||
mesh.indices[triIdx++] = topRight;
|
||||
mesh.indices[triIdx++] = bottomLeft;
|
||||
mesh.indices[triIdx++] = bottomRight;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate proper normals
|
||||
calculateNormals(mesh);
|
||||
|
||||
UploadMesh(&mesh, false);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Getters
|
||||
float getWorldWidth() const { return worldWidth; }
|
||||
float getWorldHeight() const { return worldHeight; }
|
||||
Vector3 getWorldBounds() const {
|
||||
return {worldWidth, getMaxHeight() * config.heightScale, worldHeight};
|
||||
}
|
||||
Vector3 getWorldCenter() const { return {0, 0, 0}; }
|
||||
int getSamplesPerSide() const { return samplesPerSide; }
|
||||
|
||||
private:
|
||||
void calculateNormals(Mesh& mesh) const {
|
||||
// Initialize normals to zero
|
||||
for (int i = 0; i < mesh.vertexCount * 3; i++) {
|
||||
mesh.normals[i] = 0.0f;
|
||||
}
|
||||
|
||||
// Calculate face normals and add to vertex normals
|
||||
for (int i = 0; i < mesh.triangleCount; i++) {
|
||||
unsigned short i1 = mesh.indices[i * 3];
|
||||
unsigned short i2 = mesh.indices[i * 3 + 1];
|
||||
unsigned short i3 = mesh.indices[i * 3 + 2];
|
||||
|
||||
Vector3 v1 = {mesh.vertices[i1 * 3], mesh.vertices[i1 * 3 + 1], mesh.vertices[i1 * 3 + 2]};
|
||||
Vector3 v2 = {mesh.vertices[i2 * 3], mesh.vertices[i2 * 3 + 1], mesh.vertices[i2 * 3 + 2]};
|
||||
Vector3 v3 = {mesh.vertices[i3 * 3], mesh.vertices[i3 * 3 + 1], mesh.vertices[i3 * 3 + 2]};
|
||||
|
||||
Vector3 edge1 = Vector3Subtract(v2, v1);
|
||||
Vector3 edge2 = Vector3Subtract(v3, v1);
|
||||
Vector3 normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2));
|
||||
|
||||
// Add face normal to each vertex
|
||||
mesh.normals[i1 * 3] += normal.x;
|
||||
mesh.normals[i1 * 3 + 1] += normal.y;
|
||||
mesh.normals[i1 * 3 + 2] += normal.z;
|
||||
|
||||
mesh.normals[i2 * 3] += normal.x;
|
||||
mesh.normals[i2 * 3 + 1] += normal.y;
|
||||
mesh.normals[i2 * 3 + 2] += normal.z;
|
||||
|
||||
mesh.normals[i3 * 3] += normal.x;
|
||||
mesh.normals[i3 * 3 + 1] += normal.y;
|
||||
mesh.normals[i3 * 3 + 2] += normal.z;
|
||||
}
|
||||
|
||||
// Normalize vertex normals
|
||||
for (int i = 0; i < mesh.vertexCount; i++) {
|
||||
Vector3 normal = {
|
||||
mesh.normals[i * 3],
|
||||
mesh.normals[i * 3 + 1],
|
||||
mesh.normals[i * 3 + 2]
|
||||
};
|
||||
normal = Vector3Normalize(normal);
|
||||
mesh.normals[i * 3] = normal.x;
|
||||
mesh.normals[i * 3 + 1] = normal.y;
|
||||
mesh.normals[i * 3 + 2] = normal.z;
|
||||
}
|
||||
}
|
||||
|
||||
float getMaxHeight() const {
|
||||
float maxHeight = 0.0f;
|
||||
for (float h : data) {
|
||||
maxHeight = std::max(maxHeight, h);
|
||||
}
|
||||
return maxHeight;
|
||||
}
|
||||
};
|
||||
89
server/net/world.go
Normal file
89
server/net/world.go
Normal file
@ -0,0 +1,89 @@
|
||||
package net
|
||||
|
||||
import "math"
|
||||
|
||||
// WorldConfig holds world configuration based on heightmap
|
||||
type WorldConfig struct {
|
||||
SamplesPerSide int // Number of heightmap samples per side
|
||||
UnitsPerSample float32 // World units (meters) per sample
|
||||
WorldWidth float32 // Total world width in units
|
||||
WorldHeight float32 // Total world height (depth) in units
|
||||
MinBounds Vec3 // Minimum world bounds
|
||||
MaxBounds Vec3 // Maximum world bounds
|
||||
MaxTerrainHeight float32 // Maximum terrain height
|
||||
}
|
||||
|
||||
// NewWorldConfig creates world configuration from heightmap
|
||||
func NewWorldConfig(heightmap [][]float32, unitsPerSample float32) *WorldConfig {
|
||||
samplesPerSide := len(heightmap)
|
||||
worldSize := float32(samplesPerSide-1) * unitsPerSample
|
||||
halfSize := worldSize * 0.5
|
||||
|
||||
// Find max terrain height
|
||||
maxHeight := float32(0)
|
||||
for y := range heightmap {
|
||||
for x := range heightmap[y] {
|
||||
if heightmap[y][x] > maxHeight {
|
||||
maxHeight = heightmap[y][x]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &WorldConfig{
|
||||
SamplesPerSide: samplesPerSide,
|
||||
UnitsPerSample: unitsPerSample,
|
||||
WorldWidth: worldSize,
|
||||
WorldHeight: worldSize,
|
||||
MinBounds: Vec3{X: -halfSize, Y: 0, Z: -halfSize},
|
||||
MaxBounds: Vec3{X: halfSize, Y: maxHeight + 10, Z: halfSize},
|
||||
MaxTerrainHeight: maxHeight,
|
||||
}
|
||||
}
|
||||
|
||||
// ClampPosition clamps a position to world bounds
|
||||
func (w *WorldConfig) ClampPosition(pos Vec3) Vec3 {
|
||||
return Vec3{
|
||||
X: float32(math.Max(float64(w.MinBounds.X), math.Min(float64(w.MaxBounds.X), float64(pos.X)))),
|
||||
Y: float32(math.Max(float64(w.MinBounds.Y), math.Min(float64(w.MaxBounds.Y), float64(pos.Y)))),
|
||||
Z: float32(math.Max(float64(w.MinBounds.Z), math.Min(float64(w.MaxBounds.Z), float64(pos.Z)))),
|
||||
}
|
||||
}
|
||||
|
||||
// IsInBounds checks if a position is within world bounds
|
||||
func (w *WorldConfig) IsInBounds(pos Vec3) bool {
|
||||
return pos.X >= w.MinBounds.X && pos.X <= w.MaxBounds.X &&
|
||||
pos.Y >= w.MinBounds.Y && pos.Y <= w.MaxBounds.Y &&
|
||||
pos.Z >= w.MinBounds.Z && pos.Z <= w.MaxBounds.Z
|
||||
}
|
||||
|
||||
// GetHeightAt gets interpolated height at world position
|
||||
func (w *WorldConfig) GetHeightAt(heightmap [][]float32, worldX, worldZ float32) float32 {
|
||||
// Convert world coordinates to sample coordinates
|
||||
sampleX := (worldX + w.WorldWidth*0.5) / w.UnitsPerSample
|
||||
sampleZ := (worldZ + w.WorldHeight*0.5) / w.UnitsPerSample
|
||||
|
||||
// Clamp to valid range
|
||||
sampleX = float32(math.Max(0, math.Min(float64(w.SamplesPerSide-1), float64(sampleX))))
|
||||
sampleZ = float32(math.Max(0, math.Min(float64(w.SamplesPerSide-1), float64(sampleZ))))
|
||||
|
||||
// Get integer sample indices
|
||||
x0 := int(math.Floor(float64(sampleX)))
|
||||
z0 := int(math.Floor(float64(sampleZ)))
|
||||
x1 := int(math.Min(float64(x0+1), float64(w.SamplesPerSide-1)))
|
||||
z1 := int(math.Min(float64(z0+1), float64(w.SamplesPerSide-1)))
|
||||
|
||||
// Get fractional parts for interpolation
|
||||
fx := sampleX - float32(x0)
|
||||
fz := sampleZ - float32(z0)
|
||||
|
||||
// Get heights at four corners
|
||||
h00 := heightmap[z0][x0]
|
||||
h10 := heightmap[z0][x1]
|
||||
h01 := heightmap[z1][x0]
|
||||
h11 := heightmap[z1][x1]
|
||||
|
||||
// Bilinear interpolation
|
||||
h0 := h00*(1-fx) + h10*fx
|
||||
h1 := h01*(1-fx) + h11*fx
|
||||
return h0*(1-fz) + h1*fz
|
||||
}
|
||||
BIN
tools/generate_heightmap
Executable file
BIN
tools/generate_heightmap
Executable file
Binary file not shown.
69
tools/generate_heightmap.cpp
Normal file
69
tools/generate_heightmap.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// Simple heightmap generator for testing different world sizes
|
||||
void generateHeightmap(const std::string& filename, int size, float amplitude = 10.0f) {
|
||||
std::vector<float> data(size * size);
|
||||
|
||||
// Generate a simple heightmap with some hills
|
||||
for (int z = 0; z < size; z++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
float fx = (float)x / (size - 1) * 2.0f - 1.0f;
|
||||
float fz = (float)z / (size - 1) * 2.0f - 1.0f;
|
||||
|
||||
// Create some hills using sine waves
|
||||
float height = 0;
|
||||
height += sin(fx * 3.14159f * 2.0f) * cos(fz * 3.14159f * 2.0f) * amplitude * 0.5f;
|
||||
height += sin(fx * 3.14159f * 4.0f) * sin(fz * 3.14159f * 4.0f) * amplitude * 0.25f;
|
||||
|
||||
// Add a central mountain
|
||||
float dist = sqrt(fx * fx + fz * fz);
|
||||
height += std::max(0.0f, (1.0f - dist) * amplitude);
|
||||
|
||||
data[z * size + x] = height;
|
||||
}
|
||||
}
|
||||
|
||||
// Write to binary file
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
if (!file) {
|
||||
std::cerr << "Failed to open file: " << filename << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t fileSize = size;
|
||||
file.write(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
|
||||
file.write(reinterpret_cast<char*>(data.data()), data.size() * sizeof(float));
|
||||
|
||||
std::cout << "Generated heightmap: " << filename << " (" << size << "x" << size << ")\n";
|
||||
std::cout << "World size will be: " << (size-1) << "x" << (size-1) << " units\n";
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cout << "Usage: " << argv[0] << " <size> <output_file> [amplitude]\n";
|
||||
std::cout << "Example: " << argv[0] << " 256 heightmap_256.bin 20.0\n";
|
||||
std::cout << "\nCommon sizes:\n";
|
||||
std::cout << " 64x64 -> 63x63 unit world (small)\n";
|
||||
std::cout << " 128x128 -> 127x127 unit world (medium)\n";
|
||||
std::cout << " 256x256 -> 255x255 unit world (large)\n";
|
||||
std::cout << " 512x512 -> 511x511 unit world (huge)\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
int size = std::stoi(argv[1]);
|
||||
std::string filename = argv[2];
|
||||
float amplitude = (argc > 3) ? std::stof(argv[3]) : 10.0f;
|
||||
|
||||
if (size < 2) {
|
||||
std::cerr << "Size must be at least 2\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
generateHeightmap(filename, size, amplitude);
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user