1
0

return movement authority to server

This commit is contained in:
Sky Johnson 2025-09-09 22:38:23 -05:00
parent 663444157e
commit dd682b8b67
7 changed files with 428 additions and 0 deletions

BIN
assets/heightmap_large.bin Normal file

Binary file not shown.

BIN
assets/heightmap_small.bin Normal file

Binary file not shown.

64
client/config.hpp Normal file
View 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";
}
};

View 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
View 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

Binary file not shown.

View 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;
}