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