asteroidgen/src/Density.h

146 lines
3.4 KiB
C++

#pragma once
// system
#include <vector>
// external
#include <glm/vec3.hpp>
#include <glm/gtc/random.hpp>
// vendor
#include <RTree.h>
#include <PerlinNoise.hpp>
#include <stb_image_write.h>
struct Impact {
glm::vec3 pos;
float r;
bool contains(const glm::vec3 &p) const {
return (glm::length(pos - p) < r);
}
};
class Density {
std::vector<Impact> impacts;
RTree<int, float, 3, float> impactTree;
siv::PerlinNoise noise;
glm::vec3 scale;
float scaleFactor;
float frequency, octave;
size_t version;
public:
Density() :
scale(1.0f), scaleFactor(4), frequency(2), octave(2), version(0) {
}
const std::vector<Impact> &getImpacts() {
return impacts;
}
size_t getVersion() {
return version;
}
void addImpact(Impact &impact) {
size_t idx = impacts.size();
impacts.push_back(impact);
glm::vec3 l = impact.pos - glm::vec3(impact.r);
glm::vec3 u = impact.pos + glm::vec3(impact.r);
impactTree.Insert(&l.x, &u.x, idx);
version++;
}
void setSeed(uint32_t seed) {
noise.reseed(seed);
version++;
impacts.clear();
}
void setScale(const glm::vec3 &scale) {
this->scale = scale;
scaleFactor = 4.f / std::min(scale.x, std::min(scale.y, scale.z));
impacts.clear();
version++;
}
void setFrequency(float f) {
frequency = f;
impacts.clear();
version++;
}
void setOctave(float o) {
octave = o;
impacts.clear();
version++;
}
float operator()(const glm::vec3 &p, bool includeImpacts = true) const {
if (includeImpacts && impacts.size() > 0) {
bool insideImpact = false;
impactTree.Search(&p.x, &p.x, [&](int idx) -> bool {
if (impacts[idx].contains(p)) {
insideImpact = true;
return false;
} else {
return true;
}
});
if (insideImpact)
return 0.f;
}
float v = (float) noise.octaveNoise0_1(p.x * frequency, p.y * frequency,
p.z * frequency, octave);
glm::vec3 r = p - glm::vec3(0.5);
v *= std::max(1.f - scaleFactor * dot(scale * r, r), 0.f);
return v;
}
bool intersectIsolevel(const glm::vec3 &start, const glm::vec3 &end,
glm::vec3 &pos, float isolevel, float resolution) const {
glm::vec3 step = glm::normalize(end - start) * glm::vec3(resolution);
size_t nSteps = ceil(glm::length(end - start) / resolution);
pos = start;
for (size_t iStep = 0; iStep < nSteps; iStep++) {
if ((*this)(pos) > isolevel)
return true;
pos += step;
}
return false;
}
void addRandomImpacts(size_t count, float isolevel, float resolution,
float minRadius, float maxRadius, int exponent) {
for (size_t i = 0; i < count; i++) {
glm::vec3 start = glm::vec3(0.5f) + glm::sphericalRand(0.6f);
glm::vec3 end = glm::vec3(0.5f) + glm::sphericalRand(0.6f);
Impact impact;
if (intersectIsolevel(start, end, impact.pos, isolevel,
resolution)) {
impact.pos -= (impact.r / 1.5f) * normalize(end-start);
impact.r = minRadius
+ pow(glm::linearRand(0.f, 1.f), exponent) * maxRadius;
addImpact(impact);
}
}
}
void saveCrossSection(const char *filename, size_t pixels) {
float step = 1.f / pixels;
glm::vec3 pos(step / 2, step / 2, 0.5f);
std::vector<uint8_t> img(pixels * pixels);
for (size_t ix = 0; ix < pixels; ix++) {
for (size_t iy = 0; iy < pixels; iy++) {
glm::vec3 pos(step * (0.5f + ix), step * (0.5f + iy), 0.5f);
img[iy * pixels + ix] = (*this)(pos) * 255;
}
}
stbi_write_png(filename, pixels, pixels, 1, img.data(), 0);
}
};