146 lines
3.4 KiB
C++
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);
|
|
}
|
|
};
|
|
|