feat(core): add temperature and humidity climate sampling
This commit is contained in:
1 parent
e295652051
commit
337150dca9
2 files changed
+236
No files matched your search
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include "World.h"
|
||||||
|
#include "Math.h"
|
||||||
|
#include "BiomeGen.h"
|
||||||
|
#include "Weather.h"
|
||||||
|
|
||||||
|
namespace vf {
|
||||||
|
|
||||||
|
struct ClimateChange {
|
||||||
|
Vec3i pos;
|
||||||
|
Block from;
|
||||||
|
Block to;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClimateConfig {
|
||||||
|
int seaLevel = 26;
|
||||||
|
// Temperature drop per block of elevation above sea level.
|
||||||
|
float heightLapse = 0.006f;
|
||||||
|
// Temperature drop per block of distance from the z=0 "equator".
|
||||||
|
float latitudeLapse = 0.0009f;
|
||||||
|
// Cold enough to lay snow and freeze exposed water.
|
||||||
|
float freezeTemperature = 0.15f;
|
||||||
|
// Warm enough to thaw ice and melt snow (hysteresis above freezing).
|
||||||
|
float meltTemperature = 0.5f;
|
||||||
|
// Milder threshold at which water freezes if it already touches ice, so a
|
||||||
|
// frozen patch creeps outward across a surface.
|
||||||
|
float frostSpreadTemperature = 0.30f;
|
||||||
|
int maxSnowDepth = 8;
|
||||||
|
// Ticks a block of ice must survive in the cold before it compacts to
|
||||||
|
// packed ice.
|
||||||
|
int packThreshold = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deterministic per-region climate simulation layered on a BiomeGen. Column
|
||||||
|
// temperature is the biome sample adjusted for elevation and latitude. During
|
||||||
|
// snowfall it accumulates SnowLayer depth on exposed surfaces; exposed water
|
||||||
|
// freezes to Ice when cold; Ice and snow thaw when warm or beside a heat source
|
||||||
|
// (lava, torches, glowstone). tick scans the region in a fixed order and emits
|
||||||
|
// the resulting block changes without mutating the world; apply commits them.
|
||||||
|
class Climate {
|
||||||
|
public:
|
||||||
|
Climate(const BiomeGen& biomes, const ClimateConfig& config = {});
|
||||||
|
|
||||||
|
float temperatureAt(int gx, int gy, int gz) const;
|
||||||
|
float humidityAt(int gx, int gz) const;
|
||||||
|
bool freezingAt(int gx, int gy, int gz) const;
|
||||||
|
bool thawingAt(int gx, int gy, int gz) const;
|
||||||
|
|
||||||
|
std::vector<ClimateChange> tick(World& world, const Vec3i& origin, const Vec3i& dims,
|
||||||
|
Precipitation precip);
|
||||||
|
|
||||||
|
static void apply(World& world, const std::vector<ClimateChange>& changes);
|
||||||
|
|
||||||
|
int snowDepthAt(const Vec3i& pos) const;
|
||||||
|
int iceAgeAt(const Vec3i& pos) const;
|
||||||
|
std::size_t trackedSnowColumns() const { return snowDepth.size(); }
|
||||||
|
|
||||||
|
const ClimateConfig& settings() const { return config; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool heatSourceNear(const World& world, const Vec3i& pos) const;
|
||||||
|
bool iceNear(const World& world, const Vec3i& pos) const;
|
||||||
|
|
||||||
|
const BiomeGen& biomes;
|
||||||
|
ClimateConfig config;
|
||||||
|
std::unordered_map<Vec3i, int, Vec3iHash> snowDepth;
|
||||||
|
std::unordered_map<Vec3i, int, Vec3iHash> iceAge;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
#include "vf/Climate.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace vf {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool isHeatSource(Block b) {
|
||||||
|
switch (b) {
|
||||||
|
case Block::Lava:
|
||||||
|
case Block::Torch:
|
||||||
|
case Block::Glowstone:
|
||||||
|
case Block::FurnaceLit:
|
||||||
|
case Block::RedstoneTorch:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFreezable(Block b) { return b == Block::Water; }
|
||||||
|
|
||||||
|
const Vec3i neighbourOffsets[6] = {
|
||||||
|
{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1},
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Climate::Climate(const BiomeGen& biomes, const ClimateConfig& config)
|
||||||
|
: biomes(biomes), config(config) {}
|
||||||
|
|
||||||
|
float Climate::temperatureAt(int gx, int gy, int gz) const {
|
||||||
|
ClimateSample sample = biomes.climateAt(gx, gz);
|
||||||
|
float elevation = static_cast<float>(gy - config.seaLevel);
|
||||||
|
float heightAdjust = elevation > 0.0f ? -config.heightLapse * elevation : 0.0f;
|
||||||
|
float latitudeAdjust = -config.latitudeLapse * static_cast<float>(std::abs(gz));
|
||||||
|
return sample.temperature + heightAdjust + latitudeAdjust;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Climate::humidityAt(int gx, int gz) const {
|
||||||
|
return biomes.climateAt(gx, gz).humidity;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Climate::freezingAt(int gx, int gy, int gz) const {
|
||||||
|
return temperatureAt(gx, gy, gz) < config.freezeTemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Climate::thawingAt(int gx, int gy, int gz) const {
|
||||||
|
return temperatureAt(gx, gy, gz) >= config.meltTemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Climate::heatSourceNear(const World& world, const Vec3i& pos) const {
|
||||||
|
for (const Vec3i& off : neighbourOffsets) {
|
||||||
|
if (isHeatSource(world.blockAt(pos.x + off.x, pos.y + off.y, pos.z + off.z))) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Climate::iceNear(const World& world, const Vec3i& pos) const {
|
||||||
|
for (const Vec3i& off : neighbourOffsets) {
|
||||||
|
Block b = world.blockAt(pos.x + off.x, pos.y + off.y, pos.z + off.z);
|
||||||
|
if (b == Block::Ice || b == Block::PackedIce) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Climate::snowDepthAt(const Vec3i& pos) const {
|
||||||
|
auto it = snowDepth.find(pos);
|
||||||
|
return it == snowDepth.end() ? 0 : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Climate::iceAgeAt(const Vec3i& pos) const {
|
||||||
|
auto it = iceAge.find(pos);
|
||||||
|
return it == iceAge.end() ? 0 : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ClimateChange> Climate::tick(World& world, const Vec3i& origin, const Vec3i& dims,
|
||||||
|
Precipitation precip) {
|
||||||
|
std::vector<ClimateChange> changes;
|
||||||
|
int x0 = origin.x, x1 = origin.x + dims.x;
|
||||||
|
int y0 = origin.y, y1 = origin.y + dims.y;
|
||||||
|
int z0 = origin.z, z1 = origin.z + dims.z;
|
||||||
|
|
||||||
|
for (int x = x0; x < x1; ++x) {
|
||||||
|
for (int z = z0; z < z1; ++z) {
|
||||||
|
for (int y = y1 - 1; y >= y0; --y) {
|
||||||
|
Vec3i cell{x, y, z};
|
||||||
|
Block here = world.blockAt(x, y, z);
|
||||||
|
Block above = world.blockAt(x, y + 1, z);
|
||||||
|
Block below = world.blockAt(x, y - 1, z);
|
||||||
|
float temperature = temperatureAt(x, y, z);
|
||||||
|
bool cold = temperature < config.freezeTemperature;
|
||||||
|
bool warm = temperature >= config.meltTemperature;
|
||||||
|
bool warmOrHeated = warm || heatSourceNear(world, cell);
|
||||||
|
|
||||||
|
if (isFreezable(here) && above == Block::Air) {
|
||||||
|
bool frostCreep = temperature < config.frostSpreadTemperature &&
|
||||||
|
iceNear(world, cell);
|
||||||
|
if (cold || frostCreep) {
|
||||||
|
changes.push_back({cell, here, Block::Ice});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (here == Block::Ice) {
|
||||||
|
if (warmOrHeated) {
|
||||||
|
changes.push_back({cell, here, Block::Water});
|
||||||
|
snowDepth.erase(cell);
|
||||||
|
iceAge.erase(cell);
|
||||||
|
} else if (cold) {
|
||||||
|
int age = iceAgeAt(cell) + 1;
|
||||||
|
if (age >= config.packThreshold) {
|
||||||
|
changes.push_back({cell, here, Block::PackedIce});
|
||||||
|
iceAge.erase(cell);
|
||||||
|
} else {
|
||||||
|
iceAge[cell] = age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (here == Block::PackedIce) {
|
||||||
|
if (warmOrHeated) changes.push_back({cell, here, Block::Water});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (here == Block::SnowLayer) {
|
||||||
|
if (warmOrHeated) {
|
||||||
|
int depth = snowDepthAt(cell) - 1;
|
||||||
|
if (depth <= 0) {
|
||||||
|
changes.push_back({cell, here, Block::Air});
|
||||||
|
snowDepth.erase(cell);
|
||||||
|
} else {
|
||||||
|
snowDepth[cell] = depth;
|
||||||
|
}
|
||||||
|
} else if (precip == Precipitation::Snow && cold) {
|
||||||
|
int depth = snowDepthAt(cell);
|
||||||
|
if (depth < config.maxSnowDepth) snowDepth[cell] = depth + 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (precip == Precipitation::Snow && cold && here == Block::Air &&
|
||||||
|
isOpaque(below) && below != Block::Ice) {
|
||||||
|
changes.push_back({cell, Block::Air, Block::SnowLayer});
|
||||||
|
snowDepth[cell] = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Climate::apply(World& world, const std::vector<ClimateChange>& changes) {
|
||||||
|
for (const ClimateChange& change : changes) {
|
||||||
|
world.setBlock(change.pos.x, change.pos.y, change.pos.z, change.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
Reference in new issue
Block a user