1
0
Fork 0
Univerxel/src/server/world/generator.hpp

164 lines
7.3 KiB
C++

#pragma once
#include <variant>
#include "Noise.hpp"
#include "../../core/world/Voxel.hpp"
#include "../../core/data/math.hpp"
namespace world::generator {
/// Abstract Noise generator
class Abstract {
public:
/// Generate chunk voxels
/// MAYBE: use template to avoid virtual
virtual void generate(const chunk_pos &at, std::array<Voxel, CHUNK_SIZE> &out) = 0;
/// Get gravity vector at given point
virtual glm::vec3 getGravity(const voxel_pos& point) const = 0;
};
// Generate empty space
class Void: public Abstract {
public:
struct Params { };
Void() = default;
void generate(const chunk_pos &, std::array<Voxel, CHUNK_SIZE> &out) override {
out.fill(Voxel());
}
glm::vec3 getGravity(const voxel_pos&) const override { return glm::vec3(0); }
};
/// Endless cave network
class Cave: public Abstract {
public:
struct Params {
Params(int seed = 42, float density = 0, float gran = 30): seed(seed), density(density), granularity(gran) { }
/// Random generator start
int seed;
/// Offset density overrage
float density;
/// Speed of density change
float granularity;
};
Cave(const Params p): params(p), density(Noise::SimplexFractal(p.seed)), material(Noise::Cellular(p.seed * 5, .1)) { }
void generate(const chunk_pos &pos, std::array<Voxel, CHUNK_SIZE> &out) override {
auto densitySet = density.Get(pos, CHUNK_LENGTH);
auto materialSet = material.Get(pos, CHUNK_LENGTH);
for (size_t i = 0; i < CHUNK_SIZE; i++) {
const auto density = std::clamp((densitySet.get()[i] + params.density) * params.granularity, 0.f, 1.f) * Voxel::DENSITY_MAX;
const auto material = density > 0 ? 2 + std::clamp(static_cast<int>(std::lrint((materialSet.get()[i] + 1) / 2 * (materials::count - 3))),
0, materials::count - 3) : 1; //NOTE: map (approx -1, 1) to (1, mat_max)
out[i] = Voxel(material, density);
}
}
glm::vec3 getGravity(const voxel_pos&) const override { return glm::vec3(-1, 0, 0); }
private:
Params params;
Noise density;
Noise material;
};
enum class PlanetShape { Cube, Sphere };
/// Abstract shaped planet generator
template <PlanetShape PS>
class Planet: public Abstract {
public:
struct Params: Cave::Params {
Params(voxel_pos::value_type height, int seed = 42, float surface_roughness = .1, float depth_roughness = .05, float density = 0, float gran = 30):
Cave::Params(seed, density, gran), height(height), surface_roughness(surface_roughness), depth_roughness(depth_roughness) { }
/// Sea level (minimal density)
voxel_pos::value_type height;
/// Density decrease over height
float surface_roughness;
/// Density decrease under height
float depth_roughness;
/// Sea border depth
float beach_depth = .01f;
/// Sea ground displacement
float beach_displacement = .01f;
};
Planet(const Params p) : params(p), density(Noise::SimplexFractal(p.seed)), displacement(Noise::Simplex(p.seed * 5, .01)) {}
void generate(const chunk_pos &pos, std::array<Voxel, CHUNK_SIZE> &out) override {
auto densitySet = density.Get(pos, CHUNK_LENGTH);
auto displacementSet = displacement.Get(pos, CHUNK_LENGTH);
for (size_t i = 0; i < CHUNK_SIZE; i++) {
const auto heightRatio = static_cast<float>(getHeight(glm::multiply(pos) + glm::llvec3(glm::fromIdx(i))) - params.height) / params.height;
const auto verticalDensityOffset = heightRatio / (heightRatio >= 0 ? params.surface_roughness : params.depth_roughness);
const auto density = std::clamp((densitySet.get()[i] + params.density - verticalDensityOffset) * params.granularity, 0.f, 1.f) * Voxel::DENSITY_MAX;
out[i] = [&]() -> Voxel {
if (density > 0) {
const auto material = [&]() -> int {
const auto noisedHeightRatio = heightRatio - displacementSet.get()[i] * params.beach_displacement;
if(noisedHeightRatio >= 0) {
return densitySet.get()[i] + params.density < ((heightRatio + 0.007f) / params.surface_roughness) ? materials::GRASS : materials::DIRT;
} else {
return noisedHeightRatio >= -params.beach_depth ? materials::SAND : materials::ROCK;
}
}();
return Voxel(material, density);
} else {
if (heightRatio >= 0) {
return Voxel(materials::AIR, (heightRatio < params.surface_roughness) * Voxel::DENSITY_MAX);
} else {
return Voxel(materials::WATER, std::clamp(-heightRatio * params.height, 0.f, 1.f) * Voxel::DENSITY_MAX);
}
}
}();
}
}
glm::vec3 getGravity(const voxel_pos& pos) const override {
const auto heightRatio = static_cast<float>((getHeight(pos) - params.height) / params.height);
const auto scale = params.height >> 7;
const auto mass = scale * scale * scale; //FIXME: average material density
return -getSurfaceDir(pos) * std::max(0.f, 1.f - heightRatio * heightRatio) * glm::vec3(mass);
}
private:
static constexpr PlanetShape shape = PS;
static constexpr glm::f64 getHeight(const voxel_pos &p) {
if constexpr(shape == PlanetShape::Cube) {
return glm::max_axis(glm::abs(p));
} else {
return glm::length(glm::dvec3(p));
}
}
/// Centre inverse direction
// NOTE: So center point is consider mass center
static constexpr glm::vec3 getSurfaceDir(const voxel_pos &p) {
if constexpr(shape == PlanetShape::Cube) {
return glm::inormalize(p);
} else {
return glm::normalize(glm::dvec3(p));
}
}
Params params;
Noise density;
Noise displacement;
};
using CubicPlanet = Planet<PlanetShape::Cube>;
using RoundPlanet = Planet<PlanetShape::Sphere>;
using params = std::variant<Void::Params, Cave::Params, CubicPlanet::Params, RoundPlanet::Params>;
inline std::unique_ptr<Abstract> load(const params &p) {
const auto get_new = [](const params &p) -> Abstract* {
if(std::holds_alternative<Void::Params>(p)) {
return new Void();
} else if(const auto c = std::get_if<Cave::Params>(&p)) {
return new Cave(*c);
} else if(const auto pc = std::get_if<CubicPlanet::Params>(&p)) {
return new CubicPlanet(*pc);
} else if(const auto ps = std::get_if<RoundPlanet::Params>(&p)) {
return new RoundPlanet(*ps);
}
return nullptr;
};
return std::unique_ptr<Abstract>(get_new(p));
}
}